diff --git a/CLAUDE.md b/CLAUDE.md index 89b2fe0f..f3b46f15 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,8 @@ When reviewing or changing compiler behavior: Example: `function test($a = 1, $b, $c) {}` is legal in PHP, but the default value for `$a` is effectively ignored and all parameters become required. This is a PHP historical compatibility artifact. AOT should reject it during preprocessing instead of preserving the behavior. +Example: PHP permits `return $value;` inside `__construct()` and lets callers consume `parent::__construct()` as a value, even though constructors cannot declare a return type. AOT treats constructors consistently with C++/Java-style semantics: constructors initialize objects and must not return values. `return;` is allowed, but `return $value;` or using a constructor call as a value must be rejected during static compilation. + ## Build & Test Commands ```bash diff --git a/phpunit/code/constructor-return-value.php b/phpunit/code/constructor-return-value.php new file mode 100644 index 00000000..c373b92b --- /dev/null +++ b/phpunit/code/constructor-return-value.php @@ -0,0 +1,14 @@ +exec('Method `ConstructorReturnType::__construct()` cannot declare a return type', 'constructor-return-type.php'); } + public function testConstructorCannotReturnValue() + { + $this->exec('Method `ConstructorReturnValue::__construct()` cannot return a value', 'constructor-return-value.php'); + } + + public function testParentConstructorCannotBeUsedAsValue() + { + $this->exec('Cannot use void expression as assignment value', 'parent-constructor-used-as-value.php'); + } + + public function testParentConstructorCannotBeUsedAsArgument() + { + $this->exec('Cannot use void expression as function argument', 'parent-constructor-used-as-argument.php'); + } + public function testDestructorCannotDeclareReturnType() { $this->exec('Method `DestructorReturnType::__destruct()` cannot declare a return type', 'destructor-return-type.php'); diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 0cc2070d..912ab209 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -887,6 +887,23 @@ class CompilerBase extends \PhpAot\Core\Translator return strtolower($fullClassName . '::' . $method); } + protected function isCurrentConstructor(): bool + { + return $this->method === '__construct'; + } + + protected function getCurrentMethodDisplayName(): string + { + return $this->getFullClassName() . '::' . $this->method; + } + + protected function assertExprCanBeUsedAsValue(NodeAbstract $expr, string $context = 'value'): void + { + if ($this->detectTypeOfExpr($expr) === self::TYPE_VOID) { + $this->fatalError($expr, 'Cannot use void expression as ' . $context); + } + } + public function getNamespacedClassName(string $class, string $currentNamespace = ''): string { if ($class === '') { @@ -1686,6 +1703,12 @@ class CompilerBase extends \PhpAot\Core\Translator } // 实际函数的返回值 $type = $this->detectTypeOfExpr($v->expr); + if ($this->isCurrentConstructor() && !$this->context->inClosure) { + $this->fatalError($v, 'Method `' . $this->getCurrentMethodDisplayName() . '()` cannot return a value'); + } + if ($type === self::TYPE_VOID) { + $this->fatalError($v, 'Cannot return void expression'); + } $expr = $this->parseExpr($v->expr); $returnType = $this->getReturnType(); @@ -3609,6 +3632,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseCallArgValue(Node\Arg $arg): string { + $this->assertExprCanBeUsedAsValue($arg->value, 'function argument'); return $this->materializeCallArgValue($arg->value, $this->parseArg($arg)); } @@ -4493,6 +4517,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function getTypeConvertedArg(Node\Arg $arg, ArgInfo $argInfo): string { $type = $this->detectTypeOfExpr($arg->value); + $this->assertExprCanBeUsedAsValue($arg->value, 'function argument'); if ($argInfo->byRef) { if ($this->isRefvalCall($arg->value)) { diff --git a/src/Php/Parser/AssignOpTrait.php b/src/Php/Parser/AssignOpTrait.php index d79072cd..1db08b7d 100644 --- a/src/Php/Parser/AssignOpTrait.php +++ b/src/Php/Parser/AssignOpTrait.php @@ -155,6 +155,9 @@ trait AssignOpTrait $this->fatalError($left, 'Cannot re-assign $this'); } $finalVarType = $type = $this->detectTypeOfExpr($right); + if ($type === self::TYPE_VOID) { + $this->fatalError($right, 'Cannot use void expression as assignment value'); + } if ($this->isVarExpr($left)) { if ($this->isStdContainer($var)) {