From f8c37f91c37540743790ce97ab8d09c6f79daa30 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 30 Jun 2026 17:08:29 +0800 Subject: [PATCH] =?UTF-8?q?test(type):=20=E6=B7=BB=E5=8A=A0=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=A3=80=E6=9F=A5=E5=92=8C=E5=B1=9E=E6=80=A7=E8=B5=8B?= =?UTF-8?q?=E5=80=BC=E7=9B=B8=E5=85=B3=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加实例属性类型检查消息包含类名的测试用例 - 添加属性联合赋值操作使用运行时类型检查的测试用例 - 添加动态属性赋值TypeError捕获的测试用例 - 添加二元运算符操作数从左到右求值顺序的测试用例 - 添加原生优化调用参数从左到右求值的测试用例 - 添加构造函数属性提升联合参数检查的测试用例 fix(assign): 修复属性赋值时的类型检查问题 - 在属性赋值操作中添加对象属性类型检查包装 - 修复属性赋值表达式中的类型验证逻辑 - 添加静态属性赋值的类型检查支持 fix(ctor): 修复构造函数属性提升的参数验证顺序 - 将构造函数属性提升移到参数类型验证之后执行 - 在数组初始化计划后添加错误检查机制 --- ...ternal-call-missing-required-named-arg.php | 6 +++ .../code/internal-call-unknown-named-arg.php | 6 +++ phpunit/code/unpack-after-named-arg.php | 10 +++++ src/Php/Parser/AssignOpTrait.php | 10 +++++ src/Php/Translator.php | 15 ++++---- .../promotion-union-param-check.phpt | 24 ++++++++++++ tests/aot/operator/binary-eval-order.phpt | 38 +++++++++++++++++++ tests/aot/stdlib/native-arg-eval-order.phpt | 23 +++++++++++ tests/aot/type_hits/009.phpt | 25 ++++++++++++ tests/aot/type_hits/010.phpt | 32 ++++++++++++++++ tests/aot/type_hits/011.phpt | 30 +++++++++++++++ 11 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 phpunit/code/internal-call-missing-required-named-arg.php create mode 100644 phpunit/code/internal-call-unknown-named-arg.php create mode 100644 phpunit/code/unpack-after-named-arg.php create mode 100644 tests/aot/object_ctor/promotion-union-param-check.phpt create mode 100644 tests/aot/operator/binary-eval-order.phpt create mode 100644 tests/aot/stdlib/native-arg-eval-order.phpt create mode 100644 tests/aot/type_hits/009.phpt create mode 100644 tests/aot/type_hits/010.phpt create mode 100644 tests/aot/type_hits/011.phpt diff --git a/phpunit/code/internal-call-missing-required-named-arg.php b/phpunit/code/internal-call-missing-required-named-arg.php new file mode 100644 index 00000000..750e71ca --- /dev/null +++ b/phpunit/code/internal-call-missing-required-named-arg.php @@ -0,0 +1,6 @@ +parseIdentifier($left->var); $propName = $this->identifierToStr($left->name, literal: true); $rightExpr = $this->trimBrackets($this->parseExpr($right)); + $rightExpr = $this->wrapObjectPropertyAssignTypeCheck($left, $right, $rightExpr); $tmp = $this->genTmpVarName(); $this->addLocalVar($tmp, self::TYPE_VAR); @@ -585,7 +586,16 @@ trait AssignOpTrait $var = $this->parseIdentifier($expr->var); $this->context->inAssignExpr = $inAssignExpr; + if ($this->isPropertyFetch($expr->var)) { + $this->assertCanAssignObjectProp($expr->var, $expr->expr); + } elseif ($this->isStaticPropertyFetch($expr->var)) { + $this->assertCanAssignStaticProp($expr->var, $expr->expr); + } + $right = $this->parseExpr($expr->expr); + if ($this->isPropertyFetch($expr->var) || $this->isStaticPropertyFetch($expr->var)) { + $right = $this->wrapObjectPropertyAssignTypeCheck($expr->var, $expr->expr, $right); + } if ($this->isVarExpr($expr->expr) and !$this->hasVar($right)) { $this->errorUndefinedVariable($expr->expr); } diff --git a/src/Php/Translator.php b/src/Php/Translator.php index b65c848f..4e7d8059 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -1790,6 +1790,7 @@ CODE; if (!$property->isStatic() && $property->arrayInitPlan && $property->default) { $body = "auto value = {$property->arrayInitPlan->expr};\n"; $body .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n"; + $body .= "php::throwErrorIfOccurred();\n"; $code .= $this->wrapArrayInitPlan($property->arrayInitPlan, $body); } } @@ -3120,19 +3121,19 @@ CODE; $this->indentLevel++; $code .= $this->genScopeVarDecl(); $code .= "\n"; - // Constructor Property Promotion - foreach ($this->functionDef->argInfoList as $argInfo) { - if (!$argInfo->property) { - continue; - } - $code .= $this->genPropertyPromotion($argInfo); - } // Runtime union/nullable parameter type checks foreach ($this->functionDef->argInfoList as $i => $argInfo) { if (!empty($argInfo->typeCheck)) { $code .= $this->genUnionParamCheck($argInfo, $i); } } + // Constructor Property Promotion happens after parameter type validation. + foreach ($this->functionDef->argInfoList as $argInfo) { + if (!$argInfo->property) { + continue; + } + $code .= $this->genPropertyPromotion($argInfo); + } $this->indentLevel--; // 构建 PHP 级别的函数名用于 debug backtrace if ($this->class) { diff --git a/tests/aot/object_ctor/promotion-union-param-check.phpt b/tests/aot/object_ctor/promotion-union-param-check.phpt new file mode 100644 index 00000000..501e95dc --- /dev/null +++ b/tests/aot/object_ctor/promotion-union-param-check.phpt @@ -0,0 +1,24 @@ +--TEST-- +Constructor property promotion checks union parameter before assignment +--ENV-- +USE_ZEND_ALLOC=0 +--FILE-- +getMessage()); + } +} +?> +--EXPECT-- +string(96) "PromotedUnionHolder::__construct(): Argument #1 ($value) must be of type int|string, array given" diff --git a/tests/aot/operator/binary-eval-order.phpt b/tests/aot/operator/binary-eval-order.phpt new file mode 100644 index 00000000..5e77f978 --- /dev/null +++ b/tests/aot/operator/binary-eval-order.phpt @@ -0,0 +1,38 @@ +--TEST-- +Binary operator operands are evaluated left-to-right +--FILE-- + (print "spaceship-right\n")); + var_dump((print "pow-left\n") ** (print "pow-right\n")); + var_dump(((print "logic-left\n") && false && (print "logic-right\n")) + (print "logic-after\n")); +} +?> +--EXPECT-- +sub-left +sub-right +int(0) +nested-left +nested-right-left +nested-right-right +int(2) +eq-left +eq-right +bool(true) +same-left +same-right +bool(true) +spaceship-left +spaceship-right +int(0) +pow-left +pow-right +int(1) +logic-left +logic-after +int(1) diff --git a/tests/aot/stdlib/native-arg-eval-order.phpt b/tests/aot/stdlib/native-arg-eval-order.phpt new file mode 100644 index 00000000..ac72de80 --- /dev/null +++ b/tests/aot/stdlib/native-arg-eval-order.phpt @@ -0,0 +1,23 @@ +--TEST-- +Native optimized calls evaluate arguments left-to-right +--FILE-- +'; +var_dump($text->replace((print "method-left\n") ? "x" : "x", (print "method-right\n") ? "y" : "y")); +?> +--EXPECT-- +repeat-left +repeat-right +string(2) "xx" +round-left +round-right +float(2.3) +cmp-left +cmp-right +int(-1) +method-left +method-right +string(3) "" diff --git a/tests/aot/type_hits/009.phpt b/tests/aot/type_hits/009.phpt new file mode 100644 index 00000000..28763bbe --- /dev/null +++ b/tests/aot/type_hits/009.phpt @@ -0,0 +1,25 @@ +--TEST-- +type hits: instance property type check message includes class name +--FILE-- +union = null; + } catch (TypeError $e) { + var_dump($e->getMessage()); + } + } +} + +function main() +{ + (new TypeHitPropertyMessage())->setInvalid(); +} +?> +--EXPECT-- +string(69) "TypeHitPropertyMessage::$union must be of type int|string, null given" diff --git a/tests/aot/type_hits/010.phpt b/tests/aot/type_hits/010.phpt new file mode 100644 index 00000000..ab024bd4 --- /dev/null +++ b/tests/aot/type_hits/010.phpt @@ -0,0 +1,32 @@ +--TEST-- +type hits: property coalesce assignment uses runtime type check +--ENV-- +USE_ZEND_ALLOC=0 +--FILE-- +union ??= null; + } catch (TypeError $e) { + var_dump($e->getMessage()); + } + + $this->union = "ok"; + $this->union ??= null; + var_dump($this->union); + } +} + +function main(): void +{ + (new TypeHitCoalesceProperty())->run(); +} +?> +--EXPECT-- +string(70) "TypeHitCoalesceProperty::$union must be of type int|string, null given" +string(2) "ok" diff --git a/tests/aot/type_hits/011.phpt b/tests/aot/type_hits/011.phpt new file mode 100644 index 00000000..4c195609 --- /dev/null +++ b/tests/aot/type_hits/011.phpt @@ -0,0 +1,30 @@ +--TEST-- +AOT dynamic property assignment TypeError can be caught +--FILE-- +$prop = $value; + var_dump('not reached'); + } catch (TypeError $e) { + var_dump(get_class($e)); + var_dump(str_contains($e->getMessage(), 'Cannot assign null to property TypeHitDynamicProperty::$union')); + } +} + +function main(): void +{ + $obj = new TypeHitDynamicProperty(); + assign_dynamic($obj, 'union', null); + $obj->union = 'ok'; + var_dump($obj->union); +} +?> +--EXPECT-- +string(9) "TypeError" +bool(true) +string(2) "ok"