Skip to content

Commit 0b0259a

Browse files
committed
Convert Implicitly nullable pure intersection types to DNF
If we don't then Reflection would give us a ReflectionIntersectionType even if the type is currently displayed as (X&Y)|null
1 parent e0d9a29 commit 0b0259a

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

Zend/zend_compile.c

+17-3
Original file line numberDiff line numberDiff line change
@@ -6527,10 +6527,24 @@ static zend_type zend_compile_typename(
65276527

65286528
ZEND_ASSERT(list->children == type_list->num_types);
65296529

6530-
ZEND_TYPE_SET_LIST(type, type_list);
65316530
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT;
6532-
/* Inform that the type list is an intersection type */
6533-
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_INTERSECTION_BIT;
6531+
/* An implicitly nullable intersection type needs to be converted to a DNF type */
6532+
if (force_allow_null) {
6533+
zend_type intersection_type = ZEND_TYPE_INIT_NONE(0);
6534+
ZEND_TYPE_SET_LIST(intersection_type, type_list);
6535+
ZEND_TYPE_FULL_MASK(intersection_type) |= _ZEND_TYPE_INTERSECTION_BIT;
6536+
6537+
zend_type_list *dnf_type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->children));
6538+
dnf_type_list->num_types = 1;
6539+
dnf_type_list->types[0] = intersection_type;
6540+
ZEND_TYPE_SET_LIST(type, dnf_type_list);
6541+
/* Inform that the type list is a DNF type */
6542+
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_UNION_BIT;
6543+
} else {
6544+
ZEND_TYPE_SET_LIST(type, type_list);
6545+
/* Inform that the type list is an intersection type */
6546+
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_INTERSECTION_BIT;
6547+
}
65346548
} else {
65356549
type = zend_compile_single_typename(ast);
65366550
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Intersection type which is implicitly nullable should be a DNF type in reflection
3+
--FILE--
4+
<?php
5+
6+
function dumpType(ReflectionType $rt, int $indent = 0) {
7+
$str_indent = str_repeat(' ', 2 * $indent);
8+
echo $str_indent . "Type $rt is " . $rt::class . ":\n";
9+
echo $str_indent . "Allows Null: " . json_encode($rt->allowsNull()) . "\n";
10+
foreach ($rt->getTypes() as $type) {
11+
if ($type instanceof ReflectionNamedType) {
12+
echo $str_indent . " Name: " . $type->getName() . "\n";
13+
echo $str_indent . " String: " . (string) $type . "\n";
14+
} else {
15+
dumpType($type, $indent+1);
16+
}
17+
}
18+
}
19+
20+
function foo(X&Y $foo = null) {
21+
var_dump($foo);
22+
}
23+
24+
dumpType((new ReflectionFunction('foo'))->getParameters()[0]->getType());
25+
26+
?>
27+
--EXPECT--
28+
Type (X&Y)|null is ReflectionUnionType:
29+
Allows Null: true
30+
Type X&Y is ReflectionIntersectionType:
31+
Allows Null: false
32+
Name: X
33+
String: X
34+
Name: Y
35+
String: Y
36+
Name: null
37+
String: null

0 commit comments

Comments
 (0)