From e83f8dbd6c028f53ae76943320c18ccedb35b066 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 19 Jul 2021 16:12:13 +0200 Subject: [PATCH 1/2] Make intersection types nullable --- .../assigning_intersection_types.phpt | 11 ++++++- .../intersection_types/bug81268.phpt | 4 +-- .../implicit_nullable_intersection_type.phpt | 10 +++--- .../invalid_types/invalid_nullable_type.phpt | 10 ------ .../intersection_types/parameter.phpt | 6 ++-- .../intersection_types/typed_reference2.phpt | 31 +++++++++++++++++ .../intersection_types/typed_reference3.phpt | 27 +++++++++++++++ .../intersection_types/variance/invalid7.phpt | 32 ++++++++++++++++++ .../intersection_types/variance/invalid8.phpt | 18 ++++++++++ .../intersection_types/variance/invalid9.phpt | 21 ++++++++++++ .../intersection_types/variance/valid10.phpt | 29 ++++++++++++++++ .../intersection_types/variance/valid9.phpt | 33 +++++++++++++++++++ Zend/zend_ast.c | 6 ++-- Zend/zend_compile.c | 11 +------ Zend/zend_language_parser.y | 2 ++ 15 files changed, 219 insertions(+), 32 deletions(-) delete mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/typed_reference2.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/typed_reference3.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid7.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid8.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid9.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid10.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid9.phpt diff --git a/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt b/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt index 568e6e25afade..4cc50047c36f0 100644 --- a/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt +++ b/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt @@ -20,6 +20,9 @@ class A { public function method2(X $a): X&Y { return new TestParent(); } + public function method3(?X&Y $a): ?X&Y { + return $a; + } } $tp = new TestParent(); @@ -42,7 +45,10 @@ $r = $o->method1($tc); var_dump($r); $r = $o->method2($tc); var_dump($r); - +$r = $o->method3($tc); +var_dump($r); +$r = $o->method3(null); +var_dump($r); ?> --EXPECTF-- @@ -55,3 +61,6 @@ object(TestChild)#%d (0) { } object(TestParent)#%d (0) { } +object(TestChild)#%d (0) { +} +NULL diff --git a/Zend/tests/type_declarations/intersection_types/bug81268.phpt b/Zend/tests/type_declarations/intersection_types/bug81268.phpt index 19f86d4eeb960..e74c43559c091 100644 --- a/Zend/tests/type_declarations/intersection_types/bug81268.phpt +++ b/Zend/tests/type_declarations/intersection_types/bug81268.phpt @@ -1,5 +1,5 @@ --TEST-- -Bug #81268 Wrong message when using null as a default value for intersection types +Bug #81268 Message when using null as a default value for intersection types --FILE-- --EXPECTF-- -Fatal error: Cannot use null as default value for property Test::$y of type X&Y in %s on line %d +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 diff --git a/Zend/tests/type_declarations/intersection_types/implicit_nullable_intersection_type.phpt b/Zend/tests/type_declarations/intersection_types/implicit_nullable_intersection_type.phpt index fdac7576a78f8..23681a5cd5c69 100644 --- a/Zend/tests/type_declarations/intersection_types/implicit_nullable_intersection_type.phpt +++ b/Zend/tests/type_declarations/intersection_types/implicit_nullable_intersection_type.phpt @@ -1,10 +1,12 @@ --TEST-- -Intersection types cannot be implicitly nullable +Intersection types can be implicitly nullable as the others --FILE-- ---EXPECTF-- -Fatal error: Cannot use null as default value for parameter $foo of type X&Y in %s on line %d +--EXPECT-- +NULL diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt deleted file mode 100644 index 1c35dfdf91c70..0000000000000 --- a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -Intersection type cannot be nullable ---FILE-- - ---EXPECTF-- -Parse error: syntax error, unexpected token "&", expecting "{" in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/parameter.phpt b/Zend/tests/type_declarations/intersection_types/parameter.phpt index d1c7de2243654..62fe09ee69b17 100644 --- a/Zend/tests/type_declarations/intersection_types/parameter.phpt +++ b/Zend/tests/type_declarations/intersection_types/parameter.phpt @@ -9,10 +9,11 @@ interface B {} class Foo implements A, B {} class Bar implements A {} -function foo(A&B $bar) { +function foo(?A&B $bar) { var_dump($bar); } +foo(null); foo(new Foo()); try { @@ -23,6 +24,7 @@ try { ?> --EXPECTF-- +NULL object(Foo)#1 (0) { } -foo(): Argument #1 ($bar) must be of type A&B, Bar given, called in %s on line %d +foo(): Argument #1 ($bar) must be of type ?A&B, Bar given, called in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/typed_reference2.phpt b/Zend/tests/type_declarations/intersection_types/typed_reference2.phpt new file mode 100644 index 0000000000000..b48ce2e91db56 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/typed_reference2.phpt @@ -0,0 +1,31 @@ +--TEST-- +Intersection types and typed reference +--FILE-- +y =& $r; +$test->z =& $r; + + +try { + $r = null; +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +--EXPECT-- +Cannot assign null to reference held by property Test::$z of type X&Z diff --git a/Zend/tests/type_declarations/intersection_types/typed_reference3.phpt b/Zend/tests/type_declarations/intersection_types/typed_reference3.phpt new file mode 100644 index 0000000000000..14710a8d3f19d --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/typed_reference3.phpt @@ -0,0 +1,27 @@ +--TEST-- +Intersection types and typed reference +--FILE-- +y =& $r; +$test->z =& $r; + +$r = null; + +?> +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid7.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid7.phpt new file mode 100644 index 0000000000000..225bc4a47cddd --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid7.phpt @@ -0,0 +1,32 @@ +--TEST-- +Invalid nullable widening +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooSecondChild::foo(): ?A&B must be compatible with FooChild::foo(): A&B in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid8.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid8.phpt new file mode 100644 index 0000000000000..78c9fdc11f0df --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid8.phpt @@ -0,0 +1,18 @@ +--TEST-- +Intersection type removing nullable +--FILE-- + +--EXPECTF-- +Fatal error: Type of Test2::$prop must be ?A&B (as in class Test) in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid9.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid9.phpt new file mode 100644 index 0000000000000..8716784fdcd0a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid9.phpt @@ -0,0 +1,21 @@ +--TEST-- +Invalid nullable narrowing +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooChild::foo(A&B $foo) must be compatible with Foo::foo(?A&B $foo) in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid10.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid10.phpt new file mode 100644 index 0000000000000..e475367140adf --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid10.phpt @@ -0,0 +1,29 @@ +--TEST-- +Valid intersection type variance +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt new file mode 100644 index 0000000000000..eed147ed78d15 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt @@ -0,0 +1,33 @@ +--TEST-- +Valid inheritence - co-variance +--FILE-- +foo()); +$o = new FooChild(); +var_dump($o->foo()); + +?> +--EXPECTF-- +NULL +object(Test)#%d (0) { +} diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index a2bbf2c9c5dc3..801027605472b 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1535,6 +1535,9 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in } return; } + if (ast->attr & ZEND_TYPE_NULLABLE) { + smart_str_appendc(str, '?'); + } if (ast->kind == ZEND_AST_TYPE_INTERSECTION) { zend_ast_list *list = zend_ast_get_list(ast); 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 } return; } - if (ast->attr & ZEND_TYPE_NULLABLE) { - smart_str_appendc(str, '?'); - } zend_ast_export_ns_name(str, ast, 0, indent); } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f9f98b74b7d6f..8b3bcf7fe1cef 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6694,15 +6694,6 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall zend_error_noreturn(E_COMPILE_ERROR, "never cannot be used as a parameter type"); } - if (force_nullable && ZEND_TYPE_IS_INTERSECTION(arg_info->type)) { - zend_string *type_str = zend_type_to_string(arg_info->type); - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use null as default value for parameter $%s of type %s", - /* We move type_str pointer one char forward to skip the '?' generated by - * the call to zend_compile_typename() */ - ZSTR_VAL(name), ZSTR_VAL(type_str)+1); - } - if (default_type != IS_UNDEF && default_type != IS_CONSTANT_AST && !force_nullable && !zend_is_valid_default_value(arg_info->type, &default_node.u.constant)) { 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 if (ZEND_TYPE_IS_SET(type) && !Z_CONSTANT(value_zv) && !zend_is_valid_default_value(type, &value_zv)) { zend_string *str = zend_type_to_string(type); - if (Z_TYPE(value_zv) == IS_NULL && !ZEND_TYPE_IS_INTERSECTION(type)) { + if (Z_TYPE(value_zv) == IS_NULL) { ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL; zend_string *nullable_str = zend_type_to_string(type); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index ccf11bda8b8c3..f9938ce914a18 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -798,6 +798,7 @@ type_expr: | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } | union_type { $$ = $1; } | intersection_type { $$ = $1; } + | '?' intersection_type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } ; type: @@ -823,6 +824,7 @@ type_expr_without_static: | '?' type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } | union_type_without_static { $$ = $1; } | intersection_type_without_static { $$ = $1; } + | '?' intersection_type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } ; type_without_static: From 5bc99467b8dfb1b2dbdebc0c85d627fdf843e4f4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 20 Jul 2021 09:47:06 +0200 Subject: [PATCH 2/2] Use X&Y|null instead of ?X&Y --- .../assigning_intersection_types.phpt | 2 +- .../intersection_types/bug81268.phpt | 2 +- .../intersection_types/parameter.phpt | 4 ++-- .../intersection_types/typed_reference2.phpt | 2 +- .../intersection_types/typed_reference3.phpt | 4 ++-- .../intersection_types/variance/invalid7.phpt | 4 ++-- .../intersection_types/variance/invalid8.phpt | 4 ++-- .../intersection_types/variance/invalid9.phpt | 4 ++-- .../intersection_types/variance/valid10.phpt | 8 ++++---- .../intersection_types/variance/valid9.phpt | 2 +- Zend/zend_ast.c | 9 ++++++--- Zend/zend_compile.c | 2 +- Zend/zend_language_parser.y | 16 ++++++++++++++-- 13 files changed, 39 insertions(+), 24 deletions(-) diff --git a/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt b/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt index 4cc50047c36f0..8a18d83b66656 100644 --- a/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt +++ b/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt @@ -20,7 +20,7 @@ class A { public function method2(X $a): X&Y { return new TestParent(); } - public function method3(?X&Y $a): ?X&Y { + public function method3(X&Y|null $a): X&Y|null { return $a; } } diff --git a/Zend/tests/type_declarations/intersection_types/bug81268.phpt b/Zend/tests/type_declarations/intersection_types/bug81268.phpt index e74c43559c091..6a317fea3dd17 100644 --- a/Zend/tests/type_declarations/intersection_types/bug81268.phpt +++ b/Zend/tests/type_declarations/intersection_types/bug81268.phpt @@ -9,4 +9,4 @@ class Test { ?> --EXPECTF-- -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 +Fatal error: Default value for property of type X&Y may not be null. Use the nullable type X&Y|null to allow null default value in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/parameter.phpt b/Zend/tests/type_declarations/intersection_types/parameter.phpt index 62fe09ee69b17..a84b2bef5ccb7 100644 --- a/Zend/tests/type_declarations/intersection_types/parameter.phpt +++ b/Zend/tests/type_declarations/intersection_types/parameter.phpt @@ -9,7 +9,7 @@ interface B {} class Foo implements A, B {} class Bar implements A {} -function foo(?A&B $bar) { +function foo(A&B|null $bar) { var_dump($bar); } @@ -27,4 +27,4 @@ try { NULL object(Foo)#1 (0) { } -foo(): Argument #1 ($bar) must be of type ?A&B, Bar given, called in %s on line %d +foo(): Argument #1 ($bar) must be of type A&B|null, Bar given, called in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/typed_reference2.phpt b/Zend/tests/type_declarations/intersection_types/typed_reference2.phpt index b48ce2e91db56..ade1405c0e4fc 100644 --- a/Zend/tests/type_declarations/intersection_types/typed_reference2.phpt +++ b/Zend/tests/type_declarations/intersection_types/typed_reference2.phpt @@ -11,7 +11,7 @@ class A implements X, Y, Z {} class B implements X, Y {} class Test { - public ?X&Y $y; + public X&Y|null $y; public X&Z $z; } $test = new Test; diff --git a/Zend/tests/type_declarations/intersection_types/typed_reference3.phpt b/Zend/tests/type_declarations/intersection_types/typed_reference3.phpt index 14710a8d3f19d..c5a78a86e6ac2 100644 --- a/Zend/tests/type_declarations/intersection_types/typed_reference3.phpt +++ b/Zend/tests/type_declarations/intersection_types/typed_reference3.phpt @@ -11,8 +11,8 @@ class A implements X, Y, Z {} class B implements X, Y {} class Test { - public ?X&Y $y; - public ?X&Z $z; + public X&Y|null $y; + public X&Z|null $z; } $test = new Test; $r = new A; diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid7.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid7.phpt index 225bc4a47cddd..8e102f7ff3c4f 100644 --- a/Zend/tests/type_declarations/intersection_types/variance/invalid7.phpt +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid7.phpt @@ -22,11 +22,11 @@ class FooChild extends Foo { } class FooSecondChild extends FooChild { - public function foo(): ?A&B { + public function foo(): A&B|null { return new Test(); } } ?> --EXPECTF-- -Fatal error: Declaration of FooSecondChild::foo(): ?A&B must be compatible with FooChild::foo(): A&B in %s on line %d +Fatal error: Declaration of FooSecondChild::foo(): A&B|null must be compatible with FooChild::foo(): A&B in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid8.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid8.phpt index 78c9fdc11f0df..b05113e80d79c 100644 --- a/Zend/tests/type_declarations/intersection_types/variance/invalid8.phpt +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid8.phpt @@ -7,7 +7,7 @@ class A {} class B extends A {} class Test { - public ?A&B $prop; + public A&B|null $prop; } class Test2 extends Test { public A&B $prop; @@ -15,4 +15,4 @@ class Test2 extends Test { ?> --EXPECTF-- -Fatal error: Type of Test2::$prop must be ?A&B (as in class Test) in %s on line %d +Fatal error: Type of Test2::$prop must be A&B|null (as in class Test) in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid9.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid9.phpt index 8716784fdcd0a..867b7fb6a0777 100644 --- a/Zend/tests/type_declarations/intersection_types/variance/invalid9.phpt +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid9.phpt @@ -7,7 +7,7 @@ interface A {} interface B {} class Foo { - public function foo(?A&B $foo) { + public function foo(A&B|null $foo) { } } @@ -18,4 +18,4 @@ class FooChild extends Foo { ?> --EXPECTF-- -Fatal error: Declaration of FooChild::foo(A&B $foo) must be compatible with Foo::foo(?A&B $foo) in %s on line %d +Fatal error: Declaration of FooChild::foo(A&B $foo) must be compatible with Foo::foo(A&B|null $foo) in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid10.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid10.phpt index e475367140adf..923ad8c20909e 100644 --- a/Zend/tests/type_declarations/intersection_types/variance/valid10.phpt +++ b/Zend/tests/type_declarations/intersection_types/variance/valid10.phpt @@ -11,15 +11,15 @@ class TestParent implements X, Y, Z {} class TestChild implements Z {} class A { - public ?X&Y $prop; + public X&Y|null $prop; - public function method1(X&Y&Z $a): ?X&Y {} + public function method1(X&Y&Z $a): X&Y|null{} public function method2(X&Y $a): ?X {} } class B extends A { - public ?X&Y $prop; + public X&Y|null $prop; - public function method1(?X&Y $a): X&Y&Z {} + public function method1(X&Y|null $a): X&Y&Z {} public function method2(?X $a): X&Y {} } diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt index eed147ed78d15..d0cf28775d79e 100644 --- a/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt +++ b/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt @@ -10,7 +10,7 @@ interface C {} class Test implements A, B, C {} class Foo { - public function foo(): ?A&B { + public function foo(): A&B|null { return null; } } diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 801027605472b..ffc19e8c98aed 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1535,9 +1535,6 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in } return; } - if (ast->attr & ZEND_TYPE_NULLABLE) { - smart_str_appendc(str, '?'); - } if (ast->kind == ZEND_AST_TYPE_INTERSECTION) { zend_ast_list *list = zend_ast_get_list(ast); for (uint32_t i = 0; i < list->children; i++) { @@ -1546,8 +1543,14 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in } zend_ast_export_type(str, list->child[i], indent); } + if (ast->attr & ZEND_TYPE_NULLABLE) { + smart_str_appends(str, "|null"); + } return; } + if (ast->attr & ZEND_TYPE_NULLABLE) { + smart_str_appendc(str, '?'); + } zend_ast_export_ns_name(str, ast, 0, indent); } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8b3bcf7fe1cef..894cf20caba20 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1284,7 +1284,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop if (type_mask & MAY_BE_NULL) { bool is_union = !str || memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL; - if (!is_union) { + if (!is_union && !ZEND_TYPE_IS_INTERSECTION(type)) { zend_string *nullable_str = zend_string_concat2("?", 1, ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(str); return nullable_str; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index f9938ce914a18..c410755bc6099 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -287,6 +287,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type reserved_non_modifiers semi_reserved +%type null_type + %% /* Rules */ start: @@ -793,12 +795,22 @@ optional_type_without_static: | type_expr_without_static { $$ = $1; } ; +null_type: + T_STRING { + zend_string *str = Z_STR(((zend_ast_zval*)$1)->val); + if (!zend_string_equals_literal_ci(str, "null")) { + zend_error(E_PARSE, "Invalid compound type expression"); + } + zend_string_free(str); + } +; + type_expr: type { $$ = $1; } | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } | union_type { $$ = $1; } + | intersection_type '|' null_type { $$ = $1; $$->attr |= ZEND_TYPE_NULLABLE; } | intersection_type { $$ = $1; } - | '?' intersection_type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } ; type: @@ -823,8 +835,8 @@ type_expr_without_static: type_without_static { $$ = $1; } | '?' type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } | union_type_without_static { $$ = $1; } + | intersection_type_without_static '|' null_type { $$ = $1; $$->attr |= ZEND_TYPE_NULLABLE; } | intersection_type_without_static { $$ = $1; } - | '?' intersection_type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } ; type_without_static: