Skip to content

Commit 2baed8a

Browse files
committed
Add variance checking for constrained associated type
1 parent d3b2cc0 commit 2baed8a

File tree

3 files changed

+52
-3
lines changed

3 files changed

+52
-3
lines changed

Zend/tests/type_declarations/associated/associated_type_with_constraint_failed.phpt

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ class C implements I {
1313
}
1414

1515
?>
16-
--EXPECT--
16+
--EXPECTF--
17+
Fatal error: Declaration of C::foo(float $param): float must be compatible with I::foo(T<string|int> $param): T<string|int> in %s on line %d

Zend/zend_compile.c

+40-2
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,12 @@ void shutdown_compiler(void) /* {{{ */
494494
CG(unlinked_uses) = NULL;
495495
}
496496
CG(current_linking_class) = NULL;
497-
ZEND_ASSERT(CG(bound_associated_types) == NULL);
497+
/* This can happen during a fatal error */
498+
if (CG(bound_associated_types)) {
499+
zend_hash_destroy(CG(bound_associated_types));
500+
FREE_HASHTABLE(CG(bound_associated_types));
501+
CG(bound_associated_types) = NULL;
502+
}
498503
}
499504
/* }}} */
500505

@@ -1436,6 +1441,26 @@ static zend_string *add_intersection_type(zend_string *str,
14361441
return str;
14371442
}
14381443

1444+
static zend_string *add_associated_type(zend_string *associated_type, zend_class_entry *scope)
1445+
{
1446+
const zend_type *constraint = zend_hash_find_ptr(scope->associated_types, associated_type);
1447+
ZEND_ASSERT(constraint != NULL);
1448+
1449+
zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope);
1450+
1451+
size_t len = ZSTR_LEN(associated_type) + ZSTR_LEN(constraint_type_str) + strlen("<>");
1452+
zend_string *result = zend_string_alloc(len, 0);
1453+
1454+
memcpy(ZSTR_VAL(result), ZSTR_VAL(associated_type), ZSTR_LEN(associated_type));
1455+
ZSTR_VAL(result)[ZSTR_LEN(associated_type)] = '<';
1456+
memcpy(ZSTR_VAL(result) + ZSTR_LEN(associated_type) + 1, ZSTR_VAL(constraint_type_str), ZSTR_LEN(constraint_type_str));
1457+
ZSTR_VAL(result)[len-1] = '>';
1458+
ZSTR_VAL(result)[len] = '\0';
1459+
1460+
zend_string_release(constraint_type_str);
1461+
return result;
1462+
}
1463+
14391464
zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope) {
14401465
zend_string *str = NULL;
14411466

@@ -1459,6 +1484,8 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry
14591484
str = add_type_string(str, resolved, /* is_intersection */ false);
14601485
zend_string_release(resolved);
14611486
} ZEND_TYPE_LIST_FOREACH_END();
1487+
} else if (ZEND_TYPE_IS_ASSOCIATED(type)) {
1488+
str = add_associated_type(ZEND_TYPE_NAME(type), scope);
14621489
} else if (ZEND_TYPE_HAS_NAME(type)) {
14631490
str = resolve_class_name(ZEND_TYPE_NAME(type), scope);
14641491
}
@@ -9040,12 +9067,18 @@ static void zend_compile_use_trait(zend_ast *ast) /* {{{ */
90409067
static void zend_associated_table_ht_dtor(zval *val) {
90419068
/* NO OP as we only use it to be able to refer and save pointers to zend_types */
90429069
// TODO do we actually want to store copies of types?
9070+
zend_type *associated_type = Z_PTR_P(val);
9071+
if (associated_type != &zend_mixed_type) {
9072+
zend_type_release(*associated_type, false);
9073+
efree(associated_type);
9074+
}
90439075
}
90449076

90459077
static void zend_compile_associated_type(zend_ast *ast) {
90469078
zend_class_entry *ce = CG(active_class_entry);
90479079
HashTable *associated_types = ce->associated_types;
90489080
zend_ast *name_ast = ast->child[0];
9081+
zend_ast *type_ast = ast->child[1];
90499082
zend_string *name = zend_ast_get_str(name_ast);
90509083

90519084
if ((ce->ce_flags & ZEND_ACC_INTERFACE) == 0) {
@@ -9065,7 +9098,12 @@ static void zend_compile_associated_type(zend_ast *ast) {
90659098
"Cannot have two associated types with the same name \"%s\"", ZSTR_VAL(name));
90669099
}
90679100

9068-
zend_hash_add_new_ptr(associated_types, name, (void*) &zend_mixed_type);
9101+
if (type_ast != NULL) {
9102+
zend_type type = zend_compile_typename(type_ast);
9103+
zend_hash_add_new_mem(associated_types, name, &type, sizeof(type));
9104+
} else {
9105+
zend_hash_add_new_ptr(associated_types, name, (void*) &zend_mixed_type);
9106+
}
90699107
}
90709108

90719109
static void zend_compile_implements(zend_ast *ast) /* {{{ */

Zend/zend_inheritance.c

+10
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,16 @@ static inheritance_status zend_is_type_subtype_of_associated_type(
693693
zend_string *associated_type_name = ZEND_TYPE_NAME(associated_type);
694694
const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_associated_types), associated_type_name);
695695
if (bound_type_ptr == NULL) {
696+
const zend_type *constraint = zend_hash_find_ptr(associated_type_scope->associated_types, associated_type_name);
697+
ZEND_ASSERT(constraint != NULL);
698+
/* Check that the provided type is a subtype of the constraint */
699+
const inheritance_status status = zend_perform_covariant_type_check(
700+
concrete_scope, concrete_type_ptr,
701+
associated_type_scope, constraint);
702+
if (status != INHERITANCE_SUCCESS) {
703+
return status;
704+
}
705+
696706
/* Loosing const qualifier here is OK because this hashtable never frees or does anything with the value */
697707
zend_hash_add_new_ptr(CG(bound_associated_types), associated_type_name, (void*)concrete_type_ptr);
698708
return INHERITANCE_SUCCESS;

0 commit comments

Comments
 (0)