Skip to content

Commit db58d12

Browse files
committed
Implement delayed variance checks and other fixes
1 parent cf0a05e commit db58d12

19 files changed

+159
-55
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
@@ -1413,7 +1413,7 @@ static zend_result update_property(zval *val, zend_property_info *prop_info) {
14131413
return zval_update_constant_ex(val, prop_info->ce);
14141414
}
14151415

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

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

1433-
if (UNEXPECTED(!zend_verify_class_constant_type(c, &tmp))) {
1433+
if (UNEXPECTED(!zend_verify_class_constant_type(c, name, &tmp))) {
14341434
zval_ptr_dtor(&tmp);
14351435
return FAILURE;
14361436
}
@@ -1499,7 +1499,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /
14991499
}
15001500

15011501
val = &c->value;
1502-
if (UNEXPECTED(zend_update_class_constant(c, c->ce) != SUCCESS)) {
1502+
if (UNEXPECTED(zend_update_class_constant(c, name, c->ce) != SUCCESS)) {
15031503
return FAILURE;
15041504
}
15051505
}
@@ -4594,7 +4594,6 @@ ZEND_API zend_class_constant *zend_declare_typed_class_constant(zend_class_entry
45944594
c->doc_comment = doc_comment;
45954595
c->attributes = NULL;
45964596
c->ce = ce;
4597-
c->name = name;
45984597
c->type = type;
45994598

46004599
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
@@ -440,7 +440,7 @@ ZEND_API void zend_declare_class_constant_double(zend_class_entry *ce, const cha
440440
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);
441441
ZEND_API void zend_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value);
442442

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

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
@@ -373,7 +373,7 @@ ZEND_API zval *zend_get_class_constant_ex(zend_string *class_name, zend_string *
373373
}
374374

375375
MARK_CONSTANT_VISITED(ret_constant);
376-
ret = zend_update_class_constant(c, c->ce);
376+
ret = zend_update_class_constant(c, constant_name, c->ce);
377377
RESET_CONSTANT_VISITED(ret_constant);
378378

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

Zend/zend_execute.c

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

831-
ZEND_COLD zend_never_inline void zend_verify_class_constant_type_error(const zend_class_constant *c, const zval *constant)
831+
ZEND_COLD zend_never_inline void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant)
832832
{
833833
zend_string *type_str = zend_type_to_string(c->type);
834834

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

838838
zend_string_release(type_str);
839839
}
@@ -979,7 +979,7 @@ if (ZEND_TYPE_HAS_LIST(member_type)) {
979979
return false;
980980
}
981981
} else if ((ZEND_TYPE_PURE_MASK(member_type) & MAY_BE_STATIC)) {
982-
return instanceof_function(value_ce, scope);
982+
return value_ce == scope;
983983
} else {
984984
const zend_class_entry *ce = zend_ce_from_type(scope, &member_type);
985985
return ce && instanceof_function(value_ce, ce);
@@ -1492,10 +1492,10 @@ static zend_always_inline bool zend_check_class_constant_type(zend_class_constan
14921492
return zend_verify_scalar_type_hint(type_mask, constant, true, false);
14931493
}
14941494

1495-
ZEND_API bool zend_never_inline zend_verify_class_constant_type(zend_class_constant *c, zval *constant)
1495+
ZEND_API bool zend_never_inline zend_verify_class_constant_type(zend_class_constant *c, const zend_string *name, zval *constant)
14961496
{
14971497
if (!zend_check_class_constant_type(c, constant)) {
1498-
zend_verify_class_constant_type_error(c, constant);
1498+
zend_verify_class_constant_type_error(c, name, constant);
14991499
return 0;
15001500
}
15011501

Zend/zend_execute.h

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

468468

469-
ZEND_API bool zend_verify_class_constant_type(zend_class_constant *c, zval *constant);
470-
ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant *c, const zval *constant);
469+
ZEND_API bool zend_verify_class_constant_type(zend_class_constant *c, const zend_string *name, zval *constant);
470+
ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant);
471471

472472
ZEND_API bool zend_verify_property_type(const zend_property_info *info, zval *property, bool strict);
473473
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)) {
@@ -2555,7 +2573,8 @@ typedef struct {
25552573
enum {
25562574
OBLIGATION_DEPENDENCY,
25572575
OBLIGATION_COMPATIBILITY,
2558-
OBLIGATION_PROPERTY_COMPATIBILITY
2576+
OBLIGATION_PROPERTY_COMPATIBILITY,
2577+
OBLIGATION_CLASS_CONSTANT_COMPATIBILITY
25592578
} type;
25602579
union {
25612580
zend_class_entry *dependency_ce;
@@ -2571,6 +2590,11 @@ typedef struct {
25712590
const zend_property_info *parent_prop;
25722591
const zend_property_info *child_prop;
25732592
};
2593+
struct {
2594+
const zend_string *const_name;
2595+
const zend_class_constant *parent_const;
2596+
const zend_class_constant *child_const;
2597+
};
25742598
};
25752599
} variance_obligation;
25762600

@@ -2646,6 +2670,18 @@ static void add_property_compatibility_obligation(
26462670
zend_hash_next_index_insert_ptr(obligations, obligation);
26472671
}
26482672

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

26512687
static void check_variance_obligation(variance_obligation *obligation) {
@@ -2669,13 +2705,19 @@ static void check_variance_obligation(variance_obligation *obligation) {
26692705
&obligation->parent_fn, obligation->parent_scope, status);
26702706
}
26712707
/* Either the compatibility check was successful or only threw a warning. */
2672-
} else {
2673-
ZEND_ASSERT(obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY);
2708+
} else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) {
26742709
inheritance_status status =
26752710
property_types_compatible(obligation->parent_prop, obligation->child_prop);
26762711
if (status != INHERITANCE_SUCCESS) {
26772712
emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop);
26782713
}
2714+
} else {
2715+
ZEND_ASSERT(obligation->type == OBLIGATION_CLASS_CONSTANT_COMPATIBILITY);
2716+
inheritance_status status =
2717+
class_constant_types_compatible(obligation->parent_const, obligation->child_const);
2718+
if (status != INHERITANCE_SUCCESS) {
2719+
emit_incompatible_class_constant_error(obligation->child_const, obligation->parent_const, obligation->const_name);
2720+
}
26792721
}
26802722
}
26812723

@@ -3139,6 +3181,7 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
31393181
zend_string *key;
31403182
zend_function *parent_func;
31413183
zend_property_info *parent_info;
3184+
zend_class_constant *parent_const;
31423185
inheritance_status overall_status = INHERITANCE_SUCCESS;
31433186

31443187
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, parent_func) {
@@ -3177,6 +3220,25 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
31773220
}
31783221
} ZEND_HASH_FOREACH_END();
31793222

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

Zend/zend_vm_def.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5955,7 +5955,7 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
59555955
}
59565956
}
59575957
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
5958-
if (UNEXPECTED(zend_update_class_constant(c, c->ce) != SUCCESS)) {
5958+
if (UNEXPECTED(zend_update_class_constant(c, constant_name, c->ce) != SUCCESS)) {
59595959
ZVAL_UNDEF(EX_VAR(opline->result.var));
59605960
FREE_OP2();
59615961
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)