Skip to content

Commit 6033e39

Browse files
committed
Fix variance when child is union and parent is intersection
1 parent 5e6b272 commit 6033e39

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

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

564614
/* For intersection types loop over the parent types first as a child
565615
* can add them */
566-
if (ZEND_TYPE_IS_INTERSECTION(proto_type) || ZEND_TYPE_IS_INTERSECTION(fe_type)) {
616+
if (ZEND_TYPE_IS_INTERSECTION(fe_type)
617+
|| (ZEND_TYPE_IS_INTERSECTION(proto_type) && !ZEND_TYPE_IS_UNION(fe_type))
618+
) {
567619
/* First try to check whether we can succeed without resolving anything */
568620
ZEND_TYPE_FOREACH(proto_type, single_type) {
569621
inheritance_status status;
@@ -593,6 +645,39 @@ static inheritance_status zend_perform_covariant_type_check(
593645
all_success = false;
594646
}
595647
} ZEND_TYPE_FOREACH_END();
648+
} else if (ZEND_TYPE_IS_INTERSECTION(proto_type) && ZEND_TYPE_IS_UNION(fe_type)) {
649+
/* Here each member of the child union must be a subtype of the intersection */
650+
651+
/* First try to check whether we can succeed without resolving anything */
652+
zend_type_list *child_union_types = ZEND_TYPE_LIST(fe_type);
653+
654+
ZEND_TYPE_LIST_FOREACH(child_union_types, single_type) {
655+
inheritance_status status;
656+
zend_string *fe_class_name;
657+
zend_class_entry *fe_ce = NULL;
658+
659+
if (ZEND_TYPE_HAS_NAME(*single_type)) {
660+
fe_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type));
661+
} else if (ZEND_TYPE_HAS_CE(*single_type)) {
662+
fe_ce = ZEND_TYPE_CE(*single_type);
663+
fe_class_name = fe_ce->name;
664+
} else {
665+
/* standard type */
666+
ZEND_ASSERT(0 && "This shouldn't happen yet");
667+
continue;
668+
}
669+
670+
status = zend_is_single_type_subtype_intersection(fe_scope,
671+
fe_class_name, fe_ce, proto_scope, proto_type,
672+
/* register_unresolved */ false);
673+
674+
if (status == INHERITANCE_ERROR) {
675+
return INHERITANCE_ERROR;
676+
}
677+
if (status != INHERITANCE_SUCCESS) {
678+
all_success = false;
679+
}
680+
} ZEND_TYPE_LIST_FOREACH_END();
596681
}
597682
/* Only union or single types both in parent and child */
598683
else {

0 commit comments

Comments
 (0)