Skip to content

Commit 2d30485

Browse files
committed
Fix variance when child is union and parent is intersection
1 parent f9c78be commit 2d30485

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
@@ -513,6 +513,56 @@ static inheritance_status zend_perform_covariant_class_type_check(
513513
return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR;
514514
}
515515

516+
/* checks that the child type (being unique) is a subtype of each member of the parent intersection */
517+
static inheritance_status zend_is_single_type_subtype_intersection(
518+
zend_class_entry *fe_scope, zend_string *fe_class_name,
519+
zend_class_entry *fe_ce, zend_class_entry *proto_scope,
520+
zend_type proto_type, bool register_unresolved
521+
) {
522+
bool have_unresolved = false;
523+
zend_type *single_type;
524+
zend_type_list *parent_intersection_types;
525+
526+
ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(proto_type));
527+
528+
parent_intersection_types = ZEND_TYPE_LIST(proto_type);
529+
530+
ZEND_TYPE_LIST_FOREACH(parent_intersection_types, single_type) {
531+
zend_class_entry *proto_ce;
532+
zend_string *proto_class_name = NULL;
533+
if (ZEND_TYPE_HAS_NAME(*single_type)) {
534+
proto_class_name =
535+
resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type));
536+
if (zend_string_equals_ci(fe_class_name, proto_class_name)) {
537+
continue;
538+
}
539+
540+
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved);
541+
proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved);
542+
} else if (ZEND_TYPE_HAS_CE(*single_type)) {
543+
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved);
544+
proto_ce = ZEND_TYPE_CE(*single_type);
545+
} else {
546+
/* standard type cannot be part a subtype of an intersection type */
547+
ZEND_ASSERT(0 && "This shouldn't happen yet");
548+
continue;
549+
}
550+
551+
if (!fe_ce || !proto_ce) {
552+
have_unresolved = true;
553+
continue;
554+
}
555+
if (!unlinked_instanceof(fe_ce, proto_ce)) {
556+
return INHERITANCE_ERROR;
557+
}
558+
559+
track_class_dependency(fe_ce, fe_class_name);
560+
track_class_dependency(proto_ce, proto_class_name);
561+
} ZEND_TYPE_LIST_FOREACH_END();
562+
563+
return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_SUCCESS;
564+
}
565+
516566
static void register_unresolved_classes(zend_class_entry *scope, zend_type type) {
517567
zend_type *single_type;
518568
ZEND_TYPE_FOREACH(type, single_type) {
@@ -575,7 +625,9 @@ static inheritance_status zend_perform_covariant_type_check(
575625

576626
/* For intersection types loop over the parent types first as a child
577627
* can add them */
578-
if (ZEND_TYPE_IS_INTERSECTION(proto_type) || ZEND_TYPE_IS_INTERSECTION(fe_type)) {
628+
if (ZEND_TYPE_IS_INTERSECTION(fe_type)
629+
|| (ZEND_TYPE_IS_INTERSECTION(proto_type) && !ZEND_TYPE_IS_UNION(fe_type))
630+
) {
579631
/* First try to check whether we can succeed without resolving anything */
580632
ZEND_TYPE_FOREACH(proto_type, single_type) {
581633
inheritance_status status;
@@ -605,6 +657,39 @@ static inheritance_status zend_perform_covariant_type_check(
605657
all_success = false;
606658
}
607659
} ZEND_TYPE_FOREACH_END();
660+
} else if (ZEND_TYPE_IS_INTERSECTION(proto_type) && ZEND_TYPE_IS_UNION(fe_type)) {
661+
/* Here each member of the child union must be a subtype of the intersection */
662+
663+
/* First try to check whether we can succeed without resolving anything */
664+
zend_type_list *child_union_types = ZEND_TYPE_LIST(fe_type);
665+
666+
ZEND_TYPE_LIST_FOREACH(child_union_types, single_type) {
667+
inheritance_status status;
668+
zend_string *fe_class_name;
669+
zend_class_entry *fe_ce = NULL;
670+
671+
if (ZEND_TYPE_HAS_NAME(*single_type)) {
672+
fe_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type));
673+
} else if (ZEND_TYPE_HAS_CE(*single_type)) {
674+
fe_ce = ZEND_TYPE_CE(*single_type);
675+
fe_class_name = fe_ce->name;
676+
} else {
677+
/* standard type */
678+
ZEND_ASSERT(0 && "This shouldn't happen yet");
679+
continue;
680+
}
681+
682+
status = zend_is_single_type_subtype_intersection(fe_scope,
683+
fe_class_name, fe_ce, proto_scope, proto_type,
684+
/* register_unresolved */ false);
685+
686+
if (status == INHERITANCE_ERROR) {
687+
return INHERITANCE_ERROR;
688+
}
689+
if (status != INHERITANCE_SUCCESS) {
690+
all_success = false;
691+
}
692+
} ZEND_TYPE_LIST_FOREACH_END();
608693
}
609694
/* Only union or single types both in parent and child */
610695
else {

0 commit comments

Comments
 (0)