fix(parser): 解决构造函数返回值和void表达式使用问题

- 在赋值操作中添加void表达式检查,防止将void用作赋值值
- 添加构造函数返回值检测,禁止构造函数返回值
- 添加函数参数中的void表达式检查
- 新增构造函数返回值测试用例
- 新增父类构造函数作为值使用的测试用例
- 更新文档说明构造函数语义一致性要求
pull/5/head
韩天峰 2 months ago
parent 4214e1e278
commit 13258d34a6
  1. 2
      CLAUDE.md
  2. 14
      phpunit/code/constructor-return-value.php
  3. 21
      phpunit/code/parent-constructor-used-as-argument.php
  4. 21
      phpunit/code/parent-constructor-used-as-value.php
  5. 15
      phpunit/src/ClassTest.php
  6. 25
      src/Php/CompilerBase.php
  7. 3
      src/Php/Parser/AssignOpTrait.php

@ -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

@ -0,0 +1,14 @@
<?php
class ConstructorReturnValue
{
public function __construct()
{
return 123;
}
}
function main(): void
{
new ConstructorReturnValue();
}

@ -0,0 +1,21 @@
<?php
class ParentConstructorArgument
{
public function __construct()
{
}
}
class ChildConstructorArgument extends ParentConstructorArgument
{
public function __construct()
{
var_dump(parent::__construct());
}
}
function main(): void
{
new ChildConstructorArgument();
}

@ -0,0 +1,21 @@
<?php
class ParentConstructorValue
{
public function __construct()
{
}
}
class ChildConstructorValue extends ParentConstructorValue
{
public function __construct()
{
$ret = parent::__construct();
}
}
function main(): void
{
new ChildConstructorValue();
}

@ -71,6 +71,21 @@ class ClassTest extends \BaseTest
$this->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');

@ -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)) {

@ -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)) {

Loading…
Cancel
Save