fix: 修复属性默认值初始化及类型窄化问题

pull/24/head
Yurun 1 month ago
parent 559a8860a0
commit 46ff43ba62
  1. 9
      src/Preprocessor.php
  2. 45
      src/Translator.php
  3. 52
      tests/compiler/object_property/default-mixed-array.phpt
  4. 55
      tests/compiler/object_property/default-values.phpt

@ -801,9 +801,16 @@ class Preprocessor extends CompilerBase
$arrayInitPlan = null;
if ($defaultNode !== null) {
if ($defaultNode instanceof Node\Expr\Array_) {
$type = Type::ARRAY;
$arrayInitPlan = $this->buildLiteralArrayInitPlan($defaultNode);
$default = $arrayInitPlan->expr;
// Only narrow the property type to `array` when the declared type
// cannot already hold an array. `mixed`/`iterable`/union/nullable
// types are represented as php::Var and can legally store an array,
// so forcing `array` here would wrongly reject non-array assignments
// (e.g. `mixed $value = []` followed by `$this->value = 123`).
if ($type !== Type::VAR) {
$type = Type::ARRAY;
}
} else {
$default = $this->parseIdentifier($defaultNode);
}

@ -867,14 +867,22 @@ CODE;
$code .= '// static property ' . PHP_EOL;
foreach ($this->symbols->classes() as $classDef) {
foreach ($classDef->properties as $property) {
if (!$property->isStatic() || !$property->arrayInitPlan || !$property->default) {
if (!$property->isStatic() || $property->default === null) {
continue;
}
$statement = 'php::setStaticProperty('
. $this->genCharPtr($classDef->getNamespacedName(false), true) . ', '
. $this->genCharPtr($property->name) . ', '
. $property->arrayInitPlan->expr . ');' . PHP_EOL;
$code .= $this->wrapArrayInitPlan($property->arrayInitPlan, $statement);
if ($property->arrayInitPlan) {
$statement = 'php::setStaticProperty('
. $this->genCharPtr($classDef->getNamespacedName(false), true) . ', '
. $this->genCharPtr($property->name) . ', '
. $property->arrayInitPlan->expr . ');' . PHP_EOL;
$code .= $this->wrapArrayInitPlan($property->arrayInitPlan, $statement);
} else {
$statement = 'php::setStaticProperty('
. $this->genCharPtr($classDef->getNamespacedName(false), true) . ', '
. $this->genCharPtr($property->name) . ', '
. 'php::Var(' . $property->default . '));' . PHP_EOL;
$code .= $statement;
}
}
}
@ -1616,11 +1624,26 @@ CODE;
$body .= "typephp_attach_property_handlers(obj, &{$handlers});\n";
}
foreach ($classDef->properties as $property) {
if (!$property->isStatic() && $property->arrayInitPlan && $property->default) {
if ($property->isStatic() || $property->default === null) {
continue;
}
if ($property->arrayInitPlan) {
$init = "auto value = {$property->arrayInitPlan->expr};\n";
$init .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n";
$init .= "php::throwErrorIfOccurred();\n";
$body .= $this->wrapArrayInitPlan($property->arrayInitPlan, $init);
} else {
// Scalar / constant / null default value. Wrap it in a
// php::Var so it can be stored as a zval in the object's
// property table via zend_update_property. Each property is
// wrapped in its own block so the local `value` does not
// clash with siblings declared in the same create_object body.
$init = "do {\n";
$init .= "auto value = php::Var({$property->default});\n";
$init .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n";
$init .= "php::throwErrorIfOccurred();\n";
$init .= "} while (0);\n";
$body .= $init;
}
}
$body .= $classDef->ctorClean;
@ -2805,13 +2828,13 @@ CODE;
// 接口没有方法实体
if ($classDef instanceof ClassDef) {
$arrayPropCount = 0;
$defaultPropCount = 0;
foreach ($classDef->properties as $property) {
if ($property->type === Type::ARRAY && $property->arrayInitPlan && $property->default && !$property->isStatic()) {
$arrayPropCount++;
if (!$property->isStatic() && $property->default !== null) {
$defaultPropCount++;
}
}
if ($arrayPropCount > 0) {
if ($defaultPropCount > 0) {
$classDef->requireCtor = true;
}
$methods = $classDef->methods;

@ -0,0 +1,52 @@
--TEST--
property default value is array with mixed declared type
--FILE--
<?php
declare(strict_types=1);
class Test
{
private mixed $value = [];
public function __construct(mixed $value)
{
$this->value = $value;
}
public function getValue(): mixed
{
return $this->value;
}
}
function main()
{
$test = new Test(123);
var_dump($test->getValue());
$test = new Test('test');
var_dump($test->getValue());
$test = new Test([1, 2, 3]);
var_dump($test->getValue());
$test = new Test(new stdClass);
$v = $test->getValue();
var_dump($v instanceof stdClass);
var_dump(get_class($v));
}
?>
--EXPECT--
int(123)
string(4) "test"
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
bool(true)
string(8) "stdClass"

@ -0,0 +1,55 @@
--TEST--
various property default values (array, int, float, string, bool, null, const)
--FILE--
<?php
class Test
{
public $untypedArray = [];
public mixed $mixedArray = [];
public array $typedArray = [];
public $untypedInt = 123;
public int $typedInt = 123;
public float $typedFloat = 1.5;
public string $typedString = 'hello';
public bool $typedBool = true;
public $untypedNull = null;
public $untypedConst = PHP_INT_MAX;
public function show(): void
{
var_dump(
$this->untypedArray,
$this->mixedArray,
$this->typedArray,
$this->untypedInt,
$this->typedInt,
$this->typedFloat,
$this->typedString,
$this->typedBool,
$this->untypedNull,
$this->untypedConst
);
}
}
function main()
{
$t = new Test();
$t->show();
}
?>
--EXPECT--
array(0) {
}
array(0) {
}
array(0) {
}
int(123)
int(123)
float(1.5)
string(5) "hello"
bool(true)
NULL
int(9223372036854775807)
Loading…
Cancel
Save