diff --git a/UPGRADING b/UPGRADING index b55d018f30953..a22bc46328e90 100644 --- a/UPGRADING +++ b/UPGRADING @@ -52,6 +52,8 @@ PHP 8.3 UPGRADE NOTES . Anonymous classes may now be marked as readonly. . Readonly properties can now be reinitialized during cloning. RFC: https://wiki.php.net/rfc/readonly_amendments + . Class, interface, trait, and enum constants now support type + declarations. RFC: https://wiki.php.net/rfc/typed_class_constants - Posix . posix_getrlimit() now takes an optional $res parameter to allow fetching a diff --git a/Zend/tests/type_declarations/typed_class_constants_diamond_error1.phpt b/Zend/tests/type_declarations/typed_class_constants_diamond_error1.phpt new file mode 100644 index 0000000000000..059c3cb2005b7 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_diamond_error1.phpt @@ -0,0 +1,16 @@ +--TEST-- +Typed class constants (diamond error with self) +--FILE-- +getMessage() . "\n"; +} +?> +--EXPECT-- +Undefined constant "C" diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_error1.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_error1.phpt new file mode 100644 index 0000000000000..d4bbe98d6f5ce --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_error1.phpt @@ -0,0 +1,14 @@ +--TEST-- +Typed class constants (incompatible inheritance; simple) +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::CONST1 must be compatible with A::CONST1 of type int in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_error2.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_error2.phpt new file mode 100644 index 0000000000000..72fc4da0ffe2e --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_error2.phpt @@ -0,0 +1,14 @@ +--TEST-- +Typed class constants (incompatible inheritance; missing type in child) +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::CONST1 must be compatible with A::CONST1 of type int in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_error3.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_error3.phpt new file mode 100644 index 0000000000000..82c3154e9bfba --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_error3.phpt @@ -0,0 +1,18 @@ +--TEST-- +Typed class constants (incompatible composition; traits) +--FILE-- + +--EXPECTF-- +Fatal error: C and T define the same constant (CONST1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_error4.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_error4.phpt new file mode 100644 index 0000000000000..55929d8953119 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_error4.phpt @@ -0,0 +1,18 @@ +--TEST-- +Typed class constants (incompatible covariant composition; traits) +--FILE-- + +--EXPECTF-- +Fatal error: C and T define the same constant (CONST1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_error5.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_error5.phpt new file mode 100644 index 0000000000000..7068d9df7cbb7 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_error5.phpt @@ -0,0 +1,18 @@ +--TEST-- +Typed class constants (incompatible contravariant composition; traits) +--FILE-- + +--EXPECTF-- +Fatal error: C and T define the same constant (CONST1) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_success1.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_success1.phpt new file mode 100644 index 0000000000000..0dbd3a3385bc8 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_success1.phpt @@ -0,0 +1,29 @@ +--TEST-- +Typed class constants (inheritance success) +--FILE-- + +--EXPECT-- +int(0) +int(0) +int(0) +array(0) { +} diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_success2.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_success2.phpt new file mode 100644 index 0000000000000..40e079c3596e7 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_success2.phpt @@ -0,0 +1,48 @@ +--TEST-- +Typed class constants (inheritance success - object types) +--FILE-- + +--EXPECTF-- +object(Z)#%d (%d) { +} +object(Z)#%d (%d) { +} +object(Z)#%d (%d) { +} +object(Z)#%d (%d) { +} +object(Z)#%d (%d) { +} \ No newline at end of file diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_success3.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_success3.phpt new file mode 100644 index 0000000000000..dfcc5742d2b43 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_success3.phpt @@ -0,0 +1,16 @@ +--TEST-- +Typed class constants (inheritance; private constants) +--FILE-- + +--EXPECT-- +string(1) "a" diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_success4.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_success4.phpt new file mode 100644 index 0000000000000..faacb0d55e49c --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_success4.phpt @@ -0,0 +1,39 @@ +--TEST-- +Typed class constants (composition; traits) +--FILE-- + +--EXPECT-- +int(1) +array(0) { +} +enum(E::Case1) +object(stdClass)#1 (0) { +} diff --git a/Zend/tests/type_declarations/typed_class_constants_inheritance_success5.phpt b/Zend/tests/type_declarations/typed_class_constants_inheritance_success5.phpt new file mode 100644 index 0000000000000..7f59dc1be35aa --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_inheritance_success5.phpt @@ -0,0 +1,26 @@ +--TEST-- +Typed class constants (redefinition; interfaces and traits) +--FILE-- + +--EXPECT-- +enum(E::Case1) diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error1.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error1.phpt new file mode 100644 index 0000000000000..8f70043091fda --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error1.phpt @@ -0,0 +1,10 @@ +--TEST-- +Typed class constants (type mismatch; simple) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use int as value for class constant A::CONST1 of type string in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error10.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error10.phpt new file mode 100644 index 0000000000000..805edd9854f43 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error10.phpt @@ -0,0 +1,15 @@ +--TEST-- +Typed class constants (type error) +--FILE-- + +--EXPECTF-- +Fatal error: Type of A2::C must be compatible with A1::C of type ?B1 in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error11.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error11.phpt new file mode 100644 index 0000000000000..714e5a00995cf --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error11.phpt @@ -0,0 +1,21 @@ +--TEST-- +Typed class constants (static type error) +--FILE-- +getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot assign E2 to class constant E1::C of type static diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error12.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error12.phpt new file mode 100644 index 0000000000000..fddc1bca0bae9 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error12.phpt @@ -0,0 +1,15 @@ +--TEST-- +Typed class constants with static in union +--FILE-- + +--EXPECT-- +object(A)#2 (0) { +} diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error2.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error2.phpt new file mode 100644 index 0000000000000..8acffaa38399f --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error2.phpt @@ -0,0 +1,26 @@ +--TEST-- +Typed class constants (type mismatch; runtime simple) +--FILE-- +getMessage() . "\n"; +} + +try { + var_dump(A::CONST1); +} catch (TypeError $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot assign string to class constant A::CONST1 of type int +Cannot assign string to class constant A::CONST1 of type int diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error3.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error3.phpt new file mode 100644 index 0000000000000..1e6ab277dec8b --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error3.phpt @@ -0,0 +1,25 @@ +--TEST-- +Typed class constants (type mismatch; runtime object) +--FILE-- +getMessage() . "\n"; +} + +try { + var_dump(A::CONST1); +} catch (TypeError $exception) { + echo $exception->getMessage() . "\n"; +} +?> +--EXPECT-- +Cannot assign stdClass to class constant A::CONST1 of type string +Cannot assign stdClass to class constant A::CONST1 of type string diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error4.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error4.phpt new file mode 100644 index 0000000000000..7e41b602cf8b3 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error4.phpt @@ -0,0 +1,10 @@ +--TEST-- +Typed class constants (type not allowed; callable) +--FILE-- + +--EXPECTF-- +Fatal error: Class constant A::CONST1 cannot have type callable in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error5.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error5.phpt new file mode 100644 index 0000000000000..c083c738989cc --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error5.phpt @@ -0,0 +1,10 @@ +--TEST-- +Typed class constants (type not allowed; void) +--FILE-- + +--EXPECTF-- +Fatal error: Class constant A::CONST1 cannot have type void in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error6.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error6.phpt new file mode 100644 index 0000000000000..cbb96588f5cc3 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error6.phpt @@ -0,0 +1,10 @@ +--TEST-- +Typed class constants (type not allowed; never) +--FILE-- + +--EXPECTF-- +Fatal error: Class constant A::CONST1 cannot have type never in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error7.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error7.phpt new file mode 100644 index 0000000000000..6fce750166305 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error7.phpt @@ -0,0 +1,25 @@ +--TEST-- +Typed class constants (type mismatch; runtime) +--FILE-- +getMessage() . "\n"; +} + +try { + var_dump(A::CONST1); +} catch (TypeError $exception) { + echo $exception->getMessage() . "\n"; +} +?> +--EXPECT-- +Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable +Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error8.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error8.phpt new file mode 100644 index 0000000000000..cbd3720a5ea4e --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error8.phpt @@ -0,0 +1,40 @@ +--TEST-- +Typed class constants (type mismatch; runtime) +--FILE-- +getMessage() . "\n"; +} + +try { + var_dump(A::CONST2); +} catch (TypeError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + var_dump(A::CONST1); +} catch (TypeError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + var_dump(A::CONST1); +} catch (TypeError $exception) { + echo $exception->getMessage() . "\n"; +} +?> +--EXPECT-- +Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable +Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable +Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable +Cannot assign stdClass to class constant A::CONST1 of type stdClass&Stringable diff --git a/Zend/tests/type_declarations/typed_class_constants_type_error9.phpt b/Zend/tests/type_declarations/typed_class_constants_type_error9.phpt new file mode 100644 index 0000000000000..b0f42b2078a5b --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_error9.phpt @@ -0,0 +1,25 @@ +--TEST-- +Typed class constants (type coercion is unsupported) +--FILE-- +getMessage() . "\n"; +} +?> +--EXPECT-- +Cannot assign S to class constant A::S of type string diff --git a/Zend/tests/type_declarations/typed_class_constants_type_success1.phpt b/Zend/tests/type_declarations/typed_class_constants_type_success1.phpt new file mode 100644 index 0000000000000..61e82637d9ada --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_success1.phpt @@ -0,0 +1,66 @@ +--TEST-- +Typed class constants (declaration; compile-type simple) +--FILE-- + +--EXPECT-- +NULL +NULL +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +int(0) +int(0) +float(3.14) +float(3.14) +float(3) +float(3) +string(0) "" +string(0) "" +array(0) { +} +array(0) { +} +string(0) "" +string(0) "" +NULL +NULL diff --git a/Zend/tests/type_declarations/typed_class_constants_type_success2.phpt b/Zend/tests/type_declarations/typed_class_constants_type_success2.phpt new file mode 100644 index 0000000000000..1359624452355 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_success2.phpt @@ -0,0 +1,52 @@ +--TEST-- +Typed class constants (declaration; runtime) +--FILE-- + +--EXPECTF-- +object(B)#%d (%d) { +} +object(B)#%d (%d) { +} +object(B)#%d (%d) { +} +object(B)#%d (%d) { +} +object(B)#%d (%d) { +} +object(B)#%d (%d) { +} +object(B)#%d (%d) { +} +object(B)#%d (%d) { +} +object(B)#%d (%d) { +} +object(B)#%d (%d) { +} diff --git a/Zend/tests/type_declarations/typed_class_constants_type_success3.phpt b/Zend/tests/type_declarations/typed_class_constants_type_success3.phpt new file mode 100644 index 0000000000000..cf3d7c9557202 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_type_success3.phpt @@ -0,0 +1,52 @@ +--TEST-- +Typed enum constants (self/static) +--FILE-- + +--EXPECT-- +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) +enum(E::Foo) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 1beb76dff4dc0..e9058f3e43db9 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1412,6 +1412,34 @@ static zend_result update_property(zval *val, zend_property_info *prop_info) { return zval_update_constant_ex(val, prop_info->ce); } +ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const zend_string *name, zend_class_entry *scope) +{ + ZEND_ASSERT(Z_TYPE(c->value) == IS_CONSTANT_AST); + + if (EXPECTED(!ZEND_TYPE_IS_SET(c->type) || ZEND_TYPE_PURE_MASK(c->type) == MAY_BE_ANY)) { + return zval_update_constant_ex(&c->value, scope); + } + + zval tmp; + + ZVAL_COPY(&tmp, &c->value); + zend_result result = zval_update_constant_ex(&tmp, scope); + if (result == FAILURE) { + zval_ptr_dtor(&tmp); + return FAILURE; + } + + if (UNEXPECTED(!zend_verify_class_constant_type(c, name, &tmp))) { + zval_ptr_dtor(&tmp); + return FAILURE; + } + + zval_ptr_dtor(&c->value); + ZVAL_COPY_VALUE(&c->value, &tmp); + + return SUCCESS; +} + ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /* {{{ */ { zend_class_mutable_data *mutable_data = NULL; @@ -1470,7 +1498,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) / } val = &c->value; - if (UNEXPECTED(zval_update_constant_ex(val, c->ce) != SUCCESS)) { + if (UNEXPECTED(zend_update_class_constant(c, name, c->ce) != SUCCESS)) { return FAILURE; } } @@ -4532,7 +4560,7 @@ ZEND_API void zend_declare_property_stringl(zend_class_entry *ce, const char *na } /* }}} */ -ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int flags, zend_string *doc_comment) /* {{{ */ +ZEND_API zend_class_constant *zend_declare_typed_class_constant(zend_class_entry *ce, zend_string *name, zval *value, int flags, zend_string *doc_comment, zend_type type) /* {{{ */ { zend_class_constant *c; @@ -4561,6 +4589,8 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c c->doc_comment = doc_comment; c->attributes = NULL; c->ce = ce; + c->type = type; + if (Z_TYPE_P(value) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; @@ -4576,7 +4606,11 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c return c; } -/* }}} */ + +ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int flags, zend_string *doc_comment) +{ + return zend_declare_typed_class_constant(ce, name, value, flags, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0)); +} ZEND_API void zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value) /* {{{ */ { diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 92daf6570aea6..9c1cf08aa78b3 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -429,6 +429,7 @@ ZEND_API void zend_declare_property_double(zend_class_entry *ce, const char *nam ZEND_API void zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type); ZEND_API void zend_declare_property_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_len, int access_type); +ZEND_API zend_class_constant *zend_declare_typed_class_constant(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment, zend_type type); ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment); ZEND_API void zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value); ZEND_API void zend_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length); @@ -438,6 +439,7 @@ ZEND_API void zend_declare_class_constant_double(zend_class_entry *ce, const cha ZEND_API void zend_declare_class_constant_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_length); ZEND_API void zend_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value); +ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const zend_string *name, zend_class_entry *scope); ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type); ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index ca12afd6b2149..73e4fed7a997a 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -161,7 +161,6 @@ enum _zend_ast_kind { ZEND_AST_CATCH, ZEND_AST_PROP_GROUP, ZEND_AST_PROP_ELEM, - ZEND_AST_CONST_ELEM, // Pseudo node for initializing enums ZEND_AST_CONST_ENUM_INIT, @@ -170,6 +169,7 @@ enum _zend_ast_kind { ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, ZEND_AST_FOREACH, ZEND_AST_ENUM_CASE, + ZEND_AST_CONST_ELEM, /* 5 child nodes */ ZEND_AST_PARAM = 5 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 6aa5bd15a990b..5b8aa00d4df51 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7693,7 +7693,7 @@ static void zend_check_trait_alias_modifiers(uint32_t attr) /* {{{ */ } /* }}} */ -static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */ +static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr_ast) { zend_ast_list *list = zend_ast_get_list(ast); zend_class_entry *ce = CG(active_class_entry); @@ -7705,9 +7705,24 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as zend_ast *name_ast = const_ast->child[0]; zend_ast **value_ast_ptr = &const_ast->child[1]; zend_ast *doc_comment_ast = const_ast->child[2]; + zend_ast *type_ast = const_ast->child[3]; zend_string *name = zval_make_interned_string(zend_ast_get_zval(name_ast)); zend_string *doc_comment = doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL; zval value_zv; + zend_type type = ZEND_TYPE_INIT_NONE(0); + + if (type_ast) { + type = zend_compile_typename(type_ast, /* force_allow_null */ 0); + + uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); + + if (type_mask != MAY_BE_ANY && (type_mask & (MAY_BE_CALLABLE|MAY_BE_VOID|MAY_BE_NEVER))) { + zend_string *type_str = zend_type_to_string(type); + + zend_error_noreturn(E_COMPILE_ERROR, "Class constant %s::%s cannot have type %s", + ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(type_str)); + } + } if (UNEXPECTED((flags & ZEND_ACC_PRIVATE) && (flags & ZEND_ACC_FINAL))) { zend_error_noreturn( @@ -7717,14 +7732,21 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as } zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false); - c = zend_declare_class_constant_ex(ce, name, &value_zv, flags, doc_comment); + + if (!Z_CONSTANT(value_zv) && ZEND_TYPE_IS_SET(type) && !zend_is_valid_default_value(type, &value_zv)) { + zend_string *type_str = zend_type_to_string(type); + + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use %s as value for class constant %s::%s of type %s", + zend_zval_type_name(&value_zv), ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(type_str)); + } + + c = zend_declare_typed_class_constant(ce, name, &value_zv, flags, doc_comment, type); if (attr_ast) { zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0); } } } -/* }}} */ static void zend_compile_class_const_group(zend_ast *ast) /* {{{ */ { diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 9a2129d908d6c..0ef351ab274a6 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -407,6 +407,7 @@ typedef struct _zend_class_constant { zend_string *doc_comment; HashTable *attributes; zend_class_entry *ce; + zend_type type; } zend_class_constant; #define ZEND_CLASS_CONST_FLAGS(c) Z_CONSTANT_FLAGS((c)->value) diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c index f4ed521605968..854f9c2116ee2 100644 --- a/Zend/zend_constants.c +++ b/Zend/zend_constants.c @@ -361,7 +361,7 @@ ZEND_API zval *zend_get_class_constant_ex(zend_string *class_name, zend_string * } MARK_CONSTANT_VISITED(ret_constant); - ret = zval_update_constant_ex(ret_constant, c->ce); + ret = zend_update_class_constant(c, constant_name, c->ce); RESET_CONSTANT_VISITED(ret_constant); if (UNEXPECTED(ret != SUCCESS)) { diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 59ae2720f92cc..42c9fcdc0fc1c 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -820,6 +820,16 @@ ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool s return zend_verify_weak_scalar_type_hint(type_mask, arg); } +ZEND_COLD zend_never_inline void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant) +{ + zend_string *type_str = zend_type_to_string(c->type); + + zend_type_error("Cannot assign %s to class constant %s::%s of type %s", + zend_zval_type_name(constant), ZSTR_VAL(c->ce->name), ZSTR_VAL(name), ZSTR_VAL(type_str)); + + zend_string_release(type_str); +} + ZEND_COLD zend_never_inline void zend_verify_property_type_error(const zend_property_info *info, const zval *property) { zend_string *type_str; @@ -908,7 +918,7 @@ static const zend_class_entry *resolve_single_class_type(zend_string *name, cons } static zend_always_inline const zend_class_entry *zend_ce_from_type( - const zend_property_info *info, const zend_type *type) { + const zend_class_entry *scope, const zend_type *type) { ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*type)); zend_string *name = ZEND_TYPE_NAME(*type); if (ZSTR_HAS_CE_CACHE(name)) { @@ -918,52 +928,61 @@ static zend_always_inline const zend_class_entry *zend_ce_from_type( } return ce; } - return resolve_single_class_type(name, info->ce); + return resolve_single_class_type(name, scope); } -static bool zend_check_intersection_for_property_class_type(zend_type_list *intersection_type_list, - const zend_property_info *info, const zend_class_entry *object_ce) +static bool zend_check_intersection_for_property_or_class_constant_class_type( + const zend_class_entry *scope, zend_type_list *intersection_type_list, const zend_class_entry *value_ce) { zend_type *list_type; ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) { ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); - const zend_class_entry *ce = zend_ce_from_type(info, list_type); - if (!ce || !instanceof_function(object_ce, ce)) { + const zend_class_entry *ce = zend_ce_from_type(scope, list_type); + if (!ce || !instanceof_function(value_ce, ce)) { return false; } } ZEND_TYPE_LIST_FOREACH_END(); return true; } -static bool zend_check_and_resolve_property_class_type( - const zend_property_info *info, const zend_class_entry *object_ce) { - if (ZEND_TYPE_HAS_LIST(info->type)) { +static bool zend_check_and_resolve_property_or_class_constant_class_type( + const zend_class_entry *scope, zend_type member_type, const zend_class_entry *value_ce) { + if (ZEND_TYPE_HAS_LIST(member_type)) { zend_type *list_type; - if (ZEND_TYPE_IS_INTERSECTION(info->type)) { - return zend_check_intersection_for_property_class_type( - ZEND_TYPE_LIST(info->type), info, object_ce); + if (ZEND_TYPE_IS_INTERSECTION(member_type)) { + return zend_check_intersection_for_property_or_class_constant_class_type( + scope, ZEND_TYPE_LIST(member_type), value_ce); } else { - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(member_type), list_type) { if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { - if (zend_check_intersection_for_property_class_type( - ZEND_TYPE_LIST(*list_type), info, object_ce)) { + if (zend_check_intersection_for_property_or_class_constant_class_type( + scope, ZEND_TYPE_LIST(*list_type), value_ce)) { return true; } continue; } ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); - const zend_class_entry *ce = zend_ce_from_type(info, list_type); - if (ce && instanceof_function(object_ce, ce)) { + const zend_class_entry *ce = zend_ce_from_type(scope, list_type); + if (ce && instanceof_function(value_ce, ce)) { return true; } } ZEND_TYPE_LIST_FOREACH_END(); + + if ((ZEND_TYPE_PURE_MASK(member_type) & MAY_BE_STATIC)) { + return value_ce == scope; + } + return false; } - } else { - const zend_class_entry *ce = zend_ce_from_type(info, &info->type); - return ce && instanceof_function(object_ce, ce); + } else if ((ZEND_TYPE_PURE_MASK(member_type) & MAY_BE_STATIC) && value_ce == scope) { + return true; + } else if (ZEND_TYPE_HAS_NAME(member_type)) { + const zend_class_entry *ce = zend_ce_from_type(scope, &member_type); + return ce && instanceof_function(value_ce, ce); } + + return false; } static zend_always_inline bool i_zend_check_property_type(const zend_property_info *info, zval *property, bool strict) @@ -974,7 +993,7 @@ static zend_always_inline bool i_zend_check_property_type(const zend_property_in } if (ZEND_TYPE_IS_COMPLEX(info->type) && Z_TYPE_P(property) == IS_OBJECT - && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property))) { + && zend_check_and_resolve_property_or_class_constant_class_type(info->ce, info->type, Z_OBJCE_P(property))) { return 1; } @@ -1455,6 +1474,33 @@ static ZEND_COLD void zend_verify_missing_return_type(const zend_function *zf) zend_verify_return_error(zf, NULL); } +static zend_always_inline bool zend_check_class_constant_type(zend_class_constant *c, zval *constant) +{ + ZEND_ASSERT(!Z_ISREF_P(constant)); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(c->type, Z_TYPE_P(constant)))) { + return 1; + } + + if (((ZEND_TYPE_PURE_MASK(c->type) & MAY_BE_STATIC) || ZEND_TYPE_IS_COMPLEX(c->type)) && Z_TYPE_P(constant) == IS_OBJECT + && zend_check_and_resolve_property_or_class_constant_class_type(c->ce, c->type, Z_OBJCE_P(constant))) { + return 1; + } + + uint32_t type_mask = ZEND_TYPE_FULL_MASK(c->type); + ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_NEVER|MAY_BE_VOID))); + return zend_verify_scalar_type_hint(type_mask, constant, true, false); +} + +ZEND_API bool zend_never_inline zend_verify_class_constant_type(zend_class_constant *c, const zend_string *name, zval *constant) +{ + if (!zend_check_class_constant_type(c, constant)) { + zend_verify_class_constant_type_error(c, name, constant); + return 0; + } + + return 1; +} + static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_use_object_as_array(void) { zend_throw_error(NULL, "Cannot use object as array"); @@ -3473,7 +3519,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( } if (ZEND_TYPE_IS_COMPLEX(type) && zv_type == IS_OBJECT - && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv))) { + && zend_check_and_resolve_property_or_class_constant_class_type(info->ce, info->type, Z_OBJCE_P(zv))) { return 1; } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index caeaa2a3b9f5f..594561da1bb42 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -484,6 +484,10 @@ ZEND_API int ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *call); #define ZEND_CLASS_HAS_TYPE_HINTS(ce) ((ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) == ZEND_ACC_HAS_TYPE_HINTS) #define ZEND_CLASS_HAS_READONLY_PROPS(ce) ((ce->ce_flags & ZEND_ACC_HAS_READONLY_PROPS) == ZEND_ACC_HAS_READONLY_PROPS) + +ZEND_API bool zend_verify_class_constant_type(zend_class_constant *c, const zend_string *name, zval *constant); +ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant); + ZEND_API bool zend_verify_property_type(const zend_property_info *info, zval *property, bool strict); ZEND_COLD void zend_verify_property_type_error(const zend_property_info *info, const zval *property); ZEND_COLD void zend_magic_get_property_type_inconsistency_error(const zend_property_info *info, const zval *property); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 5a689408263ab..ff3a4d7080751 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -51,6 +51,9 @@ static void add_compatibility_obligation( static void add_property_compatibility_obligation( zend_class_entry *ce, const zend_property_info *child_prop, const zend_property_info *parent_prop); +static void add_class_constant_compatibility_obligation( + zend_class_entry *ce, const zend_class_constant *child_const, + const zend_class_constant *parent_const, const zend_string *const_name); static void ZEND_COLD emit_incompatible_method_error( const zend_function *child, zend_class_entry *child_scope, @@ -1359,6 +1362,29 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en } /* }}} */ +static void emit_incompatible_class_constant_error( + const zend_class_constant *child, const zend_class_constant *parent, const zend_string *const_name) { + zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce); + zend_error_noreturn(E_COMPILE_ERROR, + "Type of %s::%s must be compatible with %s::%s of type %s", + ZSTR_VAL(child->ce->name), + ZSTR_VAL(const_name), + ZSTR_VAL(parent->ce->name), + ZSTR_VAL(const_name), + ZSTR_VAL(type_str)); +} + +static inheritance_status class_constant_types_compatible(const zend_class_constant *parent, const zend_class_constant *child) +{ + ZEND_ASSERT(ZEND_TYPE_IS_SET(parent->type)); + + if (!ZEND_TYPE_IS_SET(child->type)) { + return INHERITANCE_ERROR; + } + + return zend_perform_covariant_type_check(child->ce, child->type, parent->ce, parent->type); +} + static void do_inherit_class_constant(zend_string *name, zend_class_constant *parent_const, zend_class_entry *ce) /* {{{ */ { zval *zv = zend_hash_find_known_hash(&ce->constants_table, name); @@ -1366,9 +1392,14 @@ static void do_inherit_class_constant(zend_string *name, zend_class_constant *pa if (zv != NULL) { c = (zend_class_constant*)Z_PTR_P(zv); + if (UNEXPECTED((ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_PPP_MASK) > (ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PPP_MASK))) { zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::%s must be %s (as in class %s)%s", - ZSTR_VAL(ce->name), ZSTR_VAL(name), zend_visibility_string(ZEND_CLASS_CONST_FLAGS(parent_const)), ZSTR_VAL(parent_const->ce->name), (ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PUBLIC) ? "" : " or weaker"); + ZSTR_VAL(ce->name), ZSTR_VAL(name), + zend_visibility_string(ZEND_CLASS_CONST_FLAGS(parent_const)), + ZSTR_VAL(parent_const->ce->name), + (ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PUBLIC) ? "" : " or weaker" + ); } if (UNEXPECTED((ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_FINAL))) { @@ -1377,6 +1408,15 @@ static void do_inherit_class_constant(zend_string *name, zend_class_constant *pa ZSTR_VAL(ce->name), ZSTR_VAL(name), ZSTR_VAL(parent_const->ce->name), ZSTR_VAL(name) ); } + + if (!(ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PRIVATE) && UNEXPECTED(ZEND_TYPE_IS_SET(parent_const->type))) { + inheritance_status status = class_constant_types_compatible(parent_const, c); + if (status == INHERITANCE_ERROR) { + emit_incompatible_class_constant_error(c, parent_const, name); + } else if (status == INHERITANCE_UNRESOLVED) { + add_class_constant_compatibility_obligation(ce, c, parent_const, name); + } + } } else if (!(ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PRIVATE)) { if (Z_TYPE(parent_const->value) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; @@ -1641,12 +1681,18 @@ static zend_always_inline bool check_trait_property_or_constant_value_compatibil /* if any of the values is a constant, we try to resolve it */ if (UNEXPECTED(Z_TYPE_P(op1) == IS_CONSTANT_AST)) { ZVAL_COPY_OR_DUP(&op1_tmp, op1); - zval_update_constant_ex(&op1_tmp, ce); + if (UNEXPECTED(zval_update_constant_ex(&op1_tmp, ce) != SUCCESS)) { + zval_ptr_dtor(&op1_tmp); + return false; + } op1 = &op1_tmp; } if (UNEXPECTED(Z_TYPE_P(op2) == IS_CONSTANT_AST)) { ZVAL_COPY_OR_DUP(&op2_tmp, op2); - zval_update_constant_ex(&op2_tmp, ce); + if (UNEXPECTED(zval_update_constant_ex(&op2_tmp, ce) != SUCCESS)) { + zval_ptr_dtor(&op2_tmp); + return false; + } op2 = &op2_tmp; } @@ -2231,8 +2277,22 @@ static zend_class_entry* find_first_constant_definition(zend_class_entry *ce, ze } /* }}} */ -static bool do_trait_constant_check(zend_class_entry *ce, zend_class_constant *trait_constant, zend_string *name, zend_class_entry **traits, size_t current_trait) /* {{{ */ -{ +static void emit_incompatible_trait_constant_error( + zend_class_entry *ce, zend_class_constant *existing_constant, zend_class_constant *trait_constant, zend_string *name, + zend_class_entry **traits, size_t current_trait +) { + zend_error_noreturn(E_COMPILE_ERROR, + "%s and %s define the same constant (%s) in the composition of %s. However, the definition differs and is considered incompatible. Class was composed", + ZSTR_VAL(find_first_constant_definition(ce, traits, current_trait, name, existing_constant->ce)->name), + ZSTR_VAL(trait_constant->ce->name), + ZSTR_VAL(name), + ZSTR_VAL(ce->name) + ); +} + +static bool do_trait_constant_check( + zend_class_entry *ce, zend_class_constant *trait_constant, zend_string *name, zend_class_entry **traits, size_t current_trait +) { uint32_t flags_mask = ZEND_ACC_PPP_MASK | ZEND_ACC_FINAL; zval *zv = zend_hash_find_known_hash(&ce->constants_table, name); @@ -2243,21 +2303,32 @@ static bool do_trait_constant_check(zend_class_entry *ce, zend_class_constant *t zend_class_constant *existing_constant = Z_PTR_P(zv); - if ((ZEND_CLASS_CONST_FLAGS(trait_constant) & flags_mask) != (ZEND_CLASS_CONST_FLAGS(existing_constant) & flags_mask) || - !check_trait_property_or_constant_value_compatibility(ce, &trait_constant->value, &existing_constant->value)) { + if ((ZEND_CLASS_CONST_FLAGS(trait_constant) & flags_mask) != (ZEND_CLASS_CONST_FLAGS(existing_constant) & flags_mask)) { + emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait); + return false; + } + + if (ZEND_TYPE_IS_SET(trait_constant->type) != ZEND_TYPE_IS_SET(existing_constant->type)) { + emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait); + return false; + } else if (ZEND_TYPE_IS_SET(trait_constant->type)) { + inheritance_status status1 = zend_perform_covariant_type_check(ce, existing_constant->type, traits[current_trait], trait_constant->type); + inheritance_status status2 = zend_perform_covariant_type_check(traits[current_trait], trait_constant->type, ce, existing_constant->type); + if (status1 == INHERITANCE_ERROR || status2 == INHERITANCE_ERROR) { + emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait); + return false; + } + } + + if (!check_trait_property_or_constant_value_compatibility(ce, &trait_constant->value, &existing_constant->value)) { /* There is an existing constant of the same name, and it conflicts with the new one, so let's throw a fatal error */ - zend_error_noreturn(E_COMPILE_ERROR, - "%s and %s define the same constant (%s) in the composition of %s. However, the definition differs and is considered incompatible. Class was composed", - ZSTR_VAL(find_first_constant_definition(ce, traits, current_trait, name, existing_constant->ce)->name), - ZSTR_VAL(trait_constant->ce->name), - ZSTR_VAL(name), - ZSTR_VAL(ce->name)); + emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait); + return false; } /* There is an existing constant which is compatible with the new one, so no need to add it */ return false; } -/* }}} */ static void zend_do_traits_constant_binding(zend_class_entry *ce, zend_class_entry **traits) /* {{{ */ { @@ -2508,7 +2579,8 @@ typedef struct { enum { OBLIGATION_DEPENDENCY, OBLIGATION_COMPATIBILITY, - OBLIGATION_PROPERTY_COMPATIBILITY + OBLIGATION_PROPERTY_COMPATIBILITY, + OBLIGATION_CLASS_CONSTANT_COMPATIBILITY } type; union { zend_class_entry *dependency_ce; @@ -2524,6 +2596,11 @@ typedef struct { const zend_property_info *parent_prop; const zend_property_info *child_prop; }; + struct { + const zend_string *const_name; + const zend_class_constant *parent_const; + const zend_class_constant *child_const; + }; }; } variance_obligation; @@ -2599,6 +2676,18 @@ static void add_property_compatibility_obligation( zend_hash_next_index_insert_ptr(obligations, obligation); } +static void add_class_constant_compatibility_obligation( + zend_class_entry *ce, const zend_class_constant *child_const, + const zend_class_constant *parent_const, const zend_string *const_name) { + HashTable *obligations = get_or_init_obligations_for_class(ce); + variance_obligation *obligation = emalloc(sizeof(variance_obligation)); + obligation->type = OBLIGATION_CLASS_CONSTANT_COMPATIBILITY; + obligation->const_name = const_name; + obligation->child_const = child_const; + obligation->parent_const = parent_const; + zend_hash_next_index_insert_ptr(obligations, obligation); +} + static void resolve_delayed_variance_obligations(zend_class_entry *ce); static void check_variance_obligation(variance_obligation *obligation) { @@ -2622,13 +2711,19 @@ static void check_variance_obligation(variance_obligation *obligation) { &obligation->parent_fn, obligation->parent_scope, status); } /* Either the compatibility check was successful or only threw a warning. */ - } else { - ZEND_ASSERT(obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY); + } else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) { inheritance_status status = property_types_compatible(obligation->parent_prop, obligation->child_prop); if (status != INHERITANCE_SUCCESS) { emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop); } + } else { + ZEND_ASSERT(obligation->type == OBLIGATION_CLASS_CONSTANT_COMPATIBILITY); + inheritance_status status = + class_constant_types_compatible(obligation->parent_const, obligation->child_const); + if (status != INHERITANCE_SUCCESS) { + emit_incompatible_class_constant_error(obligation->child_const, obligation->parent_const, obligation->const_name); + } } } @@ -3092,6 +3187,7 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e zend_string *key; zend_function *parent_func; zend_property_info *parent_info; + zend_class_constant *parent_const; inheritance_status overall_status = INHERITANCE_SUCCESS; ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, parent_func) { @@ -3130,6 +3226,25 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e } } ZEND_HASH_FOREACH_END(); + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->constants_table, key, parent_const) { + zval *zv; + if ((ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PRIVATE) || !ZEND_TYPE_IS_SET(parent_const->type)) { + continue; + } + + zv = zend_hash_find_known_hash(&ce->constants_table, key); + if (zv) { + zend_class_constant *child_const = Z_PTR_P(zv); + if (ZEND_TYPE_IS_SET(child_const->type)) { + inheritance_status status = class_constant_types_compatible(parent_const, child_const); + ZEND_ASSERT(status != INHERITANCE_WARNING); + if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { + return status; + } + } + } + } ZEND_HASH_FOREACH_END(); + return overall_status; } /* }}} */ diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d1d54ade4dfd0..9b663887264f0 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -267,7 +267,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type echo_expr_list unset_variables catch_name_list catch_list optional_variable parameter_list class_statement_list %type implements_list case_list if_stmt_without_else %type non_empty_parameter_list argument_list non_empty_argument_list property_list -%type class_const_list class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs +%type class_const_list first_class_const_decl class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs %type ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars %type lexical_var_list encaps_list %type array_pair non_empty_array_pair_list array_pair_list possible_array_pair @@ -812,7 +812,6 @@ parameter: NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); } ; - optional_type_without_static: %empty { $$ = NULL; } | type_expr_without_static { $$ = $1; } @@ -1077,15 +1076,21 @@ property: class_const_list: class_const_list ',' class_const_decl { $$ = zend_ast_list_add($1, $3); } - | class_const_decl { $$ = zend_ast_create_list(1, ZEND_AST_CLASS_CONST_DECL, $1); } + | first_class_const_decl { $$ = zend_ast_create_list(1, ZEND_AST_CLASS_CONST_DECL, $1); } +; + +first_class_const_decl: + T_STRING '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); } + | semi_reserved '=' expr backup_doc_comment { zval zv; if (zend_lex_tstring(&zv, $1) == FAILURE) { YYABORT; } $$ = zend_ast_create(ZEND_AST_CONST_ELEM, zend_ast_create_zval(&zv), $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); } + | type_expr identifier '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $2, $4, ($5 ? zend_ast_create_zval_from_str($5) : NULL), $1); } ; class_const_decl: - identifier '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL)); } + identifier '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); } ; const_decl: - T_STRING '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL)); } + T_STRING '=' expr backup_doc_comment { $$ = zend_ast_create(ZEND_AST_CONST_ELEM, $1, $3, ($4 ? zend_ast_create_zval_from_str($4) : NULL), NULL); } ; echo_expr_list: @@ -1317,8 +1322,8 @@ function_call: { $$ = zend_ast_create(ZEND_AST_STATIC_CALL, $1, $3, $4); } | variable_class_name T_PAAMAYIM_NEKUDOTAYIM member_name argument_list { $$ = zend_ast_create(ZEND_AST_STATIC_CALL, $1, $3, $4); } - | callable_expr { $$ = CG(zend_lineno); } argument_list { - $$ = zend_ast_create(ZEND_AST_CALL, $1, $3); + | callable_expr { $$ = CG(zend_lineno); } argument_list { + $$ = zend_ast_create(ZEND_AST_CALL, $1, $3); $$->lineno = $2; } ; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index a3a8bde7bd8bb..0b6604217fa35 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5991,8 +5991,7 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO } } if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); FREE_OP2(); HANDLE_EXCEPTION(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a48c72d81b79c..21b927c02b895 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7265,8 +7265,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS } } if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); @@ -8419,8 +8418,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS } } if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); FREE_OP(opline->op2_type, opline->op2.var); HANDLE_EXCEPTION(); @@ -25084,8 +25082,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_ } } if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); @@ -25647,8 +25644,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_ } } if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); FREE_OP(opline->op2_type, opline->op2.var); HANDLE_EXCEPTION(); @@ -34232,8 +34228,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS } } if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); @@ -34585,8 +34580,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS } } if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) { ZVAL_UNDEF(EX_VAR(opline->result.var)); FREE_OP(opline->op2_type, opline->op2.var); HANDLE_EXCEPTION(); diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 35aa00516b35c..11ab472631b56 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3749,15 +3749,16 @@ static bool preload_try_resolve_constants(zend_class_entry *ce) bool ok, changed, was_changed = false; zend_class_constant *c; zval *val; + zend_string *key; EG(exception) = (void*)(uintptr_t)-1; /* prevent error reporting */ do { ok = true; changed = false; - ZEND_HASH_MAP_FOREACH_PTR(&ce->constants_table, c) { + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->constants_table, key, c) { val = &c->value; if (Z_TYPE_P(val) == IS_CONSTANT_AST) { - if (EXPECTED(zval_update_constant_ex(val, c->ce) == SUCCESS)) { + if (EXPECTED(zend_update_class_constant(c, key, c->ce) == SUCCESS)) { was_changed = changed = true; } else { ok = false; diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index e1307c8ead04a..f4c9a77996b96 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -692,12 +692,12 @@ static void zend_file_cache_serialize_class_constant(zval *z SERIALIZE_PTR(c->ce); zend_file_cache_serialize_zval(&c->value, script, info, buf); - if (c->doc_comment) { SERIALIZE_STR(c->doc_comment); } SERIALIZE_ATTRIBUTES(c->attributes); + zend_file_cache_serialize_type(&c->type, script, info, buf); } } } @@ -1531,6 +1531,7 @@ static void zend_file_cache_unserialize_class_constant(zval * UNSERIALIZE_STR(c->doc_comment); } UNSERIALIZE_ATTRIBUTES(c->attributes); + zend_file_cache_unserialize_type(&c->type, c->ce, script, buf); } } } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index d7c2d63339107..e21aaa069348a 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -840,6 +840,7 @@ static void zend_persist_class_constant(zval *zv) if (c->attributes) { c->attributes = zend_persist_attributes(c->attributes); } + zend_persist_type(&c->type); } zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 4e3af0d68c653..dfc281eb7f6f7 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -399,6 +399,7 @@ static void zend_persist_class_constant_calc(zval *zv) if (c->attributes) { zend_persist_attributes_calc(c->attributes); } + zend_persist_type_calc(&c->type); } } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 467da74a9b976..2f8acbfb00852 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -297,7 +297,7 @@ static zval *reflection_instantiate(zend_class_entry *pce, zval *object) /* {{{ static void _const_string(smart_str *str, char *name, zval *value, char *indent); static void _function_string(smart_str *str, zend_function *fptr, zend_class_entry *scope, char* indent); static void _property_string(smart_str *str, zend_property_info *prop, const char *prop_name, char* indent); -static void _class_const_string(smart_str *str, char *name, zend_class_constant *c, char* indent); +static void _class_const_string(smart_str *str, zend_string *name, zend_class_constant *c, char* indent); static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, char *indent); static void _extension_string(smart_str *str, zend_module_entry *module, char *indent); static void _zend_extension_string(smart_str *str, zend_extension *extension, char *indent); @@ -384,7 +384,7 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, char zend_class_constant *c; ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(CE_CONSTANTS_TABLE(ce), key, c) { - _class_const_string(str, ZSTR_VAL(key), c, ZSTR_VAL(sub_indent)); + _class_const_string(str, key, c, ZSTR_VAL(sub_indent)); if (UNEXPECTED(EG(exception))) { zend_string_release(sub_indent); return; @@ -556,17 +556,18 @@ static void _const_string(smart_str *str, char *name, zval *value, char *indent) /* }}} */ /* {{{ _class_const_string */ -static void _class_const_string(smart_str *str, char *name, zend_class_constant *c, char *indent) +static void _class_const_string(smart_str *str, zend_string *name, zend_class_constant *c, char *indent) { - if (zval_update_constant_ex(&c->value, c->ce) == FAILURE) { + if (Z_TYPE(c->value) == IS_CONSTANT_AST && zend_update_class_constant(c, name, c->ce) == FAILURE) { return; } const char *visibility = zend_visibility_string(ZEND_CLASS_CONST_FLAGS(c)); const char *final = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_FINAL ? "final " : ""; - const char *type = zend_zval_type_name(&c->value); + zend_string *type_str = ZEND_TYPE_IS_SET(c->type) ? zend_type_to_string(c->type) : NULL; + const char *type = type_str ? ZSTR_VAL(type_str) : zend_zval_type_name(&c->value); smart_str_append_printf(str, "%sConstant [ %s%s %s %s ] { ", - indent, final, visibility, type, name); + indent, final, visibility, type, ZSTR_VAL(name)); if (Z_TYPE(c->value) == IS_ARRAY) { smart_str_appends(str, "Array"); } else if (Z_TYPE(c->value) == IS_OBJECT) { @@ -578,6 +579,10 @@ static void _class_const_string(smart_str *str, char *name, zend_class_constant zend_tmp_string_release(tmp_value_str); } smart_str_appends(str, " }\n"); + + if (type_str) { + zend_string_release(type_str); + } } /* }}} */ @@ -3796,7 +3801,7 @@ ZEND_METHOD(ReflectionClassConstant, __toString) ZVAL_DEREF(name); ZEND_ASSERT(Z_TYPE_P(name) == IS_STRING); - _class_const_string(&str, Z_STRVAL_P(name), ref, ""); + _class_const_string(&str, Z_STR_P(name), ref, ""); RETURN_STR(smart_str_extract(&str)); } /* }}} */ @@ -3820,6 +3825,39 @@ ZEND_METHOD(ReflectionClassConstant, getName) } /* }}} */ +/* Returns the type associated with the class constant */ +ZEND_METHOD(ReflectionClassConstant, getType) +{ + reflection_object *intern; + zend_class_constant *ref; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ref); + + if (!ZEND_TYPE_IS_SET(ref->type)) { + RETURN_NULL(); + } + + reflection_type_factory(ref->type, return_value, 1); +} + +/* Returns whether class constant has a type */ +ZEND_METHOD(ReflectionClassConstant, hasType) +{ + reflection_object *intern; + zend_class_constant *ref; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ref); + RETVAL_BOOL(ZEND_TYPE_IS_SET(ref->type)); +} + static void _class_constant_check_flag(INTERNAL_FUNCTION_PARAMETERS, int mask) /* {{{ */ { reflection_object *intern; @@ -3887,8 +3925,19 @@ ZEND_METHOD(ReflectionClassConstant, getValue) } GET_REFLECTION_OBJECT_PTR(ref); + zval *name = reflection_prop_name(ZEND_THIS); + if (Z_ISUNDEF_P(name)) { + zend_throw_error(NULL, + "Typed property ReflectionClassConstant::$name " + "must not be accessed before initialization"); + RETURN_THROWS(); + } + if (Z_TYPE(ref->value) == IS_CONSTANT_AST) { - zval_update_constant_ex(&ref->value, ref->ce); + zend_result result = zend_update_class_constant(ref, Z_STR_P(name), ref->ce); + if (result == FAILURE) { + RETURN_THROWS(); + } } ZVAL_COPY_OR_DUP(return_value, &ref->value); } @@ -4694,7 +4743,7 @@ ZEND_METHOD(ReflectionClass, getConstants) array_init(return_value); ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(CE_CONSTANTS_TABLE(ce), key, constant) { - if (UNEXPECTED(zval_update_constant_ex(&constant->value, constant->ce) != SUCCESS)) { + if (UNEXPECTED(Z_TYPE(constant->value) == IS_CONSTANT_AST && zend_update_class_constant(constant, key, constant->ce) != SUCCESS)) { RETURN_THROWS(); } @@ -4744,7 +4793,7 @@ ZEND_METHOD(ReflectionClass, getConstant) zend_class_entry *ce; HashTable *constants_table; zend_class_constant *c; - zend_string *name; + zend_string *name, *key; if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) { RETURN_THROWS(); @@ -4752,8 +4801,8 @@ ZEND_METHOD(ReflectionClass, getConstant) GET_REFLECTION_OBJECT_PTR(ce); constants_table = CE_CONSTANTS_TABLE(ce); - ZEND_HASH_MAP_FOREACH_PTR(constants_table, c) { - if (UNEXPECTED(zval_update_constant_ex(&c->value, c->ce) != SUCCESS)) { + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(constants_table, key, c) { + if (UNEXPECTED(Z_TYPE(c->value) == IS_CONSTANT_AST && zend_update_class_constant(c, key, c->ce) != SUCCESS)) { RETURN_THROWS(); } } ZEND_HASH_FOREACH_END(); @@ -6954,7 +7003,7 @@ ZEND_METHOD(ReflectionEnumBackedCase, getBackingValue) if (Z_TYPE(ref->value) == IS_CONSTANT_AST) { zval_update_constant_ex(&ref->value, ref->ce); if (EG(exception)) { - return; + RETURN_THROWS(); } } diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 3ecb5f7876500..de0d83d244a85 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -346,7 +346,7 @@ public function getConstants(?int $filter = null): array {} public function getReflectionConstants(?int $filter = null): array {} /** @tentative-return-type */ - public function getConstant(string $name): mixed {} + public function getConstant(string $name): mixed {} // TODO throw exception when the constant doesn't exist /** @tentative-return-type */ public function getReflectionConstant(string $name): ReflectionClassConstant|false {} @@ -609,6 +609,10 @@ public function getDocComment(): string|false {} public function getAttributes(?string $name = null, int $flags = 0): array {} public function isEnumCase(): bool {} + + public function hasType(): bool {} + + public function getType(): ?ReflectionType {} } /** @not-serializable */ diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 708dbbeeb5d63..ddc445f5206d3 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: f640c1b592a7e9e7a8e92195df579bfaaa3da6dc */ + * Stub hash: 75d10a475cce503d94bd8471764adf495f0ddd34 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -414,6 +414,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClassConstant_isEnumCase arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionClassConstant_hasType arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + +#define arginfo_class_ReflectionClassConstant_getType arginfo_class_ReflectionFunctionAbstract_getTentativeReturnType + #define arginfo_class_ReflectionParameter___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionParameter___construct, 0, 0, 2) @@ -760,6 +764,8 @@ ZEND_METHOD(ReflectionClassConstant, getDeclaringClass); ZEND_METHOD(ReflectionClassConstant, getDocComment); ZEND_METHOD(ReflectionClassConstant, getAttributes); ZEND_METHOD(ReflectionClassConstant, isEnumCase); +ZEND_METHOD(ReflectionClassConstant, hasType); +ZEND_METHOD(ReflectionClassConstant, getType); ZEND_METHOD(ReflectionParameter, __construct); ZEND_METHOD(ReflectionParameter, __toString); ZEND_METHOD(ReflectionParameter, getName); @@ -1046,6 +1052,8 @@ static const zend_function_entry class_ReflectionClassConstant_methods[] = { ZEND_ME(ReflectionClassConstant, getDocComment, arginfo_class_ReflectionClassConstant_getDocComment, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, getAttributes, arginfo_class_ReflectionClassConstant_getAttributes, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, isEnumCase, arginfo_class_ReflectionClassConstant_isEnumCase, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClassConstant, hasType, arginfo_class_ReflectionClassConstant_hasType, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClassConstant, getType, arginfo_class_ReflectionClassConstant_getType, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/tests/ReflectionClassConstant_basic1.phpt b/ext/reflection/tests/ReflectionClassConstant_basic1.phpt index 7d563bdf3bc16..33d923d97130c 100644 --- a/ext/reflection/tests/ReflectionClassConstant_basic1.phpt +++ b/ext/reflection/tests/ReflectionClassConstant_basic1.phpt @@ -28,11 +28,15 @@ function reflectClassConstant($base, $constant) { var_dump($constInfo->getDeclaringClass()); echo "getDocComment():\n"; var_dump($constInfo->getDocComment()); + echo "hasType():\n"; + var_dump($constInfo->hasType()); + echo "getType():\n"; + echo $constInfo->getType() ?? "NULL"; echo "\n**********************************\n"; } class TestClass { - public const /** My Doc comment */ PUB = true; + public const bool /** My Doc comment */ PUB = true; /** Another doc comment */ protected const PROT = 4; private const PRIV = "keepOut"; @@ -76,7 +80,10 @@ object(ReflectionClass)#3 (1) { } getDocComment(): string(21) "/** My Doc comment */" - +hasType(): +bool(true) +getType(): +bool ********************************** ********************************** Reflecting on class constant TestClass::PROT @@ -105,7 +112,10 @@ object(ReflectionClass)#3 (1) { } getDocComment(): string(26) "/** Another doc comment */" - +hasType(): +bool(false) +getType(): +NULL ********************************** ********************************** Reflecting on class constant TestClass::PRIV @@ -134,7 +144,10 @@ object(ReflectionClass)#3 (1) { } getDocComment(): bool(false) - +hasType(): +bool(false) +getType(): +NULL ********************************** ********************************** Reflecting on class constant TestClass::FINAL @@ -163,7 +176,10 @@ object(ReflectionClass)#3 (1) { } getDocComment(): bool(false) - +hasType(): +bool(false) +getType(): +NULL ********************************** ********************************** Reflecting on class constant TestClass::PRIV @@ -192,7 +208,10 @@ object(ReflectionClass)#3 (1) { } getDocComment(): bool(false) - +hasType(): +bool(false) +getType(): +NULL ********************************** Fatal error: Uncaught ReflectionException: Constant TestClass::BAD_CONST does not exist in %s:%d diff --git a/ext/reflection/tests/ReflectionClassConstant_getValue_typed.phpt b/ext/reflection/tests/ReflectionClassConstant_getValue_typed.phpt new file mode 100644 index 0000000000000..f964f4c146fbc --- /dev/null +++ b/ext/reflection/tests/ReflectionClassConstant_getValue_typed.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test variations of getting typed class constant values +--FILE-- +getValue()); +echo $rc; + +$rc = new ReflectionClassConstant(B::class, 'CONST1'); +try { + $rc->getValue(); +} catch (TypeError $e) { + echo $e->getMessage() . "\n"; +} + +try { + echo $rc; +} catch (TypeError $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +object(stdClass)#1 (0) { +} +Constant [ public object CONST1 ] { Object } +Cannot assign stdClass to class constant B::CONST1 of type array +Cannot assign stdClass to class constant B::CONST1 of type array diff --git a/ext/reflection/tests/ReflectionClass_getConstant_typed.phpt b/ext/reflection/tests/ReflectionClass_getConstant_typed.phpt new file mode 100644 index 0000000000000..c63eaab5d9027 --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_getConstant_typed.phpt @@ -0,0 +1,29 @@ +--TEST-- +ReflectionClass::getConstant() with typed class constants +--FILE-- +getConstant("CONST1")); + +$rc = new ReflectionClass(D::class); +try { + $rc->getConstant("CONST1"); +} catch (TypeError $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +object(stdClass)#1 (0) { +} +Cannot assign stdClass to class constant D::CONST1 of type array diff --git a/ext/reflection/tests/ReflectionClass_toString_006.phpt b/ext/reflection/tests/ReflectionClass_toString_006.phpt new file mode 100644 index 0000000000000..b94467261f682 --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_toString_006.phpt @@ -0,0 +1,32 @@ +--TEST-- +Using ReflectionClass::__toString() with typed class constants +--FILE-- + +--EXPECTF-- +Class [ class Foo ] { + @@ %s 3-5 + + - Constants [1] { + Constant [ public ?int CONST1 ] { 1 } + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [0] { + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/ReflectionClass_toString_007.phpt b/ext/reflection/tests/ReflectionClass_toString_007.phpt new file mode 100644 index 0000000000000..fd3ed8f32e354 --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_toString_007.phpt @@ -0,0 +1,20 @@ +--TEST-- +Using ReflectionClass::__toString() with typed class constants when there is a type mismatch +--FILE-- +getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot assign stdClass to class constant Foo::CONST1 of type array diff --git a/ext/reflection/tests/static_properties_with_typed_class_constants1.phpt b/ext/reflection/tests/static_properties_with_typed_class_constants1.phpt new file mode 100644 index 0000000000000..cebfa1dddd3e0 --- /dev/null +++ b/ext/reflection/tests/static_properties_with_typed_class_constants1.phpt @@ -0,0 +1,24 @@ +--TEST-- +Typed class constant reflection +--FILE-- +getStaticProperties(); +} catch (TypeError $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot assign string to class constant Foo::CONST1 of type int diff --git a/ext/standard/tests/constant_with_typed_class_constant.phpt b/ext/standard/tests/constant_with_typed_class_constant.phpt new file mode 100644 index 0000000000000..87a1c006426da --- /dev/null +++ b/ext/standard/tests/constant_with_typed_class_constant.phpt @@ -0,0 +1,25 @@ +--TEST-- +Calling constant() with a typed class constant +--FILE-- +getMessage() . "\n"; +} + +?> +--EXPECT-- +object(stdClass)#1 (0) { +} +Cannot assign stdClass to class constant Foo::CONST2 of type array