test(type): 添加类型检查和属性赋值相关测试用例

- 添加实例属性类型检查消息包含类名的测试用例
- 添加属性联合赋值操作使用运行时类型检查的测试用例
- 添加动态属性赋值TypeError捕获的测试用例
- 添加二元运算符操作数从左到右求值顺序的测试用例
- 添加原生优化调用参数从左到右求值的测试用例
- 添加构造函数属性提升联合参数检查的测试用例

fix(assign): 修复属性赋值时的类型检查问题

- 在属性赋值操作中添加对象属性类型检查包装
- 修复属性赋值表达式中的类型验证逻辑
- 添加静态属性赋值的类型检查支持

fix(ctor): 修复构造函数属性提升的参数验证顺序

- 将构造函数属性提升移到参数类型验证之后执行
- 在数组初始化计划后添加错误检查机制
pull/5/head
韩天峰 2 months ago
parent 54bca6942e
commit f8c37f91c3
  1. 6
      phpunit/code/internal-call-missing-required-named-arg.php
  2. 6
      phpunit/code/internal-call-unknown-named-arg.php
  3. 10
      phpunit/code/unpack-after-named-arg.php
  4. 10
      src/Php/Parser/AssignOpTrait.php
  5. 15
      src/Php/Translator.php
  6. 24
      tests/aot/object_ctor/promotion-union-param-check.phpt
  7. 38
      tests/aot/operator/binary-eval-order.phpt
  8. 23
      tests/aot/stdlib/native-arg-eval-order.phpt
  9. 25
      tests/aot/type_hits/009.phpt
  10. 32
      tests/aot/type_hits/010.phpt
  11. 30
      tests/aot/type_hits/011.phpt

@ -0,0 +1,6 @@
<?php
function main(): void
{
str_replace(search: "a", subject: "b");
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
strlen(foo: "abc");
}

@ -0,0 +1,10 @@
<?php
function target($a, $b): void
{
}
function main(): void
{
target(a: 1, ...[2]);
}

@ -49,6 +49,7 @@ trait AssignOpTrait
$array = $this->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);
}

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

@ -0,0 +1,24 @@
--TEST--
Constructor property promotion checks union parameter before assignment
--ENV--
USE_ZEND_ALLOC=0
--FILE--
<?php
class PromotedUnionHolder
{
public function __construct(public int|string $value)
{
}
}
function main(): void
{
try {
new PromotedUnionHolder([]);
} catch (TypeError $e) {
var_dump($e->getMessage());
}
}
?>
--EXPECT--
string(96) "PromotedUnionHolder::__construct(): Argument #1 ($value) must be of type int|string, array given"

@ -0,0 +1,38 @@
--TEST--
Binary operator operands are evaluated left-to-right
--FILE--
<?php
function main()
{
var_dump((print "sub-left\n") - (print "sub-right\n"));
var_dump((print "nested-left\n") * ((print "nested-right-left\n") + (print "nested-right-right\n")));
var_dump((print "eq-left\n") == (print "eq-right\n"));
var_dump((print "same-left\n") === (print "same-right\n"));
var_dump((print "spaceship-left\n") <=> (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)

@ -0,0 +1,23 @@
--TEST--
Native optimized calls evaluate arguments left-to-right
--FILE--
<?php
var_dump(str_repeat((print "repeat-left\n") ? "x" : "x", (print "repeat-right\n") + 1));
var_dump(round((print "round-left\n") + 1.25, (print "round-right\n")));
var_dump(strcmp((print "cmp-left\n") ? "a" : "a", (print "cmp-right\n") ? "b" : "b"));
$text = '<x>';
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) "<y>"

@ -0,0 +1,25 @@
--TEST--
type hits: instance property type check message includes class name
--FILE--
<?php
class TypeHitPropertyMessage
{
public int|string $union;
public function setInvalid(): void
{
try {
$this->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"

@ -0,0 +1,32 @@
--TEST--
type hits: property coalesce assignment uses runtime type check
--ENV--
USE_ZEND_ALLOC=0
--FILE--
<?php
class TypeHitCoalesceProperty
{
public int|string $union;
public function run(): void
{
try {
$this->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"

@ -0,0 +1,30 @@
--TEST--
AOT dynamic property assignment TypeError can be caught
--FILE--
<?php
class TypeHitDynamicProperty {
public int|string $union;
}
function assign_dynamic(TypeHitDynamicProperty $obj, string $prop, mixed $value): void {
try {
$obj->$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"
Loading…
Cancel
Save