Skip to content

Commit f72c2e6

Browse files
committed
Support invariant bound types
1 parent 263f12a commit f72c2e6

File tree

6 files changed

+142
-14
lines changed

6 files changed

+142
-14
lines changed

Diff for: 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)

Diff for: Zend/tests/type_declarations/associated/multiple_associated_type.phpt

+59-5
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
?>
19-
--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
51+
--EXPECT--
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

Diff for: 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;

Diff for: 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

Diff for: 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

Diff for: Zend/zend_inheritance.c

+59-5
Original file line numberDiff line numberDiff line change
@@ -675,20 +675,40 @@ 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+
678682
static inheritance_status zend_is_type_subtype_of_associated_type(
679683
zend_class_entry *concrete_scope,
680684
const zend_type *concrete_type_ptr,
681-
const zend_type *associated_type_ptr,
682-
HashTable *associated_types
685+
zend_class_entry *associated_type_scope,
686+
const zend_type *associated_type_ptr
683687
) {
684688
const zend_type associated_type = *associated_type_ptr;
685-
const zend_type concrete_type = *concrete_type_ptr;
686689

690+
ZEND_ASSERT(CG(bound_associated_types) && "Have associated type");
687691
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(associated_type));
688692

689693
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+
concrete_scope, concrete_type_ptr, associated_type_scope, bound_type_ptr);
690705

691-
return INHERITANCE_ERROR;
706+
if (sub_type_status != super_type_status) {
707+
return INHERITANCE_ERROR;
708+
} else {
709+
return sub_type_status;
710+
}
711+
}
692712
}
693713

694714
ZEND_API inheritance_status zend_perform_covariant_type_check(
@@ -706,9 +726,15 @@ ZEND_API inheritance_status zend_perform_covariant_type_check(
706726
return INHERITANCE_SUCCESS;
707727
}
708728

729+
/* If we check for concrete return type */
709730
if (ZEND_TYPE_IS_ASSOCIATED(proto_type)) {
710731
return zend_is_type_subtype_of_associated_type(
711-
fe_scope, fe_type_ptr, proto_type_ptr, NULL);
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);
712738
}
713739

714740
/* Builtin types may be removed, but not added */
@@ -1852,6 +1878,10 @@ static void zend_link_hooked_object_iter(zend_class_entry *ce) {
18521878
}
18531879
#endif
18541880

1881+
static void zend_bound_associated_table_ht_dtor(zval *val) {
1882+
/* NO OP as we only use it to be able to refer and save pointers to zend_types */
1883+
}
1884+
18551885
ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, bool checked) /* {{{ */
18561886
{
18571887
zend_property_info *property_info;
@@ -1895,6 +1925,14 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
18951925
ce->default_object_handlers = parent_ce->default_object_handlers;
18961926
ce->ce_flags |= ZEND_ACC_RESOLVED_PARENT;
18971927

1928+
// TODO Add associated_types HT for bound type checking
1929+
if (parent_ce->num_associated_types) {
1930+
printf("Hello\n");
1931+
HashTable *ht = emalloc(sizeof(HashTable));
1932+
zend_hash_init(ht, parent_ce->num_associated_types, NULL, zend_bound_associated_table_ht_dtor, false);
1933+
CG(bound_associated_types) = ht;
1934+
}
1935+
18981936
/* Inherit properties */
18991937
if (parent_ce->default_properties_count) {
19001938
zval *src, *dst, *end;
@@ -2063,6 +2101,12 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
20632101
}
20642102
}
20652103
ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_HAS_READONLY_PROPS | ZEND_ACC_USE_GUARDS | ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES);
2104+
2105+
if (CG(bound_associated_types)) {
2106+
zend_hash_destroy(CG(bound_associated_types));
2107+
efree(CG(bound_associated_types));
2108+
CG(bound_associated_types) = NULL;
2109+
}
20662110
}
20672111
/* }}} */
20682112

@@ -2195,6 +2239,11 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry *
21952239
ZEND_INHERITANCE_RESET_CHILD_OVERRIDE;
21962240
}
21972241

2242+
if (iface->num_associated_types) {
2243+
HashTable *ht = emalloc(sizeof(HashTable));
2244+
zend_hash_init(ht, iface->num_associated_types, NULL, zend_bound_associated_table_ht_dtor, false);
2245+
CG(bound_associated_types) = ht;
2246+
}
21982247
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) {
21992248
do_inherit_iface_constant(key, c, ce, iface);
22002249
} ZEND_HASH_FOREACH_END();
@@ -2216,6 +2265,11 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry *
22162265
if (iface->num_interfaces) {
22172266
zend_do_inherit_interfaces(ce, iface);
22182267
}
2268+
if (CG(bound_associated_types)) {
2269+
zend_hash_destroy(CG(bound_associated_types));
2270+
efree(CG(bound_associated_types));
2271+
CG(bound_associated_types) = NULL;
2272+
}
22192273
}
22202274
/* }}} */
22212275

0 commit comments

Comments
 (0)