diff --git a/Zend/tests/type_declarations/typed_class_constants_001.phpt b/Zend/tests/type_declarations/typed_class_constants_001.phpt new file mode 100644 index 0000000000000..2d819a7b9a6c5 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_001.phpt @@ -0,0 +1,12 @@ +--TEST-- +Typed class constants (declaration; simple) +--FILE-- + +--EXPECT-- +1 diff --git a/Zend/tests/type_declarations/typed_class_constants_002.phpt b/Zend/tests/type_declarations/typed_class_constants_002.phpt new file mode 100644 index 0000000000000..b4b3e81473f1d --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_002.phpt @@ -0,0 +1,10 @@ +--TEST-- +Typed class constants (type mismatch; compile-time) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use int as value for class constant A::A of type string in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_003.phpt b/Zend/tests/type_declarations/typed_class_constants_003.phpt new file mode 100644 index 0000000000000..d9c461419b7ad --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_003.phpt @@ -0,0 +1,17 @@ +--TEST-- +Typed class constants (type mismatch; runtime) +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: Cannot assign int to class constant A::A of type string in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_004.phpt b/Zend/tests/type_declarations/typed_class_constants_004.phpt new file mode 100644 index 0000000000000..39becb4eb720f --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_004.phpt @@ -0,0 +1,14 @@ +--TEST-- +Typed class constants (inheritance; simple) +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::A must be compatible with A::A in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_005.phpt b/Zend/tests/type_declarations/typed_class_constants_005.phpt new file mode 100644 index 0000000000000..d45f204bde6c8 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_005.phpt @@ -0,0 +1,14 @@ +--TEST-- +Typed class constants (inheritance; missing type in child) +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::A must be compatible with A::A in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_006.phpt b/Zend/tests/type_declarations/typed_class_constants_006.phpt new file mode 100644 index 0000000000000..9ceca122c59b9 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_006.phpt @@ -0,0 +1,10 @@ +--TEST-- +Typed class constants (type not allowed; object) +--FILE-- + +--EXPECTF-- +Fatal error: Class constant A::A cannot have type object in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_007.phpt b/Zend/tests/type_declarations/typed_class_constants_007.phpt new file mode 100644 index 0000000000000..63ff7e0ea1824 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_007.phpt @@ -0,0 +1,10 @@ +--TEST-- +Typed class constants (type not allowed; void) +--FILE-- + +--EXPECTF-- +Fatal error: Class constant A::A cannot have type void in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_008.phpt b/Zend/tests/type_declarations/typed_class_constants_008.phpt new file mode 100644 index 0000000000000..aec3ab2d3bff1 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_008.phpt @@ -0,0 +1,10 @@ +--TEST-- +Typed class constants (type not allowed; callable) +--FILE-- + +--EXPECTF-- +Fatal error: Class constant A::A cannot have type callable in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_009.phpt b/Zend/tests/type_declarations/typed_class_constants_009.phpt new file mode 100644 index 0000000000000..862b37492e20c --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_009.phpt @@ -0,0 +1,16 @@ +--TEST-- +Typed class constants (inheritance; private constants) +--FILE-- + +--EXPECT-- +a diff --git a/Zend/tests/type_declarations/typed_class_constants_010.phpt b/Zend/tests/type_declarations/typed_class_constants_010.phpt new file mode 100644 index 0000000000000..d56e98f5b8aa6 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_010.phpt @@ -0,0 +1,10 @@ +--TEST-- +Typed class constants (type not allowed; class name) +--FILE-- + +--EXPECTF-- +Fatal error: Class constant A::A cannot have type self in %s on line %d diff --git a/Zend/tests/type_declarations/typed_class_constants_011.phpt b/Zend/tests/type_declarations/typed_class_constants_011.phpt new file mode 100644 index 0000000000000..b7fbc53ffccc3 --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_011.phpt @@ -0,0 +1,12 @@ +--TEST-- +Typed class constants (declaration; nullable) +--FILE-- + +--EXPECT-- +NULL diff --git a/Zend/tests/type_declarations/typed_class_constants_012.phpt b/Zend/tests/type_declarations/typed_class_constants_012.phpt new file mode 100644 index 0000000000000..dd1be14ac922a --- /dev/null +++ b/Zend/tests/type_declarations/typed_class_constants_012.phpt @@ -0,0 +1,15 @@ +--TEST-- +Typed class constants (declaration; iterable) +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + int(1) +} diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 4aa0103ae5062..53b2278fe7e66 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1152,6 +1152,10 @@ ZEND_API int zend_update_class_constants(zend_class_entry *class_type) /* {{{ */ if (UNEXPECTED(zval_update_constant_ex(val, c->ce) != SUCCESS)) { return FAILURE; } + + if (ZEND_TYPE_IS_SET(c->type) && UNEXPECTED(!zend_verify_class_constant_type(c, val))) { + return FAILURE; + } } } ZEND_HASH_FOREACH_END(); @@ -3835,7 +3839,7 @@ ZEND_API int zend_declare_property_stringl(zend_class_entry *ce, const char *nam } /* }}} */ -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 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_class_constant *c; @@ -3859,11 +3863,16 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c } else { c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant)); } + ZVAL_COPY_VALUE(&c->value, value); Z_ACCESS_FLAGS(c->value) = access_type; + + c->name = name; 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; } @@ -3875,7 +3884,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 access_type, zend_string *doc_comment) +{ + return zend_declare_typed_class_constant(ce, name, value, access_type, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0)); +} ZEND_API int 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 578cf629f836b..0a45d55a0da57 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -362,6 +362,7 @@ ZEND_API int zend_declare_property_double(zend_class_entry *ce, const char *name ZEND_API int zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type); ZEND_API int 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 int zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value); ZEND_API int zend_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length); diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 53c3be0de944d..760221bdb8e33 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1621,14 +1621,19 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appends(str, "const "); goto simple_list; case ZEND_AST_CLASS_CONST_GROUP: - if (ast->child[1]) { - zend_ast_export_attributes(str, ast->child[1], indent, 1); + if (ast->child[2]) { + zend_ast_export_attributes(str, ast->child[2], indent, 1); } zend_ast_export_visibility(str, ast->attr); smart_str_appends(str, "const "); - ast = ast->child[0]; + if (ast->child[0]) { + zend_ast_export_type(str, ast->child[0], indent); + smart_str_appendc(str, ' '); + } + + ast = ast->child[1]; goto simple_list; case ZEND_AST_NAME_LIST: @@ -2231,7 +2236,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr) ast->child[3] = attr; break; case ZEND_AST_CLASS_CONST_GROUP: - ast->child[1] = attr; + ast->child[2] = attr; break; EMPTY_SWITCH_DEFAULT_CASE() } diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 825c392a18da4..0ddda0df2ff4b 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -139,7 +139,6 @@ enum _zend_ast_kind { ZEND_AST_USE_ELEM, ZEND_AST_TRAIT_ALIAS, ZEND_AST_GROUP_USE, - ZEND_AST_CLASS_CONST_GROUP, ZEND_AST_ATTRIBUTE, /* 3 child nodes */ @@ -152,6 +151,7 @@ enum _zend_ast_kind { ZEND_AST_PROP_GROUP, ZEND_AST_PROP_ELEM, ZEND_AST_CONST_ELEM, + ZEND_AST_CLASS_CONST_GROUP, /* 4 child nodes */ ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ea5baf2a126b1..4d42c346fb1d1 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6640,7 +6640,7 @@ static void zend_check_const_and_trait_alias_attr(uint32_t attr, const char* ent } /* }}} */ -void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr_ast) /* {{{ */ +void zend_compile_class_const_decl(zend_ast *ast, zend_ast *type_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); @@ -6648,6 +6648,7 @@ void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr if ((ce->ce_flags & ZEND_ACC_TRAIT) != 0) { zend_error_noreturn(E_COMPILE_ERROR, "Traits cannot have constants"); + return; } @@ -6660,25 +6661,47 @@ void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr 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, /* use_arena */ 1); + + if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_OBJECT|MAY_BE_CALLABLE|MAY_BE_VOID) || ZEND_TYPE_HAS_NAME(type)) { + 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)); + + return; + } + } if (UNEXPECTED(flags & (ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT|ZEND_ACC_FINAL))) { zend_check_const_and_trait_alias_attr(flags, "constant"); } zend_const_expr_to_zval(&value_zv, value_ast); - c = zend_declare_class_constant_ex(ce, name, &value_zv, flags, doc_comment); + + if (!Z_CONSTANT(value_zv) && !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); } } } -/* }}} */ -void zend_compile_class_const_group(zend_ast *ast) /* {{{ */ +void zend_compile_class_const_group(zend_ast *ast) { - zend_ast *const_ast = ast->child[0]; - zend_ast *attr_ast = ast->child[1]; + zend_ast *type_ast = ast->child[0]; + zend_ast *const_ast = ast->child[1]; + zend_ast *attr_ast = ast->child[2]; if (attr_ast && zend_ast_get_list(const_ast)->children > 1) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot apply attributes to a group of constants"); @@ -6686,9 +6709,8 @@ void zend_compile_class_const_group(zend_ast *ast) /* {{{ */ return; } - zend_compile_class_const_decl(const_ast, ast->attr, attr_ast); + zend_compile_class_const_decl(const_ast, type_ast, ast->attr, attr_ast); } -/* }}} */ static void zend_compile_method_ref(zend_ast *ast, zend_trait_method_reference *method_ref) /* {{{ */ { diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index a3fb59bd3e6d4..bace100717067 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -376,9 +376,11 @@ typedef struct _zend_property_info { typedef struct _zend_class_constant { zval value; /* access flags are stored in reserved: zval.u2.access_flags */ + zend_string *name; zend_string *doc_comment; HashTable *attributes; zend_class_entry *ce; + zend_type type; } zend_class_constant; /* arg_info for internal functions */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 5df8345c762dd..ac94f468a144c 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -833,6 +833,35 @@ ZEND_API zend_bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, z return zend_verify_weak_scalar_type_hint(type_mask, arg); } +ZEND_COLD zend_never_inline void zend_verify_class_constant_type_error(zend_class_constant *c, 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(c->name), ZSTR_VAL(type_str)); +} + +zend_bool zend_never_inline zend_verify_class_constant_type(zend_class_constant *c, zval *constant) +{ + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(c->type, Z_TYPE_P(constant)))) { + return 1; + } + + uint32_t type_mask = ZEND_TYPE_FULL_MASK(c->type); + + if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(constant)) { + return 1; + } + + if (zend_verify_scalar_type_hint(type_mask, constant, /* strict */ 1, /* is_internal_arg */ 0)) { + return 1; + } + + zend_verify_class_constant_type_error(c, constant); + + return 0; +} + ZEND_COLD zend_never_inline void zend_verify_property_type_error(zend_property_info *info, zval *property) { zend_string *type_str; diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 7b1ae11748991..76b57a128a3bb 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -393,6 +393,9 @@ ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, #define ZEND_CLASS_HAS_TYPE_HINTS(ce) ((ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) == ZEND_ACC_HAS_TYPE_HINTS) +zend_bool zend_verify_class_constant_type(zend_class_constant *c, zval *constant); +ZEND_COLD void zend_verify_class_constant_type_error(zend_class_constant *c, zval *constant); + zend_bool zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict); ZEND_COLD void zend_verify_property_type_error(zend_property_info *info, zval *property); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 8a3c9ba1aac45..50a6f8affee52 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1110,6 +1110,11 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en } /* }}} */ +inheritance_status class_constant_types_compatible(const zend_class_constant *parent, const zend_class_constant *child) +{ + 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_ex(&ce->constants_table, name, 1); @@ -1117,10 +1122,18 @@ 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((Z_ACCESS_FLAGS(c->value) & ZEND_ACC_PPP_MASK) > (Z_ACCESS_FLAGS(parent_const->value) & 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(Z_ACCESS_FLAGS(parent_const->value)), ZSTR_VAL(parent_const->ce->name), (Z_ACCESS_FLAGS(parent_const->value) & ZEND_ACC_PUBLIC) ? "" : " or weaker"); } + + if (!(Z_ACCESS_FLAGS(parent_const->value) & ZEND_ACC_PRIVATE) && UNEXPECTED(ZEND_TYPE_IS_SET(parent_const->type))) { + if (class_constant_types_compatible(parent_const, c) == INHERITANCE_ERROR) { + zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s::%s must be compatible with %s::%s", + ZSTR_VAL(c->ce->name), ZSTR_VAL(name), ZSTR_VAL(parent_const->ce->name), ZSTR_VAL(name)); + } + } } else if (!(Z_ACCESS_FLAGS(parent_const->value) & ZEND_ACC_PRIVATE)) { if (Z_TYPE(parent_const->value) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 7827a178f6f5b..cb1c7de4730f4 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -796,8 +796,8 @@ attributed_class_statement: variable_modifiers optional_type_without_static property_list ';' { $$ = zend_ast_create(ZEND_AST_PROP_GROUP, $2, $3, NULL); $$->attr = $1; } - | method_modifiers T_CONST class_const_list ';' - { $$ = zend_ast_create(ZEND_AST_CLASS_CONST_GROUP, $3, NULL); + | method_modifiers T_CONST type_expr_without_static class_const_list ';' + { $$ = zend_ast_create(ZEND_AST_CLASS_CONST_GROUP, $3, $4, NULL); $$->attr = $1; } | method_modifiers function returns_ref identifier backup_doc_comment '(' parameter_list ')' return_type backup_fn_flags method_body backup_fn_flags diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index c316795450fe0..622d97582e551 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5481,14 +5481,15 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } - value = &c->value; - if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(!(c->ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) { + if (UNEXPECTED(zend_update_class_constants(c->ce)) != SUCCESS) { ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } + + value = &c->value; + CACHE_POLYMORPHIC_PTR(opline->extended_value, ce, value); } else { zend_throw_error(NULL, "Undefined class constant '%s::%s'", diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 67f1c0d615229..bb09a401bc2c4 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5966,14 +5966,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } - value = &c->value; - if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(!(c->ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) { + if (UNEXPECTED(zend_update_class_constants(c->ce)) != SUCCESS) { ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } + + value = &c->value; + CACHE_POLYMORPHIC_PTR(opline->extended_value, ce, value); } else { zend_throw_error(NULL, "Undefined class constant '%s::%s'", @@ -22818,14 +22819,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_ ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } - value = &c->value; - if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(!(c->ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) { + if (UNEXPECTED(zend_update_class_constants(c->ce)) != SUCCESS) { ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } + + value = &c->value; + CACHE_POLYMORPHIC_PTR(opline->extended_value, ce, value); } else { zend_throw_error(NULL, "Undefined class constant '%s::%s'", @@ -30774,14 +30776,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } - value = &c->value; - if (Z_TYPE_P(value) == IS_CONSTANT_AST) { - zval_update_constant_ex(value, c->ce); - if (UNEXPECTED(EG(exception) != NULL)) { + if (UNEXPECTED(!(c->ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) { + if (UNEXPECTED(zend_update_class_constants(c->ce)) != SUCCESS) { ZVAL_UNDEF(EX_VAR(opline->result.var)); HANDLE_EXCEPTION(); } } + + value = &c->value; + CACHE_POLYMORPHIC_PTR(opline->extended_value, ce, value); } else { zend_throw_error(NULL, "Undefined class constant '%s::%s'", diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index e5ce3cf88d00e..153e9eacc5850 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3783,6 +3783,43 @@ ZEND_METHOD(ReflectionClassConstant, getDocComment) } /* }}} */ +/* {{{ proto public ReflectionType ReflectionClassConstant::getType() + 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); +} +/* }}} */ + +/* {{{ proto public bool ReflectionClassConstant::hasType() + 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)); +} +/* }}} */ + /* {{{ proto public array ReflectionClassConstant::getAttributes([ string name, int flags ]) Returns the attributes of this constant */ ZEND_METHOD(ReflectionClassConstant, getAttributes) diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 31c08e92b283b..15fbeb44d4dfd 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -476,6 +476,10 @@ public function getDeclaringClass() {} /** @return string|false */ public function getDocComment() {} + public function hasType(): bool {} + + public function getType(): ?ReflectionType {} + /** @return ReflectionAttribute[] */ public function getAttributes(?string $name = null, int $flags = 0): array {} } diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 1d9f730309013..cea5b6cbf6e67 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: 762e9bab89b8edae0b567c1c3926ffa226abe874 */ + * Stub hash: 44e9589fbd66ee04142e5ad2438f7423a5c7d486 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -358,6 +358,11 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClassConstant_getDocComment arginfo_class_ReflectionFunctionAbstract___clone +#define arginfo_class_ReflectionClassConstant_hasType arginfo_class_ReflectionProperty_isPromoted + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionClassConstant_getType, 0, 0, ReflectionType, 1) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionClassConstant_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes #define arginfo_class_ReflectionParameter___clone arginfo_class_ReflectionFunctionAbstract___clone @@ -636,6 +641,8 @@ ZEND_METHOD(ReflectionClassConstant, isProtected); ZEND_METHOD(ReflectionClassConstant, getModifiers); ZEND_METHOD(ReflectionClassConstant, getDeclaringClass); ZEND_METHOD(ReflectionClassConstant, getDocComment); +ZEND_METHOD(ReflectionClassConstant, hasType); +ZEND_METHOD(ReflectionClassConstant, getType); ZEND_METHOD(ReflectionClassConstant, getAttributes); ZEND_METHOD(ReflectionParameter, __construct); ZEND_METHOD(ReflectionParameter, __toString); @@ -893,6 +900,8 @@ static const zend_function_entry class_ReflectionClassConstant_methods[] = { ZEND_ME(ReflectionClassConstant, getModifiers, arginfo_class_ReflectionClassConstant_getModifiers, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, getDeclaringClass, arginfo_class_ReflectionClassConstant_getDeclaringClass, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, getDocComment, arginfo_class_ReflectionClassConstant_getDocComment, 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_ME(ReflectionClassConstant, getAttributes, arginfo_class_ReflectionClassConstant_getAttributes, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/tests/ReflectionClassConstant_basic1.phpt b/ext/reflection/tests/ReflectionClassConstant_basic1.phpt index 414fac8543d5d..83d2052a3a87e 100644 --- a/ext/reflection/tests/ReflectionClassConstant_basic1.phpt +++ b/ext/reflection/tests/ReflectionClassConstant_basic1.phpt @@ -26,11 +26,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"; + var_dump($constInfo->getType()); 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"; @@ -70,6 +74,11 @@ object(ReflectionClass)#3 (1) { } getDocComment(): string(21) "/** My Doc comment */" +hasType(): +bool(true) +getType(): +object(ReflectionNamedType)#3 (0) { +} ********************************** ********************************** @@ -97,6 +106,10 @@ object(ReflectionClass)#3 (1) { } getDocComment(): string(26) "/** Another doc comment */" +hasType(): +bool(false) +getType(): +NULL ********************************** ********************************** @@ -124,6 +137,10 @@ object(ReflectionClass)#3 (1) { } getDocComment(): bool(false) +hasType(): +bool(false) +getType(): +NULL ********************************** ********************************** @@ -151,6 +168,10 @@ object(ReflectionClass)#3 (1) { } getDocComment(): bool(false) +hasType(): +bool(false) +getType(): +NULL **********************************