diff --git a/src/Preprocessor.php b/src/Preprocessor.php index 0273d2cd..6cdd3707 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -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); } diff --git a/src/Translator.php b/src/Translator.php index 676618a3..75da8a1d 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -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; diff --git a/tests/compiler/object_property/default-mixed-array.phpt b/tests/compiler/object_property/default-mixed-array.phpt new file mode 100644 index 00000000..dbb4e883 --- /dev/null +++ b/tests/compiler/object_property/default-mixed-array.phpt @@ -0,0 +1,52 @@ +--TEST-- +property default value is array with mixed declared type +--FILE-- +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" diff --git a/tests/compiler/object_property/default-values.phpt b/tests/compiler/object_property/default-values.phpt new file mode 100644 index 00000000..a5adcb99 --- /dev/null +++ b/tests/compiler/object_property/default-values.phpt @@ -0,0 +1,55 @@ +--TEST-- +various property default values (array, int, float, string, bool, null, const) +--FILE-- +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)