Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f30e82f

Browse filesBrowse files
committed
Fix variance when child is union and parent is intersection
1 parent da5f184 commit f30e82f
Copy full SHA for f30e82f

File tree

4 files changed

+158
-1
lines changed
Filter options

4 files changed

+158
-1
lines changed
+24Lines 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
+23Lines 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
+25Lines 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

Copy file name to clipboardExpand all lines: Zend/zend_inheritance.c
+86-1Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.