From 13258d34a60bfff35317546a7fe3b2688e0fb2ea Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 30 Jun 2026 18:40:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(parser):=20=E8=A7=A3=E5=86=B3=E6=9E=84?= =?UTF-8?q?=E9=80=A0=E5=87=BD=E6=95=B0=E8=BF=94=E5=9B=9E=E5=80=BC=E5=92=8C?= =?UTF-8?q?void=E8=A1=A8=E8=BE=BE=E5=BC=8F=E4=BD=BF=E7=94=A8=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在赋值操作中添加void表达式检查,防止将void用作赋值值 - 添加构造函数返回值检测,禁止构造函数返回值 - 添加函数参数中的void表达式检查 - 新增构造函数返回值测试用例 - 新增父类构造函数作为值使用的测试用例 - 更新文档说明构造函数语义一致性要求 --- CLAUDE.md | 2 ++ phpunit/code/constructor-return-value.php | 14 +++++++++++ .../parent-constructor-used-as-argument.php | 21 ++++++++++++++++ .../code/parent-constructor-used-as-value.php | 21 ++++++++++++++++ phpunit/src/ClassTest.php | 15 +++++++++++ src/Php/CompilerBase.php | 25 +++++++++++++++++++ src/Php/Parser/AssignOpTrait.php | 3 +++ 7 files changed, 101 insertions(+) create mode 100644 phpunit/code/constructor-return-value.php create mode 100644 phpunit/code/parent-constructor-used-as-argument.php create mode 100644 phpunit/code/parent-constructor-used-as-value.php 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)) {