feat(php): 实现赋值表达式返回值和函数参数检查功能

- 在数组赋值操作中添加临时变量存储以支持返回值
- 为属性赋值操作实现逗号表达式确保右值先计算后赋值
- 添加内部函数参数数量验证机制防止运行时错误
- 优化三元运算符表达式确保类型一致性
- 扩展函数调用优化器以支持参数类型和数量检查
- 添加测试用例验证数组维度赋值返回功能
- 添加测试用例验证属性赋值返回功能
- 添加测试用例验证三元运算符类型转换功能
pull/1/head
韩天峰 2 months ago
parent 246689db4c
commit 9138dea99f
  1. 26
      src/Php/CompilerBase.php
  2. 10
      src/Php/Optimizer/FuncCallOptimizer.php
  3. 23
      src/Php/Parser/AssignOpTrait.php
  4. 22
      tests/aot/basic/return-assign-array-dim.phpt
  5. 41
      tests/aot/basic/return-assign-set-prop-trait.phpt
  6. 30
      tests/aot/basic/return-ternary.phpt

@ -2585,6 +2585,23 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->fatalError($node, 'All execution code must be within a function, found stray code');
}
protected function checkInternalFunctionArgCount(string $funcName, Node\Expr\FuncCall $expr): void
{
$ref = Reflection::getFunction($funcName);
if (!$ref) {
return;
}
$minArgs = $ref->getNumberOfRequiredParameters();
$maxArgs = $ref->getNumberOfParameters();
$actualArgCount = count($expr->args);
if ($minArgs > 0 && $actualArgCount < $minArgs) {
$this->fatalError($expr, "{$funcName}() expects at least {$minArgs} argument(s), {$actualArgCount} given");
}
if (!$ref->isVariadic() && $maxArgs > 0 && $actualArgCount > $maxArgs) {
$this->fatalError($expr, "{$funcName}() expects at most {$maxArgs} argument(s), {$actualArgCount} given");
}
}
protected function parseFuncCall(Expr\FuncCall $expr): string
{
if ($this->isVarExpr($expr->name)) {
@ -2615,6 +2632,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
// 动态调用的函数,转换函数名为带有命名空间的全限定名称
$name = $this->getNamespacedFuncName($name);
$this->checkInternalFunctionArgCount($name, $expr);
$code = $this->parseFuncCallWithOptimizer($name, $expr);
if ($code !== false) {
return $code;
@ -3058,7 +3076,13 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($expr->if === null) {
return $this->parseValueSelection($expr, $expr->cond, $expr->else, self::OP_NOT_EMPTY);
}
return '(' . $this->parseExpr($expr->cond) . ') ? (' . $this->parseExpr($expr->if) . ') : (' . $this->parseExpr($expr->else) . ')';
$if = $this->parseExpr($expr->if);
$else = $this->parseExpr($expr->else);
if ($this->detectTypeOfExpr($expr->if) !== $this->detectTypeOfExpr($expr->else)) {
$if = 'php::Var(' . $if . ')';
$else = 'php::Var(' . $else . ')';
}
return '(' . $this->parseExpr($expr->cond) . ') ? (' . $if . ') : (' . $else . ')';
}
protected function parseMatch(Expr\Match_ $expr): string

@ -265,7 +265,7 @@ trait FuncCallOptimizer
$ref = Reflection::getFunction($funcName);
if (!$ref) {
return $this->_autoArgTypes[$funcName] = ['args' => '', 'variadic' => false];
return $this->_autoArgTypes[$funcName] = ['args' => '', 'variadic' => false, 'variadicType' => '', 'minArgs' => 0, 'maxArgs' => 0];
}
$types = [];
@ -284,7 +284,13 @@ trait FuncCallOptimizer
$types[] = $char;
}
return $this->_autoArgTypes[$funcName] = ['args' => implode('_', $types), 'variadic' => $variadic, 'variadicType' => $variadicType];
return $this->_autoArgTypes[$funcName] = [
'args' => implode('_', $types),
'variadic' => $variadic,
'variadicType' => $variadicType,
'minArgs' => $ref->getNumberOfRequiredParameters(),
'maxArgs' => $ref->getNumberOfParameters(),
];
}
protected function phpParamToArgChar(\ReflectionParameter $param): string

@ -27,26 +27,33 @@ trait AssignOpTrait
$array = $this->parseIdentifier($left->var);
$this->context->inAssignExpr = $oriInAssignExpr;
$code = '';
// 这是 PHP 的初始化+赋值写法,需要先创建数组
if (!$this->hasVar($array) and $this->isVarExpr($left->var)) {
$this->addLocalVar($array, self::TYPE_ARRAY);
}
$value = $this->trimBrackets($this->parseExpr($right));
$tmp = $this->genTmpVarName();
$this->addLocalVar($tmp, self::TYPE_VAR);
if ($left->dim === null) {
return $code . "{$array}.offsetSet(" . self::VALUE_NULL . ", {$value})";
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet(" . self::VALUE_NULL . ", {$tmp})" . '), ' . $tmp . ')';
}
$dim = $this->trimBrackets($this->parseIdentifier($left->dim));
return $code . "{$array}.offsetSet({$dim}, {$value})";
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet({$dim}, {$tmp})" . '), ' . $tmp . ')';
}
protected function parseAssignPropertyFetch(NodeAbstract $left, NodeAbstract $right): string
{
$array = $this->parseIdentifier($left->var);
$propName = $this->identifierToStr($left->name, literal: true);
$rightExpr = $this->trimBrackets($this->parseExpr($right));
return "{$array}.setProperty({$propName}, " . $this->trimBrackets($this->parseExpr($right)) . ')';
$tmp = $this->genTmpVarName();
$this->addLocalVar($tmp, self::TYPE_VAR);
// Comma expression: store RHS → execute side effect → evaluate to stored value
return '((' . $tmp . ' = ' . $rightExpr . ', ' . "{$array}.setProperty({$propName}, {$tmp})" . '), ' . $tmp . ')';
}
protected function parseRightAssociativeAssign(NodeAbstract $left, Expr\Assign $right): string
@ -535,12 +542,16 @@ trait AssignOpTrait
$propName = $this->identifierToStr($left->var->name);
$code = '';
$value = $this->trimBrackets($this->parseExpr($right));
$tmp = $this->genTmpVarName();
$this->addLocalVar($tmp, self::TYPE_VAR);
if ($left->dim === null) {
return $code . "{$obj}.appendArrayProperty({$propName}, {$value})";
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$obj}.appendArrayProperty({$propName}, {$tmp})" . '), ' . $tmp . ')';
}
$dim = $this->trimBrackets($this->parseIdentifier($left->dim));
return $code . "{$obj}.updateArrayProperty({$propName}, {$dim}, {$value})";
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$obj}.updateArrayProperty({$propName}, {$dim}, {$tmp})" . '), ' . $tmp . ')';
}
protected function parseAssignOpCoalesce(Expr\AssignOp\Coalesce $expr): string

@ -0,0 +1,22 @@
--TEST--
return assign array dim
--SKIPIF--
--FILE--
<?php
class TestReturnArrayDim
{
public function test()
{
$array['hello'] = [33];
return $array['world'] = 999;
}
}
function main()
{
$obj = new TestReturnArrayDim;
var_dump($obj->test());
}
?>
--EXPECT--
int(999)

@ -0,0 +1,41 @@
--TEST--
return assign array dim
--SKIPIF--
--FILE--
<?php
Trait TestReturnTrait {
protected ?array $array = null;
protected function makeArray(): array {
return [1, 3, 4];
}
public function test()
{
if ($this->array !== null) {
return $this->array;
}
return $this->array = $this->makeArray();
}
}
class TestReturnAssignSetProp
{
use TestReturnTrait;
}
function main()
{
$obj = new TestReturnAssignSetProp;
var_dump($obj->test());
}
?>
--EXPECT--
array(3) {
[0]=>
int(1)
[1]=>
int(3)
[2]=>
int(4)
}

@ -0,0 +1,30 @@
--TEST--
return ternary
--SKIPIF--
--FILE--
<?php
class TestReturnTernary
{
protected function isA(): bool {
return false;
}
protected function isB(): bool {
return random_int(1, 10000) % 100 < 50;
}
public function test()
{
return ($this->isA() && $this->isB()) ? 'str' : false;
}
}
function main()
{
$obj = new TestReturnTernary;
var_dump($obj->test());
}
?>
--EXPECT--
bool(false)
Loading…
Cancel
Save