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 1b0cdf8

Browse filesBrowse files
committed
Allow 'null' as a standalone type
1 parent 7d8e3da commit 1b0cdf8
Copy full SHA for 1b0cdf8
Expand file treeCollapse file tree

14 files changed

+125
-24
lines changed
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Null can be used as a standalone type
3+
--FILE--
4+
<?php
5+
6+
function test(null $v): null {
7+
return $v;
8+
}
9+
10+
var_dump(test(null));
11+
12+
?>
13+
===DONE===
14+
--EXPECT--
15+
NULL
16+
===DONE===
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Test typed properties allow null
3+
--FILE--
4+
<?php
5+
class Foo {
6+
public null $value;
7+
}
8+
9+
$foo = new Foo();
10+
?>
11+
===DONE===
12+
--EXPECT--
13+
===DONE===
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Typed null|false return without value generates compile-time error
3+
--FILE--
4+
<?php
5+
6+
function test() : null|false {
7+
return;
8+
}
9+
10+
test();
11+
12+
?>
13+
--EXPECTF--
14+
Fatal error: A function with return type must return a value (did you mean "return null;" instead of "return;"?) in %s on line %d
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Typed null return without value generates compile-time error
3+
--FILE--
4+
<?php
5+
6+
function test() : null {
7+
return;
8+
}
9+
10+
test();
11+
12+
?>
13+
--EXPECTF--
14+
Fatal error: A function with return type must return a value (did you mean "return null;" instead of "return;"?) in %s on line %d
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Null and false can be used in a union type
3+
--FILE--
4+
<?php
5+
6+
function test1(): null|false {}
7+
function test2(): false|null {}
8+
9+
?>
10+
===DONE===
11+
--EXPECT--
12+
===DONE===

‎Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt

Copy file name to clipboardExpand all lines: Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ function test(): ?null {
88

99
?>
1010
--EXPECTF--
11-
Fatal error: Null cannot be used as a standalone type in %s on line %d
11+
Fatal error: null cannot be marked as nullable in %s on line %d

‎Zend/tests/type_declarations/union_types/standalone_false.phpt

Copy file name to clipboardExpand all lines: Zend/tests/type_declarations/union_types/standalone_false.phpt
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ function test(): false {}
77

88
?>
99
--EXPECTF--
10-
Fatal error: False cannot be used as a standalone type in %s on line %d
10+
Fatal error: false cannot be used as a standalone type in %s on line %d
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
False cannot be used as a standalone type even with implicit nullability
3+
--FILE--
4+
<?php
5+
6+
function test(false $v = null) {}
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: false cannot be used as a standalone type in %s on line %d
+4-3Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
--TEST--
2-
Null cannot be used as a standalone type
2+
Null can be used as a standalone type
33
--FILE--
44
<?php
55

66
function test(): null {}
77

88
?>
9-
--EXPECTF--
10-
Fatal error: Null cannot be used as a standalone type in %s on line %d
9+
===DONE===
10+
--EXPECT--
11+
===DONE===

‎Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt

Copy file name to clipboardExpand all lines: Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ function test(): ?false {}
77

88
?>
99
--EXPECTF--
10-
Fatal error: False cannot be used as a standalone type in %s on line %d
10+
Fatal error: false cannot be marked as nullable since false is not a standalone type in %s on line %d

‎Zend/zend_compile.c

Copy file name to clipboardExpand all lines: Zend/zend_compile.c
+21-17Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,7 +1257,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
12571257

12581258
if (type_mask & MAY_BE_NULL) {
12591259
bool is_union = !str || memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL;
1260-
if (!is_union) {
1260+
if (!is_union && !zend_string_equals_literal(str, "false")) {
12611261
zend_string *nullable_str = zend_string_concat2("?", 1, ZSTR_VAL(str), ZSTR_LEN(str));
12621262
zend_string_release(str);
12631263
return nullable_str;
@@ -6188,11 +6188,11 @@ static bool zend_type_contains_traversable(zend_type type) {
61886188
static zend_type zend_compile_typename(
61896189
zend_ast *ast, bool force_allow_null) /* {{{ */
61906190
{
6191-
bool allow_null = force_allow_null;
6191+
bool is_marked_nullable = false;
61926192
zend_ast_attr orig_ast_attr = ast->attr;
61936193
zend_type type = ZEND_TYPE_INIT_NONE(0);
61946194
if (ast->attr & ZEND_TYPE_NULLABLE) {
6195-
allow_null = 1;
6195+
is_marked_nullable = true;
61966196
ast->attr &= ~ZEND_TYPE_NULLABLE;
61976197
}
61986198

@@ -6316,10 +6316,6 @@ static zend_type zend_compile_typename(
63166316
type = zend_compile_single_typename(ast);
63176317
}
63186318

6319-
if (allow_null) {
6320-
ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL;
6321-
}
6322-
63236319
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
63246320
if ((type_mask & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) {
63256321
zend_string *type_str = zend_type_to_string(type);
@@ -6334,7 +6330,7 @@ static zend_type zend_compile_typename(
63346330
ZSTR_VAL(type_str));
63356331
}
63366332

6337-
if (type_mask == MAY_BE_ANY && (orig_ast_attr & ZEND_TYPE_NULLABLE)) {
6333+
if (type_mask == MAY_BE_ANY && is_marked_nullable) {
63386334
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed cannot be marked as nullable since mixed already includes null");
63396335
}
63406336

@@ -6345,6 +6341,23 @@ static zend_type zend_compile_typename(
63456341
ZSTR_VAL(type_str));
63466342
}
63476343

6344+
if ((type_mask & MAY_BE_NULL) && is_marked_nullable) {
6345+
zend_error_noreturn(E_COMPILE_ERROR, "null cannot be marked as nullable");
6346+
}
6347+
6348+
if ((type_mask & MAY_BE_FALSE) && !ZEND_TYPE_IS_COMPLEX(type) && !(type_mask & ~MAY_BE_FALSE)) {
6349+
if (is_marked_nullable) {
6350+
zend_error_noreturn(E_COMPILE_ERROR, "false cannot be marked as nullable since false is not a standalone type");
6351+
} else {
6352+
zend_error_noreturn(E_COMPILE_ERROR, "false cannot be used as a standalone type");
6353+
}
6354+
}
6355+
6356+
if (is_marked_nullable || force_allow_null) {
6357+
ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL;
6358+
type_mask = ZEND_TYPE_PURE_MASK(type);
6359+
}
6360+
63486361
if ((type_mask & MAY_BE_VOID) && (ZEND_TYPE_IS_COMPLEX(type) || type_mask != MAY_BE_VOID)) {
63496362
zend_error_noreturn(E_COMPILE_ERROR, "Void can only be used as a standalone type");
63506363
}
@@ -6353,15 +6366,6 @@ static zend_type zend_compile_typename(
63536366
zend_error_noreturn(E_COMPILE_ERROR, "never can only be used as a standalone type");
63546367
}
63556368

6356-
if ((type_mask & (MAY_BE_NULL|MAY_BE_FALSE))
6357-
&& !ZEND_TYPE_IS_COMPLEX(type) && !(type_mask & ~(MAY_BE_NULL|MAY_BE_FALSE))) {
6358-
if (type_mask == MAY_BE_NULL) {
6359-
zend_error_noreturn(E_COMPILE_ERROR, "Null cannot be used as a standalone type");
6360-
} else {
6361-
zend_error_noreturn(E_COMPILE_ERROR, "False cannot be used as a standalone type");
6362-
}
6363-
}
6364-
63656369
ast->attr = orig_ast_attr;
63666370
return type;
63676371
}

‎ext/reflection/php_reflection.c

Copy file name to clipboardExpand all lines: ext/reflection/php_reflection.c
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,10 @@ static reflection_type_kind get_type_kind(zend_type type) {
13421342
if (type_mask_without_null == MAY_BE_BOOL || ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY) {
13431343
return NAMED_TYPE;
13441344
}
1345+
/* null|false must be a union type */
1346+
if (ZEND_TYPE_PURE_MASK(type) == (MAY_BE_NULL|MAY_BE_FALSE)) {
1347+
return UNION_TYPE;
1348+
}
13451349
/* Check that only one bit is set. */
13461350
if ((type_mask_without_null & (type_mask_without_null - 1)) != 0) {
13471351
return UNION_TYPE;
@@ -1356,6 +1360,7 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be
13561360
type_reference *reference;
13571361
reflection_type_kind type_kind = get_type_kind(type);
13581362
bool is_mixed = ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY;
1363+
bool is_only_null = (ZEND_TYPE_PURE_MASK(type) == MAY_BE_NULL && !ZEND_TYPE_IS_COMPLEX(type));
13591364

13601365
switch (type_kind) {
13611366
case INTERSECTION_TYPE:
@@ -1373,7 +1378,7 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be
13731378
intern = Z_REFLECTION_P(object);
13741379
reference = (type_reference*) emalloc(sizeof(type_reference));
13751380
reference->type = type;
1376-
reference->legacy_behavior = legacy_behavior && type_kind == NAMED_TYPE && !is_mixed;
1381+
reference->legacy_behavior = legacy_behavior && type_kind == NAMED_TYPE && !is_mixed && !is_only_null;
13771382
intern->ptr = reference;
13781383
intern->ref_type = REF_TYPE_TYPE;
13791384

‎ext/reflection/tests/ReflectionType_possible_types.phpt

Copy file name to clipboardExpand all lines: ext/reflection/tests/ReflectionType_possible_types.phpt
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ $functions = [
1212
function(): array {},
1313
function(): callable {},
1414
function(): iterable {},
15+
function(): null {},
1516
function(): StdClass {}
1617
];
1718

@@ -30,4 +31,5 @@ string(4) "bool"
3031
string(5) "array"
3132
string(8) "callable"
3233
string(8) "iterable"
34+
string(4) "null"
3335
string(8) "StdClass"

‎ext/reflection/tests/union_types.phpt

Copy file name to clipboardExpand all lines: ext/reflection/tests/union_types.phpt
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ function dumpType(ReflectionUnionType $rt) {
1515

1616
function test1(): X|Y|int|float|false|null { }
1717
function test2(): X|iterable|bool { }
18+
function test3(): null|false { }
1819

1920
class Test {
2021
public X|Y|int $prop;
2122
}
2223

2324
dumpType((new ReflectionFunction('test1'))->getReturnType());
2425
dumpType((new ReflectionFunction('test2'))->getReturnType());
26+
dumpType((new ReflectionFunction('test3'))->getReturnType());
2527

2628
$rc = new ReflectionClass(Test::class);
2729
$rp = $rc->getProperty('prop');
@@ -75,6 +77,14 @@ Allows null: false
7577
Name: bool
7678
String: bool
7779
Allows Null: false
80+
Type false|null:
81+
Allows null: true
82+
Name: false
83+
String: false
84+
Allows Null: false
85+
Name: null
86+
String: null
87+
Allows Null: true
7888
Type X|Y|int:
7989
Allows null: false
8090
Name: X

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.