Skip to content

Commit 61ee393

Browse files
committed
Fix variance when child is union and parent is intersection
1 parent 552f513 commit 61ee393

File tree

4 files changed

+158
-1
lines changed

4 files changed

+158
-1
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 1
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
9+
class TestOne implements X, Y {}
10+
class TestTwo implements X {}
11+
12+
interface A
13+
{
14+
public function foo(): X&Y;
15+
}
16+
17+
interface B extends A
18+
{
19+
public function foo(): TestOne|TestTwo;
20+
}
21+
22+
?>
23+
--EXPECTF--
24+
Fatal error: Declaration of B::foo(): TestOne|TestTwo must be compatible with A::foo(): X&Y in %s on line %d
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 2
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
9+
class TestOne implements X, Y {}
10+
11+
interface A
12+
{
13+
public function foo(): X&Y;
14+
}
15+
16+
interface B extends A
17+
{
18+
public function foo(): TestOne|int;
19+
}
20+
21+
?>
22+
--EXPECTF--
23+
Fatal error: Declaration of B::foo(): TestOne|int must be compatible with A::foo(): X&Y in %s on line %d
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 2
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
interface Z extends Y {}
9+
10+
class TestOne implements X, Z {}
11+
class TestTwo implements X, Y {}
12+
13+
interface A
14+
{
15+
public function foo(): X&Z;
16+
}
17+
18+
interface B extends A
19+
{
20+
public function foo(): TestOne|TestTwo;
21+
}
22+
23+
?>
24+
--EXPECTF--
25+
Fatal error: Declaration of B::foo(): TestOne|TestTwo must be compatible with A::foo(): X&Z in %s on line %d

Zend/zend_inheritance.c

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,56 @@ static inheritance_status zend_perform_covariant_class_type_check(
482482
return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR;
483483
}
484484

485+
/* checks that the child type (being unique) is a subtype of each member of the parent intersection */
486+
static inheritance_status zend_is_single_type_subtype_intersection(
487+
zend_class_entry *fe_scope, zend_string *fe_class_name,
488+
zend_class_entry *fe_ce, zend_class_entry *proto_scope,
489+
zend_type proto_type, bool register_unresolved
490+
) {
491+
bool have_unresolved = false;
492+
zend_type *single_type;
493+
zend_type_list *parent_intersection_types;
494+
495+
ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(proto_type));
496+
497+
parent_intersection_types = ZEND_TYPE_LIST(proto_type);
498+
499+
ZEND_TYPE_LIST_FOREACH(parent_intersection_types, single_type) {
500+
zend_class_entry *proto_ce;
501+
zend_string *proto_class_name = NULL;
502+
if (ZEND_TYPE_HAS_NAME(*single_type)) {
503+
proto_class_name =
504+
resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type));
505+
if (zend_string_equals_ci(fe_class_name, proto_class_name)) {
506+
continue;
507+
}
508+
509+
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved);
510+
proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved);
511+
} else if (ZEND_TYPE_HAS_CE(*single_type)) {
512+
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved);
513+
proto_ce = ZEND_TYPE_CE(*single_type);
514+
} else {
515+
/* standard type cannot be part a subtype of an intersection type */
516+
ZEND_ASSERT(0 && "This shouldn't happen yet");
517+
continue;
518+
}
519+
520+
if (!fe_ce || !proto_ce) {
521+
have_unresolved = true;
522+
continue;
523+
}
524+
if (!unlinked_instanceof(fe_ce, proto_ce)) {
525+
return INHERITANCE_ERROR;
526+
}
527+
528+
track_class_dependency(fe_ce, fe_class_name);
529+
track_class_dependency(proto_ce, proto_class_name);
530+
} ZEND_TYPE_LIST_FOREACH_END();
531+
532+
return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_SUCCESS;
533+
}
534+
485535
static inheritance_status zend_perform_covariant_type_check(
486536
zend_class_entry *fe_scope, zend_type fe_type,
487537
zend_class_entry *proto_scope, zend_type proto_type) /* {{{ */
@@ -527,7 +577,9 @@ static inheritance_status zend_perform_covariant_type_check(
527577

528578
/* For intersection types loop over the parent types first as a child
529579
* can add them */
530-
if (ZEND_TYPE_IS_INTERSECTION(proto_type) || ZEND_TYPE_IS_INTERSECTION(fe_type)) {
580+
if (ZEND_TYPE_IS_INTERSECTION(fe_type)
581+
|| (ZEND_TYPE_IS_INTERSECTION(proto_type) && !ZEND_TYPE_IS_UNION(fe_type))
582+
) {
531583
/* First try to check whether we can succeed without resolving anything */
532584
ZEND_TYPE_FOREACH(proto_type, single_type) {
533585
inheritance_status status;
@@ -557,6 +609,39 @@ static inheritance_status zend_perform_covariant_type_check(
557609
all_success = false;
558610
}
559611
} ZEND_TYPE_FOREACH_END();
612+
} else if (ZEND_TYPE_IS_INTERSECTION(proto_type) && ZEND_TYPE_IS_UNION(fe_type)) {
613+
/* Here each member of the child union must be a subtype of the intersection */
614+
615+
/* First try to check whether we can succeed without resolving anything */
616+
zend_type_list *child_union_types = ZEND_TYPE_LIST(fe_type);
617+
618+
ZEND_TYPE_LIST_FOREACH(child_union_types, single_type) {
619+
inheritance_status status;
620+
zend_string *fe_class_name;
621+
zend_class_entry *fe_ce = NULL;
622+
623+
if (ZEND_TYPE_HAS_NAME(*single_type)) {
624+
fe_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type));
625+
} else if (ZEND_TYPE_HAS_CE(*single_type)) {
626+
fe_ce = ZEND_TYPE_CE(*single_type);
627+
fe_class_name = fe_ce->name;
628+
} else {
629+
/* standard type */
630+
ZEND_ASSERT(0 && "This shouldn't happen yet");
631+
continue;
632+
}
633+
634+
status = zend_is_single_type_subtype_intersection(fe_scope,
635+
fe_class_name, fe_ce, proto_scope, proto_type,
636+
/* register_unresolved */ false);
637+
638+
if (status == INHERITANCE_ERROR) {
639+
return INHERITANCE_ERROR;
640+
}
641+
if (status != INHERITANCE_SUCCESS) {
642+
all_success = false;
643+
}
644+
} ZEND_TYPE_LIST_FOREACH_END();
560645
}
561646
/* Only union or single types both in parent and child */
562647
else {

0 commit comments

Comments
 (0)