Skip to content

Commit 715b5bd

Browse files
committed
Implement delayed variance checks and other fixes
1 parent e4498cc commit 715b5bd

19 files changed

+159
-52
lines changed

Zend/tests/type_declarations/typed_class_constants_inheritance_error1.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ class B extends A {
1111
}
1212
?>
1313
--EXPECTF--
14-
Fatal error: Declaration of B::CONST1 must be compatible with A::CONST1 in %s on line %d
14+
Fatal error: Type of B::CONST1 must be compatible with A::CONST1 of type int in %s on line %d

Zend/tests/type_declarations/typed_class_constants_inheritance_error2.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ class B extends A {
1111
}
1212
?>
1313
--EXPECTF--
14-
Fatal error: Declaration of B::CONST1 must be compatible with A::CONST1 in %s on line %d
14+
Fatal error: Type of B::CONST1 must be compatible with A::CONST1 of type int in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Typed class constants (type error)
3+
--FILE--
4+
<?php
5+
class A1 {
6+
const ?B1 C = null;
7+
}
8+
9+
class A2 extends A1 {
10+
const ?B2 C = null;
11+
}
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: Type of A2::C must be compatible with A1::C of type ?B1 in %s on line %d
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Typed class constants (static type error)
3+
--FILE--
4+
<?php
5+
enum E1 {
6+
const static C = E2::Foo;
7+
}
8+
9+
enum E2 {
10+
case Foo;
11+
}
12+
13+
try {
14+
var_dump(E1::C);
15+
} catch (TypeError $e) {
16+
echo $e->getMessage() . "\n";
17+
}
18+
19+
?>
20+
--EXPECT--
21+
Cannot assign E2 to class constant E1::C of type static

Zend/tests/type_declarations/typed_class_constants_type_success3.phpt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,15 @@ var_dump(A::CONST1);
2525
var_dump(A::CONST1);
2626
var_dump(A::CONST2);
2727
var_dump(A::CONST2);
28+
var_dump(E::CONST3);
29+
var_dump(E::CONST3);
2830
var_dump(A::CONST3);
2931
var_dump(A::CONST3);
32+
var_dump(E::CONST4);
33+
var_dump(E::CONST4);
3034
var_dump(A::CONST4);
3135
var_dump(A::CONST4);
36+
3237
?>
3338
--EXPECT--
3439
enum(E::Foo)
@@ -41,3 +46,7 @@ enum(E::Foo)
4146
enum(E::Foo)
4247
enum(E::Foo)
4348
enum(E::Foo)
49+
enum(E::Foo)
50+
enum(E::Foo)
51+
enum(E::Foo)
52+
enum(E::Foo)

Zend/zend_API.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,7 +1412,7 @@ static zend_result update_property(zval *val, zend_property_info *prop_info) {
14121412
return zval_update_constant_ex(val, prop_info->ce);
14131413
}
14141414

1415-
ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, zend_class_entry *scope)
1415+
ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const zend_string *name, zend_class_entry *scope)
14161416
{
14171417
ZEND_ASSERT(Z_TYPE(c->value) == IS_CONSTANT_AST);
14181418

@@ -1429,7 +1429,7 @@ ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, zend_cla
14291429
return FAILURE;
14301430
}
14311431

1432-
if (UNEXPECTED(!zend_verify_class_constant_type(c, &tmp))) {
1432+
if (UNEXPECTED(!zend_verify_class_constant_type(c, name, &tmp))) {
14331433
zval_ptr_dtor(&tmp);
14341434
return FAILURE;
14351435
}
@@ -1498,7 +1498,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /
14981498
}
14991499

15001500
val = &c->value;
1501-
if (UNEXPECTED(zend_update_class_constant(c, c->ce) != SUCCESS)) {
1501+
if (UNEXPECTED(zend_update_class_constant(c, name, c->ce) != SUCCESS)) {
15021502
return FAILURE;
15031503
}
15041504
}
@@ -4593,7 +4593,6 @@ ZEND_API zend_class_constant *zend_declare_typed_class_constant(zend_class_entry
45934593
c->doc_comment = doc_comment;
45944594
c->attributes = NULL;
45954595
c->ce = ce;
4596-
c->name = name;
45974596
c->type = type;
45984597

45994598
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {

Zend/zend_API.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ ZEND_API void zend_declare_class_constant_double(zend_class_entry *ce, const cha
439439
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);
440440
ZEND_API void zend_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value);
441441

442-
ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, zend_class_entry *scope);
442+
ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const zend_string *name, zend_class_entry *scope);
443443
ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type);
444444
ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type);
445445

Zend/zend_compile.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,6 @@ typedef struct _zend_property_info {
404404

405405
typedef struct _zend_class_constant {
406406
zval value; /* flags are stored in u2 */
407-
zend_string *name;
408407
zend_string *doc_comment;
409408
HashTable *attributes;
410409
zend_class_entry *ce;

Zend/zend_constants.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ ZEND_API zval *zend_get_class_constant_ex(zend_string *class_name, zend_string *
361361
}
362362

363363
MARK_CONSTANT_VISITED(ret_constant);
364-
ret = zend_update_class_constant(c, c->ce);
364+
ret = zend_update_class_constant(c, constant_name, c->ce);
365365
RESET_CONSTANT_VISITED(ret_constant);
366366

367367
if (UNEXPECTED(ret != SUCCESS)) {

Zend/zend_execute.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -820,12 +820,12 @@ ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool s
820820
return zend_verify_weak_scalar_type_hint(type_mask, arg);
821821
}
822822

823-
ZEND_COLD zend_never_inline void zend_verify_class_constant_type_error(const zend_class_constant *c, const zval *constant)
823+
ZEND_COLD zend_never_inline void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant)
824824
{
825825
zend_string *type_str = zend_type_to_string(c->type);
826826

827827
zend_type_error("Cannot assign %s to class constant %s::%s of type %s",
828-
zend_zval_type_name(constant), ZSTR_VAL(c->ce->name), ZSTR_VAL(c->name), ZSTR_VAL(type_str));
828+
zend_zval_type_name(constant), ZSTR_VAL(c->ce->name), ZSTR_VAL(name), ZSTR_VAL(type_str));
829829

830830
zend_string_release(type_str);
831831
}
@@ -971,7 +971,7 @@ if (ZEND_TYPE_HAS_LIST(member_type)) {
971971
return false;
972972
}
973973
} else if ((ZEND_TYPE_PURE_MASK(member_type) & MAY_BE_STATIC)) {
974-
return instanceof_function(value_ce, scope);
974+
return value_ce == scope;
975975
} else {
976976
const zend_class_entry *ce = zend_ce_from_type(scope, &member_type);
977977
return ce && instanceof_function(value_ce, ce);
@@ -1484,10 +1484,10 @@ static zend_always_inline bool zend_check_class_constant_type(zend_class_constan
14841484
return zend_verify_scalar_type_hint(type_mask, constant, true, false);
14851485
}
14861486

1487-
ZEND_API bool zend_never_inline zend_verify_class_constant_type(zend_class_constant *c, zval *constant)
1487+
ZEND_API bool zend_never_inline zend_verify_class_constant_type(zend_class_constant *c, const zend_string *name, zval *constant)
14881488
{
14891489
if (!zend_check_class_constant_type(c, constant)) {
1490-
zend_verify_class_constant_type_error(c, constant);
1490+
zend_verify_class_constant_type_error(c, name, constant);
14911491
return 0;
14921492
}
14931493

Zend/zend_execute.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,8 @@ ZEND_API int ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *call);
485485
#define ZEND_CLASS_HAS_READONLY_PROPS(ce) ((ce->ce_flags & ZEND_ACC_HAS_READONLY_PROPS) == ZEND_ACC_HAS_READONLY_PROPS)
486486

487487

488-
ZEND_API bool zend_verify_class_constant_type(zend_class_constant *c, zval *constant);
489-
ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant *c, const zval *constant);
488+
ZEND_API bool zend_verify_class_constant_type(zend_class_constant *c, const zend_string *name, zval *constant);
489+
ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant);
490490

491491
ZEND_API bool zend_verify_property_type(const zend_property_info *info, zval *property, bool strict);
492492
ZEND_COLD void zend_verify_property_type_error(const zend_property_info *info, const zval *property);

Zend/zend_inheritance.c

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ static void add_compatibility_obligation(
5151
static void add_property_compatibility_obligation(
5252
zend_class_entry *ce, const zend_property_info *child_prop,
5353
const zend_property_info *parent_prop);
54+
static void add_class_constant_compatibility_obligation(
55+
zend_class_entry *ce, const zend_class_constant *child_const,
56+
const zend_class_constant *parent_const, const zend_string *const_name);
5457

5558
static void ZEND_COLD emit_incompatible_method_error(
5659
const zend_function *child, zend_class_entry *child_scope,
@@ -1359,8 +1362,22 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en
13591362
}
13601363
/* }}} */
13611364

1365+
static void emit_incompatible_class_constant_error(
1366+
const zend_class_constant *child, const zend_class_constant *parent, const zend_string *const_name) {
1367+
zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce);
1368+
zend_error_noreturn(E_COMPILE_ERROR,
1369+
"Type of %s::%s must be compatible with %s::%s of type %s",
1370+
ZSTR_VAL(child->ce->name),
1371+
ZSTR_VAL(const_name),
1372+
ZSTR_VAL(parent->ce->name),
1373+
ZSTR_VAL(const_name),
1374+
ZSTR_VAL(type_str));
1375+
}
1376+
13621377
static inheritance_status class_constant_types_compatible(const zend_class_constant *parent, const zend_class_constant *child)
13631378
{
1379+
ZEND_ASSERT(ZEND_TYPE_IS_SET(parent->type));
1380+
13641381
if (!ZEND_TYPE_IS_SET(child->type)) {
13651382
return INHERITANCE_ERROR;
13661383
}
@@ -1393,10 +1410,11 @@ static void do_inherit_class_constant(zend_string *name, zend_class_constant *pa
13931410
}
13941411

13951412
if (!(ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PRIVATE) && UNEXPECTED(ZEND_TYPE_IS_SET(parent_const->type))) {
1396-
if (class_constant_types_compatible(parent_const, c) == INHERITANCE_ERROR) {
1397-
zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s::%s must be compatible with %s::%s",
1398-
ZSTR_VAL(c->ce->name), ZSTR_VAL(name), ZSTR_VAL(parent_const->ce->name), ZSTR_VAL(name)
1399-
);
1413+
inheritance_status status = class_constant_types_compatible(parent_const, c);
1414+
if (status == INHERITANCE_ERROR) {
1415+
emit_incompatible_class_constant_error(c, parent_const, name);
1416+
} else if (status == INHERITANCE_UNRESOLVED) {
1417+
add_class_constant_compatibility_obligation(ce, c, parent_const, name);
14001418
}
14011419
}
14021420
} else if (!(ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PRIVATE)) {
@@ -2557,7 +2575,8 @@ typedef struct {
25572575
enum {
25582576
OBLIGATION_DEPENDENCY,
25592577
OBLIGATION_COMPATIBILITY,
2560-
OBLIGATION_PROPERTY_COMPATIBILITY
2578+
OBLIGATION_PROPERTY_COMPATIBILITY,
2579+
OBLIGATION_CLASS_CONSTANT_COMPATIBILITY
25612580
} type;
25622581
union {
25632582
zend_class_entry *dependency_ce;
@@ -2573,6 +2592,11 @@ typedef struct {
25732592
const zend_property_info *parent_prop;
25742593
const zend_property_info *child_prop;
25752594
};
2595+
struct {
2596+
const zend_string *const_name;
2597+
const zend_class_constant *parent_const;
2598+
const zend_class_constant *child_const;
2599+
};
25762600
};
25772601
} variance_obligation;
25782602

@@ -2648,6 +2672,18 @@ static void add_property_compatibility_obligation(
26482672
zend_hash_next_index_insert_ptr(obligations, obligation);
26492673
}
26502674

2675+
static void add_class_constant_compatibility_obligation(
2676+
zend_class_entry *ce, const zend_class_constant *child_const,
2677+
const zend_class_constant *parent_const, const zend_string *const_name) {
2678+
HashTable *obligations = get_or_init_obligations_for_class(ce);
2679+
variance_obligation *obligation = emalloc(sizeof(variance_obligation));
2680+
obligation->type = OBLIGATION_CLASS_CONSTANT_COMPATIBILITY;
2681+
obligation->const_name = const_name;
2682+
obligation->child_const = child_const;
2683+
obligation->parent_const = parent_const;
2684+
zend_hash_next_index_insert_ptr(obligations, obligation);
2685+
}
2686+
26512687
static void resolve_delayed_variance_obligations(zend_class_entry *ce);
26522688

26532689
static void check_variance_obligation(variance_obligation *obligation) {
@@ -2671,13 +2707,19 @@ static void check_variance_obligation(variance_obligation *obligation) {
26712707
&obligation->parent_fn, obligation->parent_scope, status);
26722708
}
26732709
/* Either the compatibility check was successful or only threw a warning. */
2674-
} else {
2675-
ZEND_ASSERT(obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY);
2710+
} else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) {
26762711
inheritance_status status =
26772712
property_types_compatible(obligation->parent_prop, obligation->child_prop);
26782713
if (status != INHERITANCE_SUCCESS) {
26792714
emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop);
26802715
}
2716+
} else {
2717+
ZEND_ASSERT(obligation->type == OBLIGATION_CLASS_CONSTANT_COMPATIBILITY);
2718+
inheritance_status status =
2719+
class_constant_types_compatible(obligation->parent_const, obligation->child_const);
2720+
if (status != INHERITANCE_SUCCESS) {
2721+
emit_incompatible_class_constant_error(obligation->child_const, obligation->parent_const, obligation->const_name);
2722+
}
26812723
}
26822724
}
26832725

@@ -3141,6 +3183,7 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
31413183
zend_string *key;
31423184
zend_function *parent_func;
31433185
zend_property_info *parent_info;
3186+
zend_class_constant *parent_const;
31443187
inheritance_status overall_status = INHERITANCE_SUCCESS;
31453188

31463189
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, parent_func) {
@@ -3179,6 +3222,25 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
31793222
}
31803223
} ZEND_HASH_FOREACH_END();
31813224

3225+
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->constants_table, key, parent_const) {
3226+
zval *zv;
3227+
if ((ZEND_CLASS_CONST_FLAGS(parent_const) & ZEND_ACC_PRIVATE) || !ZEND_TYPE_IS_SET(parent_const->type)) {
3228+
continue;
3229+
}
3230+
3231+
zv = zend_hash_find_known_hash(&ce->constants_table, key);
3232+
if (zv) {
3233+
zend_class_constant *child_const = Z_PTR_P(zv);
3234+
if (ZEND_TYPE_IS_SET(child_const->type)) {
3235+
inheritance_status status = class_constant_types_compatible(parent_const, child_const);
3236+
ZEND_ASSERT(status != INHERITANCE_WARNING);
3237+
if (UNEXPECTED(status != INHERITANCE_SUCCESS)) {
3238+
return status;
3239+
}
3240+
}
3241+
}
3242+
} ZEND_HASH_FOREACH_END();
3243+
31823244
return overall_status;
31833245
}
31843246
/* }}} */

Zend/zend_vm_def.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5991,7 +5991,7 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
59915991
}
59925992
}
59935993
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
5994-
if (UNEXPECTED(zend_update_class_constant(c, c->ce) != SUCCESS)) {
5994+
if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) {
59955995
ZVAL_UNDEF(EX_VAR(opline->result.var));
59965996
FREE_OP2();
59975997
HANDLE_EXCEPTION();

Zend/zend_vm_execute.h

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)