Skip to content

Commit 014865b

Browse files
Make intersection types nullable
1 parent 28f6a2b commit 014865b

15 files changed

+219
-32
lines changed

Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class A {
2020
public function method2(X $a): X&Y {
2121
return new TestParent();
2222
}
23+
public function method3(?X&Y $a): ?X&Y {
24+
return $a;
25+
}
2326
}
2427

2528
$tp = new TestParent();
@@ -42,7 +45,10 @@ $r = $o->method1($tc);
4245
var_dump($r);
4346
$r = $o->method2($tc);
4447
var_dump($r);
45-
48+
$r = $o->method3($tc);
49+
var_dump($r);
50+
$r = $o->method3(null);
51+
var_dump($r);
4652

4753
?>
4854
--EXPECTF--
@@ -55,3 +61,6 @@ object(TestChild)#%d (0) {
5561
}
5662
object(TestParent)#%d (0) {
5763
}
64+
object(TestChild)#%d (0) {
65+
}
66+
NULL
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
Bug #81268 Wrong message when using null as a default value for intersection types
2+
Bug #81268 Message when using null as a default value for intersection types
33
--FILE--
44
<?php
55

@@ -9,4 +9,4 @@ class Test {
99

1010
?>
1111
--EXPECTF--
12-
Fatal error: Cannot use null as default value for property Test::$y of type X&Y in %s on line %d
12+
Fatal error: Default value for property of type X&Y may not be null. Use the nullable type ?X&Y to allow null default value in %s on line %d
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
--TEST--
2-
Intersection types cannot be implicitly nullable
2+
Intersection types can be implicitly nullable as the others
33
--FILE--
44
<?php
55

6-
function foo(X&Y $foo = null) {}
6+
function foo(X&Y $foo = null): { return $foo; }
7+
8+
var_dump(foo());
79

810
?>
9-
--EXPECTF--
10-
Fatal error: Cannot use null as default value for parameter $foo of type X&Y in %s on line %d
11+
--EXPECT--
12+
NULL

Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt

Lines changed: 0 additions & 10 deletions
This file was deleted.

Zend/tests/type_declarations/intersection_types/parameter.phpt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ interface B {}
99
class Foo implements A, B {}
1010
class Bar implements A {}
1111

12-
function foo(A&B $bar) {
12+
function foo(?A&B $bar) {
1313
var_dump($bar);
1414
}
1515

16+
foo(null);
1617
foo(new Foo());
1718

1819
try {
@@ -23,6 +24,7 @@ try {
2324

2425
?>
2526
--EXPECTF--
27+
NULL
2628
object(Foo)#1 (0) {
2729
}
28-
foo(): Argument #1 ($bar) must be of type A&B, Bar given, called in %s on line %d
30+
foo(): Argument #1 ($bar) must be of type ?A&B, Bar given, called in %s on line %d
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
Intersection types and typed reference
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
interface Z {}
9+
10+
class A implements X, Y, Z {}
11+
class B implements X, Y {}
12+
13+
class Test {
14+
public ?X&Y $y;
15+
public X&Z $z;
16+
}
17+
$test = new Test;
18+
$r = new A;
19+
$test->y =& $r;
20+
$test->z =& $r;
21+
22+
23+
try {
24+
$r = null;
25+
} catch (\TypeError $e) {
26+
echo $e->getMessage(), \PHP_EOL;
27+
}
28+
29+
?>
30+
--EXPECT--
31+
Cannot assign null to reference held by property Test::$z of type X&Z
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Intersection types and typed reference
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
interface Z {}
9+
10+
class A implements X, Y, Z {}
11+
class B implements X, Y {}
12+
13+
class Test {
14+
public ?X&Y $y;
15+
public ?X&Z $z;
16+
}
17+
$test = new Test;
18+
$r = new A;
19+
$test->y =& $r;
20+
$test->z =& $r;
21+
22+
$r = null;
23+
24+
?>
25+
==DONE==
26+
--EXPECT--
27+
==DONE==
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
Invalid nullable widening
3+
--FILE--
4+
<?php
5+
6+
interface A {}
7+
interface B {}
8+
interface C {}
9+
10+
class Test implements A, B, C {}
11+
12+
class Foo {
13+
public function foo(): A {
14+
return new Test();
15+
}
16+
}
17+
18+
class FooChild extends Foo {
19+
public function foo(): A&B {
20+
return new Test();
21+
}
22+
}
23+
24+
class FooSecondChild extends FooChild {
25+
public function foo(): ?A&B {
26+
return new Test();
27+
}
28+
}
29+
30+
?>
31+
--EXPECTF--
32+
Fatal error: Declaration of FooSecondChild::foo(): ?A&B must be compatible with FooChild::foo(): A&B in %s on line %d
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Intersection type removing nullable
3+
--FILE--
4+
<?php
5+
6+
class A {}
7+
class B extends A {}
8+
9+
class Test {
10+
public ?A&B $prop;
11+
}
12+
class Test2 extends Test {
13+
public A&B $prop;
14+
}
15+
16+
?>
17+
--EXPECTF--
18+
Fatal error: Type of Test2::$prop must be ?A&B (as in class Test) in %s on line %d
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Invalid nullable narrowing
3+
--FILE--
4+
<?php
5+
6+
interface A {}
7+
interface B {}
8+
9+
class Foo {
10+
public function foo(?A&B $foo) {
11+
}
12+
}
13+
14+
class FooChild extends Foo {
15+
public function foo(A&B $foo) {
16+
}
17+
}
18+
19+
?>
20+
--EXPECTF--
21+
Fatal error: Declaration of FooChild::foo(A&B $foo) must be compatible with Foo::foo(?A&B $foo) in %s on line %d
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Valid intersection type variance
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
interface Z {}
9+
10+
class TestParent implements X, Y, Z {}
11+
class TestChild implements Z {}
12+
13+
class A {
14+
public ?X&Y $prop;
15+
16+
public function method1(X&Y&Z $a): ?X&Y {}
17+
public function method2(X&Y $a): ?X {}
18+
}
19+
class B extends A {
20+
public ?X&Y $prop;
21+
22+
public function method1(?X&Y $a): X&Y&Z {}
23+
public function method2(?X $a): X&Y {}
24+
}
25+
26+
?>
27+
===DONE===
28+
--EXPECT--
29+
===DONE===
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Valid inheritence - co-variance
3+
--FILE--
4+
<?php
5+
6+
interface A {}
7+
interface B {}
8+
interface C {}
9+
10+
class Test implements A, B, C {}
11+
12+
class Foo {
13+
public function foo(): ?A&B {
14+
return null;
15+
}
16+
}
17+
18+
class FooChild extends Foo {
19+
public function foo(): A&B {
20+
return new Test();
21+
}
22+
}
23+
24+
$o = new Foo();
25+
var_dump($o->foo());
26+
$o = new FooChild();
27+
var_dump($o->foo());
28+
29+
?>
30+
--EXPECTF--
31+
NULL
32+
object(Test)#%d (0) {
33+
}

Zend/zend_ast.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,9 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in
15351535
}
15361536
return;
15371537
}
1538+
if (ast->attr & ZEND_TYPE_NULLABLE) {
1539+
smart_str_appendc(str, '?');
1540+
}
15381541
if (ast->kind == ZEND_AST_TYPE_INTERSECTION) {
15391542
zend_ast_list *list = zend_ast_get_list(ast);
15401543
for (uint32_t i = 0; i < list->children; i++) {
@@ -1545,9 +1548,6 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in
15451548
}
15461549
return;
15471550
}
1548-
if (ast->attr & ZEND_TYPE_NULLABLE) {
1549-
smart_str_appendc(str, '?');
1550-
}
15511551
zend_ast_export_ns_name(str, ast, 0, indent);
15521552
}
15531553

Zend/zend_compile.c

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6694,15 +6694,6 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
66946694
zend_error_noreturn(E_COMPILE_ERROR, "never cannot be used as a parameter type");
66956695
}
66966696

6697-
if (force_nullable && ZEND_TYPE_IS_INTERSECTION(arg_info->type)) {
6698-
zend_string *type_str = zend_type_to_string(arg_info->type);
6699-
zend_error_noreturn(E_COMPILE_ERROR,
6700-
"Cannot use null as default value for parameter $%s of type %s",
6701-
/* We move type_str pointer one char forward to skip the '?' generated by
6702-
* the call to zend_compile_typename() */
6703-
ZSTR_VAL(name), ZSTR_VAL(type_str)+1);
6704-
}
6705-
67066697
if (default_type != IS_UNDEF && default_type != IS_CONSTANT_AST && !force_nullable
67076698
&& !zend_is_valid_default_value(arg_info->type, &default_node.u.constant)) {
67086699
zend_string *type_str = zend_type_to_string(arg_info->type);
@@ -7333,7 +7324,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z
73337324
if (ZEND_TYPE_IS_SET(type) && !Z_CONSTANT(value_zv)
73347325
&& !zend_is_valid_default_value(type, &value_zv)) {
73357326
zend_string *str = zend_type_to_string(type);
7336-
if (Z_TYPE(value_zv) == IS_NULL && !ZEND_TYPE_IS_INTERSECTION(type)) {
7327+
if (Z_TYPE(value_zv) == IS_NULL) {
73377328
ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL;
73387329
zend_string *nullable_str = zend_type_to_string(type);
73397330

Zend/zend_language_parser.y

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ type_expr:
798798
| '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
799799
| union_type { $$ = $1; }
800800
| intersection_type { $$ = $1; }
801+
| '?' intersection_type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
801802
;
802803

803804
type:
@@ -823,6 +824,7 @@ type_expr_without_static:
823824
| '?' type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
824825
| union_type_without_static { $$ = $1; }
825826
| intersection_type_without_static { $$ = $1; }
827+
| '?' intersection_type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
826828
;
827829

828830
type_without_static:

0 commit comments

Comments
 (0)