Skip to content

Commit f30e82f

Browse files
committed
Fix variance when child is union and parent is intersection
1 parent da5f184 commit f30e82f

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
@@ -507,6 +507,56 @@ static inheritance_status zend_perform_covariant_class_type_check(
507507
return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR;
508508
}
509509

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

553603
/* For intersection types loop over the parent types first as a child
554604
* can add them */
555-
if (ZEND_TYPE_IS_INTERSECTION(proto_type) || ZEND_TYPE_IS_INTERSECTION(fe_type)) {
605+
if (ZEND_TYPE_IS_INTERSECTION(fe_type)
606+
|| (ZEND_TYPE_IS_INTERSECTION(proto_type) && !ZEND_TYPE_IS_UNION(fe_type))
607+
) {
556608
/* First try to check whether we can succeed without resolving anything */
557609
ZEND_TYPE_FOREACH(proto_type, single_type) {
558610
inheritance_status status;
@@ -582,6 +634,39 @@ static inheritance_status zend_perform_covariant_type_check(
582634
all_success = false;
583635
}
584636
} ZEND_TYPE_FOREACH_END();
637+
} else if (ZEND_TYPE_IS_INTERSECTION(proto_type) && ZEND_TYPE_IS_UNION(fe_type)) {
638+
/* Here each member of the child union must be a subtype of the intersection */
639+
640+
/* First try to check whether we can succeed without resolving anything */
641+
zend_type_list *child_union_types = ZEND_TYPE_LIST(fe_type);
642+
643+
ZEND_TYPE_LIST_FOREACH(child_union_types, single_type) {
644+
inheritance_status status;
645+
zend_string *fe_class_name;
646+
zend_class_entry *fe_ce = NULL;
647+
648+
if (ZEND_TYPE_HAS_NAME(*single_type)) {
649+
fe_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type));
650+
} else if (ZEND_TYPE_HAS_CE(*single_type)) {
651+
fe_ce = ZEND_TYPE_CE(*single_type);
652+
fe_class_name = fe_ce->name;
653+
} else {
654+
/* standard type */
655+
ZEND_ASSERT(0 && "This shouldn't happen yet");
656+
continue;
657+
}
658+
659+
status = zend_is_single_type_subtype_intersection(fe_scope,
660+
fe_class_name, fe_ce, proto_scope, proto_type,
661+
/* register_unresolved */ false);
662+
663+
if (status == INHERITANCE_ERROR) {
664+
return INHERITANCE_ERROR;
665+
}
666+
if (status != INHERITANCE_SUCCESS) {
667+
all_success = false;
668+
}
669+
} ZEND_TYPE_LIST_FOREACH_END();
585670
}
586671
/* Only union or single types both in parent and child */
587672
else {

0 commit comments

Comments
 (0)