mirror of
https://github.com/php/php-src.git
synced 2024-09-21 09:57:23 +00:00
Allow `null
and
false
` as standalone types (#7546)
RFC: https://wiki.php.net/rfc/null-standalone-type Also a drive-by consistency fix for error messages.
This commit is contained in:
parent
7bb2a9ff38
commit
6039c07a3a
14
Zend/tests/type_declarations/standalone_null.phpt
Normal file
14
Zend/tests/type_declarations/standalone_null.phpt
Normal file
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Null can be used as a standalone type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test(null $v): null {
|
||||
return $v;
|
||||
}
|
||||
|
||||
var_dump(test(null));
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
NULL
|
20
Zend/tests/type_declarations/typed_properties_110.phpt
Normal file
20
Zend/tests/type_declarations/typed_properties_110.phpt
Normal file
@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Test typed properties allow null
|
||||
--FILE--
|
||||
<?php
|
||||
class Foo {
|
||||
public null $value;
|
||||
}
|
||||
|
||||
$foo = new Foo();
|
||||
$foo->value = null;
|
||||
|
||||
try {
|
||||
$foo->value = 1;
|
||||
} catch (\TypeError $e) {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Cannot assign int to property Foo::$value of type null
|
20
Zend/tests/type_declarations/typed_properties_111.phpt
Normal file
20
Zend/tests/type_declarations/typed_properties_111.phpt
Normal file
@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Test typed properties allow false
|
||||
--FILE--
|
||||
<?php
|
||||
class Foo {
|
||||
public false $value;
|
||||
}
|
||||
|
||||
$foo = new Foo();
|
||||
$foo->value = false;
|
||||
|
||||
try {
|
||||
$foo->value = true;
|
||||
} catch (\TypeError $e) {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Cannot assign bool to property Foo::$value of type false
|
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Typed null|false return without value generates compile-time error
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test() : null|false {
|
||||
return;
|
||||
}
|
||||
|
||||
test();
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: A function with return type must return a value (did you mean "return null;" instead of "return;"?) in %s on line %d
|
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Typed null return without value generates compile-time error
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test() : null {
|
||||
return;
|
||||
}
|
||||
|
||||
test();
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: A function with return type must return a value (did you mean "return null;" instead of "return;"?) in %s on line %d
|
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Null and false can be used in a union type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test1(): null|false {}
|
||||
function test2(): false|null {}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -8,4 +8,4 @@ function test(): ?null {
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Null cannot be used as a standalone type in %s on line %d
|
||||
Fatal error: null cannot be marked as nullable in %s on line %d
|
||||
|
@ -1,10 +1,11 @@
|
||||
--TEST--
|
||||
False cannot be used as a standalone type
|
||||
False can be used as a standalone type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test(): false {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: False cannot be used as a standalone type in %s on line %d
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
||||
|
@ -0,0 +1,11 @@
|
||||
--TEST--
|
||||
False can be used as a standalone type even with implicit nullability
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test(false $v = null) {}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -1,10 +1,11 @@
|
||||
--TEST--
|
||||
Null cannot be used as a standalone type
|
||||
Null can be used as a standalone type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test(): null {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Null cannot be used as a standalone type in %s on line %d
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
||||
|
@ -1,10 +1,11 @@
|
||||
--TEST--
|
||||
Nullable false cannot be used as a standalone type
|
||||
Nullable false can be used as a standalone type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test(): ?false {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: False cannot be used as a standalone type in %s on line %d
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
||||
|
@ -6186,11 +6186,11 @@ static bool zend_type_contains_traversable(zend_type type) {
|
||||
static zend_type zend_compile_typename(
|
||||
zend_ast *ast, bool force_allow_null) /* {{{ */
|
||||
{
|
||||
bool allow_null = force_allow_null;
|
||||
bool is_marked_nullable = ast->attr & ZEND_TYPE_NULLABLE;
|
||||
zend_ast_attr orig_ast_attr = ast->attr;
|
||||
zend_type type = ZEND_TYPE_INIT_NONE(0);
|
||||
if (ast->attr & ZEND_TYPE_NULLABLE) {
|
||||
allow_null = 1;
|
||||
|
||||
if (is_marked_nullable) {
|
||||
ast->attr &= ~ZEND_TYPE_NULLABLE;
|
||||
}
|
||||
|
||||
@ -6314,10 +6314,6 @@ static zend_type zend_compile_typename(
|
||||
type = zend_compile_single_typename(ast);
|
||||
}
|
||||
|
||||
if (allow_null) {
|
||||
ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL;
|
||||
}
|
||||
|
||||
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
|
||||
if ((type_mask & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) {
|
||||
zend_string *type_str = zend_type_to_string(type);
|
||||
@ -6332,7 +6328,7 @@ static zend_type zend_compile_typename(
|
||||
ZSTR_VAL(type_str));
|
||||
}
|
||||
|
||||
if (type_mask == MAY_BE_ANY && (orig_ast_attr & ZEND_TYPE_NULLABLE)) {
|
||||
if (type_mask == MAY_BE_ANY && is_marked_nullable) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed cannot be marked as nullable since mixed already includes null");
|
||||
}
|
||||
|
||||
@ -6343,6 +6339,15 @@ static zend_type zend_compile_typename(
|
||||
ZSTR_VAL(type_str));
|
||||
}
|
||||
|
||||
if ((type_mask & MAY_BE_NULL) && is_marked_nullable) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "null cannot be marked as nullable");
|
||||
}
|
||||
|
||||
if (is_marked_nullable || force_allow_null) {
|
||||
ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL;
|
||||
type_mask = ZEND_TYPE_PURE_MASK(type);
|
||||
}
|
||||
|
||||
if ((type_mask & MAY_BE_VOID) && (ZEND_TYPE_IS_COMPLEX(type) || type_mask != MAY_BE_VOID)) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Void can only be used as a standalone type");
|
||||
}
|
||||
@ -6351,15 +6356,6 @@ static zend_type zend_compile_typename(
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "never can only be used as a standalone type");
|
||||
}
|
||||
|
||||
if ((type_mask & (MAY_BE_NULL|MAY_BE_FALSE))
|
||||
&& !ZEND_TYPE_IS_COMPLEX(type) && !(type_mask & ~(MAY_BE_NULL|MAY_BE_FALSE))) {
|
||||
if (type_mask == MAY_BE_NULL) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Null cannot be used as a standalone type");
|
||||
} else {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "False cannot be used as a standalone type");
|
||||
}
|
||||
}
|
||||
|
||||
ast->attr = orig_ast_attr;
|
||||
return type;
|
||||
}
|
||||
|
@ -1356,6 +1356,7 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be
|
||||
type_reference *reference;
|
||||
reflection_type_kind type_kind = get_type_kind(type);
|
||||
bool is_mixed = ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY;
|
||||
bool is_only_null = (ZEND_TYPE_PURE_MASK(type) == MAY_BE_NULL && !ZEND_TYPE_IS_COMPLEX(type));
|
||||
|
||||
switch (type_kind) {
|
||||
case INTERSECTION_TYPE:
|
||||
@ -1373,7 +1374,7 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be
|
||||
intern = Z_REFLECTION_P(object);
|
||||
reference = (type_reference*) emalloc(sizeof(type_reference));
|
||||
reference->type = type;
|
||||
reference->legacy_behavior = legacy_behavior && type_kind == NAMED_TYPE && !is_mixed;
|
||||
reference->legacy_behavior = legacy_behavior && type_kind == NAMED_TYPE && !is_mixed && !is_only_null;
|
||||
intern->ptr = reference;
|
||||
intern->ref_type = REF_TYPE_TYPE;
|
||||
|
||||
|
@ -12,6 +12,8 @@ $functions = [
|
||||
function(): array {},
|
||||
function(): callable {},
|
||||
function(): iterable {},
|
||||
function(): null {},
|
||||
function(): false {},
|
||||
function(): StdClass {}
|
||||
];
|
||||
|
||||
@ -30,4 +32,6 @@ string(4) "bool"
|
||||
string(5) "array"
|
||||
string(8) "callable"
|
||||
string(8) "iterable"
|
||||
string(4) "null"
|
||||
string(5) "false"
|
||||
string(8) "StdClass"
|
||||
|
@ -13,8 +13,17 @@ function dumpType(ReflectionUnionType $rt) {
|
||||
}
|
||||
}
|
||||
|
||||
function dumpBCType(ReflectionNamedType $rt) {
|
||||
echo "Type $rt:\n";
|
||||
echo " Name: " . $rt->getName() . "\n";
|
||||
echo " String: " . (string) $rt . "\n";
|
||||
echo " Allows Null: " . ($rt->allowsNull() ? "true" : "false") . "\n";
|
||||
}
|
||||
|
||||
function test1(): X|Y|int|float|false|null { }
|
||||
function test2(): X|iterable|bool { }
|
||||
function test3(): null|false { }
|
||||
function test4(): ?false { }
|
||||
|
||||
class Test {
|
||||
public X|Y|int $prop;
|
||||
@ -22,6 +31,8 @@ class Test {
|
||||
|
||||
dumpType((new ReflectionFunction('test1'))->getReturnType());
|
||||
dumpType((new ReflectionFunction('test2'))->getReturnType());
|
||||
dumpBCType((new ReflectionFunction('test3'))->getReturnType());
|
||||
dumpBCType((new ReflectionFunction('test4'))->getReturnType());
|
||||
|
||||
$rc = new ReflectionClass(Test::class);
|
||||
$rp = $rc->getProperty('prop');
|
||||
@ -75,6 +86,14 @@ Allows null: false
|
||||
Name: bool
|
||||
String: bool
|
||||
Allows Null: false
|
||||
Type ?false:
|
||||
Name: false
|
||||
String: ?false
|
||||
Allows Null: true
|
||||
Type ?false:
|
||||
Name: false
|
||||
String: ?false
|
||||
Allows Null: true
|
||||
Type X|Y|int:
|
||||
Allows null: false
|
||||
Name: X
|
||||
|
Loading…
Reference in New Issue
Block a user