Skip to content

Commit c1af362

Browse files
committed
Support invariant bound types for associated types
1 parent 45798d9 commit c1af362

File tree

6 files changed

+139
-8
lines changed

6 files changed

+139
-8
lines changed

Zend/tests/type_declarations/associated/associated_001.phpt

+19-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,25 @@ interface I {
88
public function foo(T $param): T;
99
}
1010

11-
class C implements I {
12-
public function foo(string $param): string {}
11+
class CS implements I {
12+
public function foo(string $param): string {
13+
return $param . '!';
14+
}
1315
}
1416

17+
class CI implements I {
18+
public function foo(int $param): int {
19+
return $param + 42;
20+
}
21+
}
22+
23+
$cs = new CS();
24+
var_dump($cs->foo("Hello"));
25+
26+
$ci = new CI();
27+
var_dump($ci->foo(5));
28+
1529
?>
16-
--EXPECTF--
17-
Fatal error: Declaration of C::foo(string $param): string must be compatible with I::foo(T $param): T in %s on line %d
30+
--EXPECT--
31+
string(6) "Hello!"
32+
int(47)

Zend/tests/type_declarations/associated/multiple_associated_type.phpt

+58-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,65 @@ interface I {
1010
public function get(K $key): V;
1111
}
1212

13-
class C implements I {
14-
public function set(int $key, string $value): void {}
15-
public function get(int $key): string {}
13+
class C1 implements I {
14+
public array $a = [];
15+
public function set(int $key, string $value): void {
16+
$this->a[$key] = $value . '!';
17+
}
18+
public function get(int $key): string {
19+
return $this->a[$key];
20+
}
1621
}
1722

23+
class C2 implements I {
24+
public array $a = [];
25+
public function set(string $key, object $value): void {
26+
$this->a[$key] = $value;
27+
}
28+
public function get(string $key): object {
29+
return $this->a[$key];
30+
}
31+
}
32+
33+
$c1 = new C1();
34+
$c1->set(5, "Hello");
35+
var_dump($c1->a);
36+
var_dump($c1->get(5));
37+
38+
$c2 = new C2();
39+
$c2->set('C1', $c1);
40+
var_dump($c2->a);
41+
var_dump($c2->get('C1'));
42+
43+
try {
44+
$c1->set('blah', "Hello");
45+
} catch (\Throwable $e) {
46+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
47+
}
48+
49+
1850
?>
1951
--EXPECTF--
20-
Fatal error: Declaration of C::set(int $key, string $value): void must be compatible with I::set(K $key, V $value): void in %s on line %d
52+
array(1) {
53+
[5]=>
54+
string(6) "Hello!"
55+
}
56+
string(6) "Hello!"
57+
array(1) {
58+
["C1"]=>
59+
object(C1)#1 (1) {
60+
["a"]=>
61+
array(1) {
62+
[5]=>
63+
string(6) "Hello!"
64+
}
65+
}
66+
}
67+
object(C1)#1 (1) {
68+
["a"]=>
69+
array(1) {
70+
[5]=>
71+
string(6) "Hello!"
72+
}
73+
}
74+
TypeError: C1::set(): Argument #1 ($key) must be of type int, string given, called in %s on line %d

Zend/zend.c

+1
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{
732732

733733
compiler_globals->script_encoding_list = NULL;
734734
compiler_globals->current_linking_class = NULL;
735+
compiler_globals->bound_associated_types = NULL;
735736

736737
/* Map region is going to be created and resized at run-time. */
737738
compiler_globals->map_ptr_real_base = NULL;

Zend/zend_compile.c

+2
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ void init_compiler(void) /* {{{ */
463463
CG(delayed_autoloads) = NULL;
464464
CG(unlinked_uses) = NULL;
465465
CG(current_linking_class) = NULL;
466+
CG(bound_associated_types) = NULL;
466467
}
467468
/* }}} */
468469

@@ -491,6 +492,7 @@ void shutdown_compiler(void) /* {{{ */
491492
CG(unlinked_uses) = NULL;
492493
}
493494
CG(current_linking_class) = NULL;
495+
ZEND_ASSERT(CG(bound_associated_types) == NULL);
494496
}
495497
/* }}} */
496498

Zend/zend_globals.h

+2
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ struct _zend_compiler_globals {
151151
HashTable *delayed_autoloads;
152152
HashTable *unlinked_uses;
153153
zend_class_entry *current_linking_class;
154+
/* Those are initialized and destroyed by zend_do_inheritance_ex() */
155+
HashTable *bound_associated_types;
154156

155157
uint32_t rtd_key_counter;
156158

Zend/zend_inheritance.c

+57
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,42 @@ static inheritance_status zend_is_intersection_subtype_of_type(
675675
return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
676676
}
677677

678+
ZEND_API inheritance_status zend_perform_covariant_type_check(
679+
zend_class_entry *fe_scope, const zend_type *fe_type_ptr,
680+
zend_class_entry *proto_scope, const zend_type *proto_type_ptr);
681+
682+
static inheritance_status zend_is_type_subtype_of_associated_type(
683+
zend_class_entry *concrete_scope,
684+
const zend_type *concrete_type_ptr,
685+
zend_class_entry *associated_type_scope,
686+
const zend_type *associated_type_ptr
687+
) {
688+
const zend_type associated_type = *associated_type_ptr;
689+
690+
ZEND_ASSERT(CG(bound_associated_types) && "Have associated type");
691+
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(associated_type));
692+
693+
zend_string *associated_type_name = ZEND_TYPE_NAME(associated_type);
694+
const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_associated_types), associated_type_name);
695+
if (bound_type_ptr == NULL) {
696+
/* Loosing const qualifier here is OK because this hashtable never frees or does anything with the value */
697+
zend_hash_add_new_ptr(CG(bound_associated_types), associated_type_name, (void*)concrete_type_ptr);
698+
return INHERITANCE_SUCCESS;
699+
} else {
700+
/* Associated type must be invariant */
701+
const inheritance_status sub_type_status = zend_perform_covariant_type_check(
702+
concrete_scope, concrete_type_ptr, associated_type_scope, bound_type_ptr);
703+
const inheritance_status super_type_status = zend_perform_covariant_type_check(
704+
associated_type_scope, bound_type_ptr, concrete_scope, concrete_type_ptr);
705+
706+
if (sub_type_status != super_type_status) {
707+
return INHERITANCE_ERROR;
708+
} else {
709+
return sub_type_status;
710+
}
711+
}
712+
}
713+
678714
ZEND_API inheritance_status zend_perform_covariant_type_check(
679715
zend_class_entry *fe_scope, const zend_type *fe_type_ptr,
680716
zend_class_entry *proto_scope, const zend_type *proto_type_ptr)
@@ -690,6 +726,17 @@ ZEND_API inheritance_status zend_perform_covariant_type_check(
690726
return INHERITANCE_SUCCESS;
691727
}
692728

729+
/* If we check for concrete return type */
730+
if (ZEND_TYPE_IS_ASSOCIATED(proto_type)) {
731+
return zend_is_type_subtype_of_associated_type(
732+
fe_scope, fe_type_ptr, proto_scope, proto_type_ptr);
733+
}
734+
/* If we check for concrete parameter type */
735+
if (ZEND_TYPE_IS_ASSOCIATED(fe_type)) {
736+
return zend_is_type_subtype_of_associated_type(
737+
proto_scope, proto_type_ptr, fe_scope, fe_type_ptr);
738+
}
739+
693740
/* Builtin types may be removed, but not added */
694741
uint32_t fe_type_mask = ZEND_TYPE_PURE_MASK(fe_type);
695742
uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type);
@@ -2174,6 +2221,11 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry *
21742221
ZEND_INHERITANCE_RESET_CHILD_OVERRIDE;
21752222
}
21762223

2224+
if (iface->associated_types) {
2225+
HashTable *ht = emalloc(sizeof(HashTable));
2226+
zend_hash_init(ht, zend_hash_num_elements(iface->associated_types), NULL, NULL, false);
2227+
CG(bound_associated_types) = ht;
2228+
}
21772229
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) {
21782230
do_inherit_iface_constant(key, c, ce, iface);
21792231
} ZEND_HASH_FOREACH_END();
@@ -2195,6 +2247,11 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry *
21952247
if (iface->num_interfaces) {
21962248
zend_do_inherit_interfaces(ce, iface);
21972249
}
2250+
if (CG(bound_associated_types)) {
2251+
zend_hash_destroy(CG(bound_associated_types));
2252+
efree(CG(bound_associated_types));
2253+
CG(bound_associated_types) = NULL;
2254+
}
21982255
}
21992256
/* }}} */
22002257

0 commit comments

Comments
 (0)