diff --git a/phpunit/code/property-default-array-for-int.php b/phpunit/code/property-default-array-for-int.php new file mode 100644 index 00000000..6a5d56fe --- /dev/null +++ b/phpunit/code/property-default-array-for-int.php @@ -0,0 +1,16 @@ +a); + } +} + +function property_default_array_for_int(): void +{ + $test = new PropertyDefaultArrayForInt(); +} diff --git a/phpunit/code/property-default-array-for-object.php b/phpunit/code/property-default-array-for-object.php new file mode 100644 index 00000000..89d348b2 --- /dev/null +++ b/phpunit/code/property-default-array-for-object.php @@ -0,0 +1,15 @@ +i); +} diff --git a/phpunit/src/ClassTest.php b/phpunit/src/ClassTest.php index 49d62cc6..a997fcb7 100644 --- a/phpunit/src/ClassTest.php +++ b/phpunit/src/ClassTest.php @@ -194,4 +194,74 @@ class ClassTest extends \BaseTest // 作为函数/方法默认参数值应当能够在编译期正确解析。 $this->compile('class-const-default-value.php'); } + + public function testPropertyDefaultArrayForIntTypeFailsAtCompileTime() + { + $this->exec( + 'Cannot use array as default value for property PropertyDefaultArrayForInt::$a of type int', + 'property-default-array-for-int.php' + ); + } + + public function testPropertyDefaultStringForIntTypeFailsAtCompileTime() + { + $this->exec( + 'Cannot use string as default value for property PropertyDefaultStringForInt::$a of type int', + 'property-default-string-for-int.php' + ); + } + + public function testPropertyDefaultNullForNonNullableIntFailsAtCompileTime() + { + $this->exec( + 'Cannot use null as default value for property PropertyDefaultNullForInt::$a of type int', + 'property-default-null-for-int.php' + ); + } + + public function testPropertyDefaultArrayForObjectTypeFailsAtCompileTime() + { + $this->exec( + 'Cannot use array as default value for property PropertyDefaultArrayForObject::$dep of type PropertyDefaultArrayForObjectDep', + 'property-default-array-for-object.php' + ); + } + + public function testValidPropertyDefaultsCompile() + { + // 合法的默认值(含 int→float 协变、nullable、联合类型、mixed、常量)应通过检查。 + $this->compile('property-default-valid.php'); + } + + public function testTrueDefaultForFalsePropertyFailsAtCompileTime() + { + $this->exec( + 'Cannot use true as default value for property PropertyDefaultTrueForFalse::$value of type false', + 'property-default-true-for-false.php' + ); + } + + public function testFalseDefaultForTruePropertyFailsAtCompileTime() + { + $this->exec( + 'Cannot use false as default value for property PropertyDefaultFalseForTrue::$value of type true', + 'property-default-false-for-true.php' + ); + } + + public function testClassConstantPropertyDefaultTypeFailsAtCompileTime() + { + $this->exec( + 'Cannot use string as default value for property PropertyDefaultClassConstType::$value of type int', + 'property-default-class-const-type.php' + ); + } + + public function testExpressionPropertyDefaultTypeFailsAtCompileTime() + { + $this->exec( + 'Cannot use float as default value for property PropertyDefaultExpressionType::$value of type int', + 'property-default-expression-type.php' + ); + } } diff --git a/src/Preprocessor.php b/src/Preprocessor.php index b65cea84..612906d6 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -21,6 +21,7 @@ use TypePhp\Exception\SyntaxError; use TypePhp\Transform\PropertyHookLowering; use TypePhp\Transform\Visitor; use PhpParser\Modifiers; +use PhpParser\ConstExprEvaluator; use PhpParser\Node; use PhpParser\Node\IntersectionType; use PhpParser\Node\NullableType; @@ -620,12 +621,20 @@ class Preprocessor extends CompilerBase } $this->symbolDeclInFile[$fullClassNameLower] = $this->file; + // Property defaults may reference class constants declared later in the + // class body. Collect every constant first so default-value validation + // is independent of declaration order, matching PHP's class semantics. + foreach ($class->stmts as $stmt) { + if ($stmt instanceof Node\Stmt\ClassConst) { + $this->parseClassConstDef($stmt); + } + } + $code = ''; foreach ($class->stmts as $v) { $type = $v->getType(); switch ($type) { case 'Stmt_ClassConst': - $this->parseClassConstDef($v); break; case 'Stmt_Property': $this->parseClassPropertyDef($v); @@ -891,10 +900,18 @@ class Preprocessor extends CompilerBase $default = null; $arrayInitPlan = null; if ($defaultNode !== null) { + $this->checkPropertyDefaultType($name, $typeNode, $defaultNode, $errorNode); 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); } @@ -918,6 +935,205 @@ class Preprocessor extends CompilerBase return $propDef; } + /** + * Diagnose, during preprocessing, whether a property's default value is + * compatible with its declared type. + * + * TypePHP rejects obvious mismatches such as `int $a = []` at compile time + * instead of silently coercing the declared type or deferring to a runtime + * TypeError, matching the static-compilation principles in CLAUDE.md. + */ + protected function checkPropertyDefaultType(string $name, ?NodeAbstract $typeNode, NodeAbstract $defaultNode, NodeAbstract $errorNode): void + { + if ($typeNode === null) { + // Untyped property accepts any default value. + return; + } + + $valueType = $this->detectDefaultValueType($defaultNode); + if ($valueType === null) { + // The value type is not statically decidable (e.g. user or class + // constant references); leave it to later stages. + return; + } + + $allowed = $this->collectAllowedDefaultTypes($typeNode); + if ($allowed === null) { + // mixed / callable / otherwise unconstrained type declaration. + return; + } + + if (in_array($valueType, $allowed, true)) { + return; + } + + $className = $this->getFullClassName(); + $typeStr = $this->propertyTypeDeclToString($typeNode); + $this->fatalError( + $errorNode, + "Cannot use {$valueType} as default value for property {$className}::\${$name} of type {$typeStr}" + ); + } + + /** + * Determine the PHP value type of a constant expression used as a default + * value. Returns one of int/float/string/true/false/array/null, or null when + * the type cannot be decided statically. + */ + protected function detectDefaultValueType(NodeAbstract $node, ?string $scopeClass = null, int $depth = 0): ?string + { + if ($depth > 16) { + return null; + } + $scopeClass ??= $this->getFullClassName(); + + switch ($node->getType()) { + case 'Scalar_Int': + return 'int'; + case 'Scalar_Float': + return 'float'; + case 'Scalar_String': + case 'Scalar_InterpolatedString': + case 'Expr_BinaryOp_Concat': + return 'string'; + case 'Expr_Array': + return 'array'; + case 'Expr_UnaryMinus': + case 'Expr_UnaryPlus': + return $this->detectDefaultValueType($node->expr, $scopeClass, $depth + 1); + case 'Expr_ConstFetch': + return match (strtolower($node->name->toString())) { + 'true' => 'true', + 'false' => 'false', + 'null' => 'null', + default => null, + }; + case 'Expr_ClassConstFetch': + if (!$node->class instanceof Node\Name || !$node->name instanceof Node\Identifier) { + return null; + } + $constName = $node->name->toString(); + if (strcasecmp($constName, 'class') === 0) { + return 'string'; + } + $className = $node->class->toString(); + if (strcasecmp($className, 'self') === 0 || strcasecmp($className, 'static') === 0) { + $targetClass = $scopeClass; + } elseif (strcasecmp($className, 'parent') === 0) { + $targetClass = $this->getParentClass($scopeClass); + } else { + $targetClass = $this->getNamespacedClassName($className); + } + if ($targetClass === '' || !$this->hasClass($targetClass)) { + return null; + } + $targetDef = $this->getClass($targetClass); + if (!$targetDef->hasConstant($constName)) { + return null; + } + return $this->detectDefaultValueType( + $targetDef->getConstant($constName)->valueExpr, + $targetClass, + $depth + 1 + ); + default: + try { + $value = (new ConstExprEvaluator( + static function (Node\Expr $expr): never { + throw new \RuntimeException('Unresolved constant expression'); + } + ))->evaluateDirectly($node); + } catch (\Throwable) { + return null; + } + return match (true) { + is_int($value) => 'int', + is_float($value) => 'float', + is_string($value) => 'string', + $value === true => 'true', + $value === false => 'false', + is_array($value) => 'array', + $value === null => 'null', + default => null, + }; + } + } + + /** + * Collect the set of value types accepted as a default for a declared type + * node. Returns null when the type imposes no statically-checkable + * constraint (mixed / callable / unknown). + * + * @return array|null + */ + protected function collectAllowedDefaultTypes(NodeAbstract $typeNode): ?array + { + if ($typeNode instanceof NullableType) { + $inner = $this->collectAllowedDefaultTypes($typeNode->type); + if ($inner === null) { + return null; + } + return array_values(array_unique(array_merge($inner, ['null']))); + } + + if ($typeNode instanceof UnionType) { + $all = []; + foreach ($typeNode->types as $sub) { + $part = $this->collectAllowedDefaultTypes($sub); + if ($part === null) { + // A mixed-like member accepts any default value. + return null; + } + $all = array_merge($all, $part); + } + return array_values(array_unique($all)); + } + + if ($typeNode instanceof IntersectionType) { + // Intersection types are object-only; no scalar/array default valid. + return []; + } + + return match (strtolower($this->parseIdentifier($typeNode))) { + 'int' => ['int'], + 'float', 'double' => ['float', 'int'], // int coerces to float + 'string' => ['string'], + 'bool' => ['true', 'false'], + 'true' => ['true'], + 'false' => ['false'], + 'array' => ['array'], + 'iterable' => ['array'], + 'null' => ['null'], + 'object' => [], // no literal object default exists + 'self', 'parent', 'static' => [], + 'mixed' => null, + 'callable' => null, // string/array/closure — not checkable + default => [], // class type: only null via ?Type + }; + } + + protected function propertyTypeDeclToString(NodeAbstract $typeNode): string + { + if ($typeNode instanceof NullableType) { + return '?' . $this->propertyTypeDeclToString($typeNode->type); + } + if ($typeNode instanceof UnionType) { + $parts = []; + foreach ($typeNode->types as $t) { + $parts[] = $this->propertyTypeDeclToString($t); + } + return implode('|', $parts); + } + if ($typeNode instanceof IntersectionType) { + $parts = []; + foreach ($typeNode->types as $t) { + $parts[] = $this->propertyTypeDeclToString($t); + } + return implode('&', $parts); + } + return $this->parseIdentifier($typeNode); + } + protected function parseClassPropertyDef(Node\Stmt\Property $v): void { $oriCtx = $this->context; diff --git a/src/Translator.php b/src/Translator.php index 1f15eca3..3b1950de 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -866,15 +866,36 @@ CODE; $code .= '// static property ' . PHP_EOL; foreach ($this->symbols->classes() as $classDef) { + // Traits are never instantiated on their own; their static properties + // live on the classes that use them (where the members are flattened). + // Initialising a default on the trait itself would write to the trait's + // static property table and, on PHP >= 8.3, trigger a + // "Accessing static trait property" deprecation when the value is read + // through `self::` from a consuming class. Skip traits here; the + // consuming classes still initialise their own (flattened) copies. + if ($classDef->trait) { + continue; + } 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 { + $default = $property->type === Type::FLOAT + ? $this->convertFloatExpr($property->default) + : $property->default; + $statement = 'php::setStaticProperty(' + . $this->genCharPtr($classDef->getNamespacedName(false), true) . ', ' + . $this->genCharPtr($property->name) . ', ' + . 'php::Var(' . $default . '));' . PHP_EOL; + $code .= $statement; + } } } @@ -1609,19 +1630,63 @@ CODE; if ($classDef && !$classDef->trait && !$classDef->enum) { $className = $classDef->getNamespacedName(); $handlers = "property_handlers_{$className}"; - $buildCreateBody = function (bool $attachHandlers) use ($classDef, $className, $handlers): string { + $initBlock = ''; + foreach ($classDef->properties as $property) { + 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"; + $initBlock .= $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. + $default = $property->type === Type::FLOAT + ? $this->convertFloatExpr($property->default) + : $property->default; + $init = "do {\n"; + $init .= "auto value = php::Var({$default});\n"; + $init .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n"; + $init .= "php::throwErrorIfOccurred();\n"; + $init .= "} while (0);\n"; + $initBlock .= $init; + } + } + + $buildCreateBody = function (bool $attachHandlers) use ($classDef, $className, $handlers, $ce, $initBlock): string { $body = $classDef->ctorInit; - $body .= "auto obj = create_object_{$className}(class_type);\n"; if ($attachHandlers) { + // PHP < 8.4: the custom handlers are attached to the object + // AFTER the standard create_object, so object_properties_init + // runs with the standard handlers (no asymmetric check). Our + // explicit default inits run with the custom handlers + // attached, so we set EG(fake_scope) to the object's own class + // to satisfy asymmetric visibility for the class's own + // properties. + $body .= "auto obj = create_object_{$className}(class_type);\n"; $body .= "typephp_attach_property_handlers(obj, &{$handlers});\n"; - } - foreach ($classDef->properties as $property) { - if (!$property->isStatic() && $property->arrayInitPlan && $property->default) { - $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); - } + $body .= "zend_class_entry *__typephp_saved_fake_scope = EG(fake_scope);\n"; + $body .= "EG(fake_scope) = obj->ce;\n"; + $body .= $initBlock; + $body .= "EG(fake_scope) = __typephp_saved_fake_scope;\n"; + } else { + // PHP >= 8.4: the custom handlers live in + // default_object_handlers, so the object already carries the + // asymmetric write_property hook at creation time and + // object_properties_init would reject private(set)/protected(set) + // default values (including inherited ones). Create the object + // with the standard handlers, run the default initialization + // (no visibility check), then attach the custom handlers. + $body .= "auto obj = zend_objects_new(class_type);\n"; + $body .= "obj->handlers = const_cast(zend_get_std_object_handlers());\n"; + $body .= "object_properties_init(obj, class_type);\n"; + $body .= $initBlock; + $body .= "obj->handlers = &{$handlers};\n"; } $body .= $classDef->ctorClean; return $body . "return obj;\n"; @@ -1633,7 +1698,7 @@ CODE; $code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; $code .= $buildCreateBody(true); $code .= "};\n"; - if ($classDef->requireCtor) { + if ($classDef->requireCtor || $this->classHasAsymmetricOrHookedProperty($classDef)) { $code .= "#else\n"; $code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n"; $code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; @@ -1646,6 +1711,46 @@ CODE; return $code; } + /** + * Whether the given class (or any of its ancestors) declares an asymmetric + * visibility property (private(set)/protected(set)) or a hooked property + * (getter/setter). Such classes install a custom write_property handler, and + * on PHP >= 8.4 that handler lives in the class's default object handlers, so + * the engine's object_properties_init would reject inherited default values + * unless we generate our own create_object that initializes with the standard + * handlers first. + */ + private function classHasAsymmetricOrHookedProperty(ClassDef $classDef): bool + { + $current = $classDef; + $seen = []; + while ($current !== null) { + $key = strtolower(ltrim($current->getNamespacedName(), '\\')); + if (isset($seen[$key])) { + break; + } + $seen[$key] = true; + foreach ($current->properties as $property) { + if ($property->isPrivateSet() + || $property->isProtectedSet() + || $property->getter !== null + || $property->setter !== null + ) { + return true; + } + } + if (!$current->extends) { + break; + } + $parent = $this->getClassDef($current->extends); + if ($parent === null) { + break; + } + $current = $parent; + } + return false; + } + protected function getRegisterClassFunction(string $name): string { return self::PREFIX . 'register_class_' . $name; @@ -2884,13 +2989,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/src/gen_stub.php b/src/gen_stub.php index 752c6b9b..c19ee29a 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -2371,12 +2371,16 @@ class EvaluatedValue if ($class === 'self') { $constName = ClassInfo::$currentClass . "::" . $constName; if (isset($allConstInfos[$constName])) { - return formatConstValue($allConstInfos[$constName]->getValue($allConstInfos)->value); + return $allConstInfos[$constName]->getValue($allConstInfos)->value; } else { - return formatConstValue(getTranslator()->getClassConstValue($expr, ClassInfo::$currentClass, $constName)); + return normalizeConstExprValue( + getTranslator()->getClassConstValue($expr, ClassInfo::$currentClass, $constName) + ); } } else { - return formatConstValue(getTranslator()->getClassConstValue($expr, $class, $constName, ClassInfo::$currentClass)); + return normalizeConstExprValue( + getTranslator()->getClassConstValue($expr, $class, $constName, ClassInfo::$currentClass) + ); } } else { $constName = $expr->name->__toString(); @@ -2414,7 +2418,7 @@ class EvaluatedValue if (isset($definedConstants[$constName])) { $constValue = $definedConstants[$constName]; if (is_scalar($constValue)) { - return formatConstValue($constValue); + return $constValue; } } @@ -2508,11 +2512,15 @@ class EvaluatedValue // fully qualified class name (already stored in $this->value). return '"' . addcslashes($this->value, '\\') . '"'; } - return $this->value; + return '"' . getTranslator()->escapeString((string) $this->value) . '"'; } elseif ($this->expr instanceof Expr\ConstFetch) { return getTranslator()->getConstValue($this->expr->name->toString()); } elseif (!($this->expr instanceof String_)) { - throw new Exception("Expression at line " . $this->expr->getStartLine() . " must be a scalar string"); + // ConstExprEvaluator has already reduced concatenations and + // other constant string expressions to their PHP value. Emit + // that value as a C string literal instead of rejecting every + // non-literal string expression. + return '"' . getTranslator()->escapeString((string) $this->value) . '"'; } $expr = preg_replace("/(^'|'$)/", '"', getTranslator()->escapeString($expr)); } elseif ($this->type->isInt() or $this->type->isFloat()) { @@ -3306,6 +3314,11 @@ class PropertyInfo extends VariableLike $defaultValue = EvaluatedValue::null(); } else { $defaultValue = EvaluatedValue::createFromExpression($this->defaultValue, null, null, $allConstInfos); + if ($simpleType !== null && $simpleType->isFloat() && $defaultValue->type->isInt()) { + // PHP permits an integer default for a float property and + // stores it as a double in the class default table. + $defaultValue->type = $simpleType; + } if ($defaultValue->isUnknownConstValue || ($defaultValue->originatingConsts && $defaultValue->getCExpr() === null)) { echo "Skipping code generation for property $this->name, because it has an unknown constant default value\n"; return ""; @@ -6365,16 +6378,16 @@ function initPhpParser() { $isInitialized = true; } -function formatConstValue(mixed $constValue) +function normalizeConstExprValue(mixed $constValue): mixed { - if (is_string($constValue)) { - if (str_starts_with($constValue, '"') and str_ends_with($constValue, '"')) { - return $constValue; - } - return '"' . $constValue . '"'; - } else { - return $constValue; + if (is_string($constValue) + && strlen($constValue) >= 2 + && $constValue[0] === '"' + && $constValue[strlen($constValue) - 1] === '"' + ) { + return stripcslashes(substr($constValue, 1, -1)); } + return $constValue; } function getTranslator(): Translator diff --git a/tests/compiler/object_property/default-expressions-inheritance.phpt b/tests/compiler/object_property/default-expressions-inheritance.phpt new file mode 100644 index 00000000..fc517d24 --- /dev/null +++ b/tests/compiler/object_property/default-expressions-inheritance.phpt @@ -0,0 +1,53 @@ +--TEST-- +Property defaults support constant expressions, inheritance, and traits +--FILE-- +label; + } +} + +class PropertyDefaultsChild extends PropertyDefaultsParent +{ + use PropertyDefaultsTrait; +} + +function main(): void +{ + $value = new PropertyDefaultsChild(); + var_dump($value->sum, $value->ratio, $value->label(), $value->traitValue); + var_dump( + PropertyDefaultsChild::$counter, + PropertyDefaultsChild::$staticRatio, + PropertyDefaultsChild::$traitStatic + ); +} +?> +--EXPECT-- +int(3) +float(2) +string(12) "parent-value" +int(11) +int(5) +float(3) +string(12) "trait-static" 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)