From bf1c3e19a891950ceb0611141baa9a379892907d Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 30 Sep 2021 05:48:49 +0100 Subject: [PATCH 1/4] Allow 'null' as a standalone type --- .../type_declarations/standalone_null.phpt | 16 ++++++++ .../typed_properties_110.phpt | 13 +++++++ ...typed_return_null_false_without_value.phpt | 14 +++++++ .../typed_return_null_without_value.phpt | 14 +++++++ .../union_types/null_false_union.phpt | 12 ++++++ .../redundant_types/nullable_null.phpt | 2 +- .../union_types/standalone_false.phpt | 2 +- ...standalone_false_implicit_nullability.phpt | 10 +++++ .../union_types/standalone_null.phpt | 7 ++-- .../standalone_nullable_false.phpt | 2 +- Zend/zend_compile.c | 38 ++++++++++--------- ext/reflection/php_reflection.c | 7 +++- .../tests/ReflectionType_possible_types.phpt | 2 + ext/reflection/tests/union_types.phpt | 10 +++++ 14 files changed, 125 insertions(+), 24 deletions(-) create mode 100644 Zend/tests/type_declarations/standalone_null.phpt create mode 100644 Zend/tests/type_declarations/typed_properties_110.phpt create mode 100644 Zend/tests/type_declarations/typed_return_null_false_without_value.phpt create mode 100644 Zend/tests/type_declarations/typed_return_null_without_value.phpt create mode 100644 Zend/tests/type_declarations/union_types/null_false_union.phpt create mode 100644 Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt diff --git a/Zend/tests/type_declarations/standalone_null.phpt b/Zend/tests/type_declarations/standalone_null.phpt new file mode 100644 index 0000000000000..0be4706d3b596 --- /dev/null +++ b/Zend/tests/type_declarations/standalone_null.phpt @@ -0,0 +1,16 @@ +--TEST-- +Null can be used as a standalone type +--FILE-- + +===DONE=== +--EXPECT-- +NULL +===DONE=== diff --git a/Zend/tests/type_declarations/typed_properties_110.phpt b/Zend/tests/type_declarations/typed_properties_110.phpt new file mode 100644 index 0000000000000..72c24a64e7eaf --- /dev/null +++ b/Zend/tests/type_declarations/typed_properties_110.phpt @@ -0,0 +1,13 @@ +--TEST-- +Test typed properties allow null +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/typed_return_null_false_without_value.phpt b/Zend/tests/type_declarations/typed_return_null_false_without_value.phpt new file mode 100644 index 0000000000000..ae785c9a126d3 --- /dev/null +++ b/Zend/tests/type_declarations/typed_return_null_false_without_value.phpt @@ -0,0 +1,14 @@ +--TEST-- +Typed null|false return without value generates compile-time error +--FILE-- + +--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 diff --git a/Zend/tests/type_declarations/typed_return_null_without_value.phpt b/Zend/tests/type_declarations/typed_return_null_without_value.phpt new file mode 100644 index 0000000000000..9152622a4a338 --- /dev/null +++ b/Zend/tests/type_declarations/typed_return_null_without_value.phpt @@ -0,0 +1,14 @@ +--TEST-- +Typed null return without value generates compile-time error +--FILE-- + +--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 diff --git a/Zend/tests/type_declarations/union_types/null_false_union.phpt b/Zend/tests/type_declarations/union_types/null_false_union.phpt new file mode 100644 index 0000000000000..1cf6b46066189 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/null_false_union.phpt @@ -0,0 +1,12 @@ +--TEST-- +Null and false can be used in a union type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt b/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt index 0e0e915bdd223..9c35978576237 100644 --- a/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt +++ b/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt @@ -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 diff --git a/Zend/tests/type_declarations/union_types/standalone_false.phpt b/Zend/tests/type_declarations/union_types/standalone_false.phpt index 76d8c9b7a1ede..9deca723d7d30 100644 --- a/Zend/tests/type_declarations/union_types/standalone_false.phpt +++ b/Zend/tests/type_declarations/union_types/standalone_false.phpt @@ -7,4 +7,4 @@ function test(): false {} ?> --EXPECTF-- -Fatal error: False cannot be used as a standalone type in %s on line %d +Fatal error: false cannot be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt b/Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt new file mode 100644 index 0000000000000..42a92eeb69850 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt @@ -0,0 +1,10 @@ +--TEST-- +False cannot be used as a standalone type even with implicit nullability +--FILE-- + +--EXPECTF-- +Fatal error: false cannot be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/standalone_null.phpt b/Zend/tests/type_declarations/union_types/standalone_null.phpt index d66f27c117a75..a18375d68cb4e 100644 --- a/Zend/tests/type_declarations/union_types/standalone_null.phpt +++ b/Zend/tests/type_declarations/union_types/standalone_null.phpt @@ -1,10 +1,11 @@ --TEST-- -Null cannot be used as a standalone type +Null can be used as a standalone type --FILE-- ---EXPECTF-- -Fatal error: Null cannot be used as a standalone type in %s on line %d +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt index aa1bf34db1eb9..ec8a69778cbc9 100644 --- a/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt +++ b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt @@ -7,4 +7,4 @@ function test(): ?false {} ?> --EXPECTF-- -Fatal error: False cannot be used as a standalone type in %s on line %d +Fatal error: false cannot be marked as nullable since false is not a standalone type in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index cfe44d3f3766f..b6b304e0503f3 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1255,7 +1255,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_string_equals_literal(str, "false")) { zend_string *nullable_str = zend_string_concat2("?", 1, ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(str); return nullable_str; @@ -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 = false; 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; + is_marked_nullable = true; 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,23 @@ 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 ((type_mask & MAY_BE_FALSE) && !ZEND_TYPE_IS_COMPLEX(type) && !(type_mask & ~MAY_BE_FALSE)) { + if (is_marked_nullable) { + zend_error_noreturn(E_COMPILE_ERROR, "false cannot be marked as nullable since false is not a standalone type"); + } else { + zend_error_noreturn(E_COMPILE_ERROR, "false cannot be used as a standalone type"); + } + } + + 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 +6364,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; } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 84ef8bdfbb450..7193910135416 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1342,6 +1342,10 @@ static reflection_type_kind get_type_kind(zend_type type) { if (type_mask_without_null == MAY_BE_BOOL || ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY) { return NAMED_TYPE; } + /* null|false must be a union type */ + if (ZEND_TYPE_PURE_MASK(type) == (MAY_BE_NULL|MAY_BE_FALSE)) { + return UNION_TYPE; + } /* Check that only one bit is set. */ if ((type_mask_without_null & (type_mask_without_null - 1)) != 0) { return UNION_TYPE; @@ -1356,6 +1360,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 +1378,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; diff --git a/ext/reflection/tests/ReflectionType_possible_types.phpt b/ext/reflection/tests/ReflectionType_possible_types.phpt index ccb87254663d3..6a1d08a23d242 100644 --- a/ext/reflection/tests/ReflectionType_possible_types.phpt +++ b/ext/reflection/tests/ReflectionType_possible_types.phpt @@ -12,6 +12,7 @@ $functions = [ function(): array {}, function(): callable {}, function(): iterable {}, + function(): null {}, function(): StdClass {} ]; @@ -30,4 +31,5 @@ string(4) "bool" string(5) "array" string(8) "callable" string(8) "iterable" +string(4) "null" string(8) "StdClass" diff --git a/ext/reflection/tests/union_types.phpt b/ext/reflection/tests/union_types.phpt index 8dbbbdab854a3..ed5107d17b51d 100644 --- a/ext/reflection/tests/union_types.phpt +++ b/ext/reflection/tests/union_types.phpt @@ -15,6 +15,7 @@ function dumpType(ReflectionUnionType $rt) { function test1(): X|Y|int|float|false|null { } function test2(): X|iterable|bool { } +function test3(): null|false { } class Test { public X|Y|int $prop; @@ -22,6 +23,7 @@ class Test { dumpType((new ReflectionFunction('test1'))->getReturnType()); dumpType((new ReflectionFunction('test2'))->getReturnType()); +dumpType((new ReflectionFunction('test3'))->getReturnType()); $rc = new ReflectionClass(Test::class); $rp = $rc->getProperty('prop'); @@ -75,6 +77,14 @@ Allows null: false Name: bool String: bool Allows Null: false +Type false|null: +Allows null: true + Name: false + String: false + Allows Null: false + Name: null + String: null + Allows Null: true Type X|Y|int: Allows null: false Name: X From 3ef48bcd4c3efa0b9100a69ee35b3aeb96b1300c Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sun, 20 Feb 2022 17:21:03 +0000 Subject: [PATCH 2/4] Allow false as standalone type --- .../type_declarations/typed_properties_111.phpt | 13 +++++++++++++ .../union_types/standalone_false.phpt | 7 ++++--- .../standalone_false_implicit_nullability.phpt | 7 ++++--- .../union_types/standalone_nullable_false.phpt | 7 ++++--- Zend/zend_compile.c | 8 -------- .../tests/ReflectionType_possible_types.phpt | 2 ++ ext/reflection/tests/union_types.phpt | 10 ++++++++++ 7 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 Zend/tests/type_declarations/typed_properties_111.phpt diff --git a/Zend/tests/type_declarations/typed_properties_111.phpt b/Zend/tests/type_declarations/typed_properties_111.phpt new file mode 100644 index 0000000000000..52fb8a586a857 --- /dev/null +++ b/Zend/tests/type_declarations/typed_properties_111.phpt @@ -0,0 +1,13 @@ +--TEST-- +Test typed properties allow false +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/union_types/standalone_false.phpt b/Zend/tests/type_declarations/union_types/standalone_false.phpt index 9deca723d7d30..1a31e23d22988 100644 --- a/Zend/tests/type_declarations/union_types/standalone_false.phpt +++ b/Zend/tests/type_declarations/union_types/standalone_false.phpt @@ -1,10 +1,11 @@ --TEST-- -False cannot be used as a standalone type +False can be used as a standalone type --FILE-- ---EXPECTF-- -Fatal error: false cannot be used as a standalone type in %s on line %d +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt b/Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt index 42a92eeb69850..3baac82f7e905 100644 --- a/Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt +++ b/Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt @@ -1,10 +1,11 @@ --TEST-- -False cannot be used as a standalone type even with implicit nullability +False can be used as a standalone type even with implicit nullability --FILE-- ---EXPECTF-- -Fatal error: false cannot be used as a standalone type in %s on line %d +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt index ec8a69778cbc9..b4a3f88451ae0 100644 --- a/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt +++ b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt @@ -1,10 +1,11 @@ --TEST-- -Nullable false cannot be used as a standalone type +Nullable false can be used as a standalone type --FILE-- ---EXPECTF-- -Fatal error: false cannot be marked as nullable since false is not a standalone type in %s on line %d +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b6b304e0503f3..a4d0cb99f4078 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6343,14 +6343,6 @@ static zend_type zend_compile_typename( zend_error_noreturn(E_COMPILE_ERROR, "null cannot be marked as nullable"); } - if ((type_mask & MAY_BE_FALSE) && !ZEND_TYPE_IS_COMPLEX(type) && !(type_mask & ~MAY_BE_FALSE)) { - if (is_marked_nullable) { - zend_error_noreturn(E_COMPILE_ERROR, "false cannot be marked as nullable since false is not a standalone type"); - } else { - zend_error_noreturn(E_COMPILE_ERROR, "false cannot be used as a standalone type"); - } - } - if (is_marked_nullable || force_allow_null) { ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL; type_mask = ZEND_TYPE_PURE_MASK(type); diff --git a/ext/reflection/tests/ReflectionType_possible_types.phpt b/ext/reflection/tests/ReflectionType_possible_types.phpt index 6a1d08a23d242..dd6d39300b590 100644 --- a/ext/reflection/tests/ReflectionType_possible_types.phpt +++ b/ext/reflection/tests/ReflectionType_possible_types.phpt @@ -13,6 +13,7 @@ $functions = [ function(): callable {}, function(): iterable {}, function(): null {}, + function(): false {}, function(): StdClass {} ]; @@ -32,4 +33,5 @@ string(5) "array" string(8) "callable" string(8) "iterable" string(4) "null" +string(5) "false" string(8) "StdClass" diff --git a/ext/reflection/tests/union_types.phpt b/ext/reflection/tests/union_types.phpt index ed5107d17b51d..e5140bd5bdc80 100644 --- a/ext/reflection/tests/union_types.phpt +++ b/ext/reflection/tests/union_types.phpt @@ -16,6 +16,7 @@ function dumpType(ReflectionUnionType $rt) { 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; @@ -24,6 +25,7 @@ class Test { dumpType((new ReflectionFunction('test1'))->getReturnType()); dumpType((new ReflectionFunction('test2'))->getReturnType()); dumpType((new ReflectionFunction('test3'))->getReturnType()); +dumpType((new ReflectionFunction('test4'))->getReturnType()); $rc = new ReflectionClass(Test::class); $rp = $rc->getProperty('prop'); @@ -78,6 +80,14 @@ Allows null: false String: bool Allows Null: false Type false|null: +Allows null: true + Name: false + String: false + Allows Null: false + Name: null + String: null + Allows Null: true +Type false|null: Allows null: true Name: false String: false From f9d5360cda57f68ac541ff492eb8e0ae0150cf5d Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 24 Mar 2022 00:27:17 +0000 Subject: [PATCH 3/4] Address review comments --- Zend/tests/type_declarations/standalone_null.phpt | 2 -- .../tests/type_declarations/typed_properties_110.phpt | 11 +++++++++-- .../tests/type_declarations/typed_properties_111.phpt | 11 +++++++++-- Zend/zend_compile.c | 6 +++--- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Zend/tests/type_declarations/standalone_null.phpt b/Zend/tests/type_declarations/standalone_null.phpt index 0be4706d3b596..d96a1f471e483 100644 --- a/Zend/tests/type_declarations/standalone_null.phpt +++ b/Zend/tests/type_declarations/standalone_null.phpt @@ -10,7 +10,5 @@ function test(null $v): null { var_dump(test(null)); ?> -===DONE=== --EXPECT-- NULL -===DONE=== diff --git a/Zend/tests/type_declarations/typed_properties_110.phpt b/Zend/tests/type_declarations/typed_properties_110.phpt index 72c24a64e7eaf..6a108b4529c66 100644 --- a/Zend/tests/type_declarations/typed_properties_110.phpt +++ b/Zend/tests/type_declarations/typed_properties_110.phpt @@ -7,7 +7,14 @@ class Foo { } $foo = new Foo(); +$foo->value = null; + +try { + $foo->value = 1; +} catch (\TypeError $e) { + echo $e->getMessage(); +} + ?> -===DONE=== --EXPECT-- -===DONE=== +Cannot assign int to property Foo::$value of type null diff --git a/Zend/tests/type_declarations/typed_properties_111.phpt b/Zend/tests/type_declarations/typed_properties_111.phpt index 52fb8a586a857..c23fb72e5d657 100644 --- a/Zend/tests/type_declarations/typed_properties_111.phpt +++ b/Zend/tests/type_declarations/typed_properties_111.phpt @@ -7,7 +7,14 @@ class Foo { } $foo = new Foo(); +$foo->value = false; + +try { + $foo->value = true; +} catch (\TypeError $e) { + echo $e->getMessage(); +} + ?> -===DONE=== --EXPECT-- -===DONE=== +Cannot assign bool to property Foo::$value of type false diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a4d0cb99f4078..3a88b560a5f62 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -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 is_marked_nullable = false; + 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) { - is_marked_nullable = true; + + if (is_marked_nullable) { ast->attr &= ~ZEND_TYPE_NULLABLE; } From cec8ad0d5fab1072ed9335b6868a1c6f60dc8fc2 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 7 Apr 2022 14:00:29 +0100 Subject: [PATCH 4/4] Make false|null be represented as ?false --- Zend/zend_compile.c | 2 +- ext/reflection/php_reflection.c | 4 ---- ext/reflection/tests/union_types.phpt | 27 +++++++++++++-------------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 3a88b560a5f62..444dee5b0d7d4 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1255,7 +1255,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 && !zend_string_equals_literal(str, "false")) { + if (!is_union) { zend_string *nullable_str = zend_string_concat2("?", 1, ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(str); return nullable_str; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 7193910135416..4f30cc2fca700 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1342,10 +1342,6 @@ static reflection_type_kind get_type_kind(zend_type type) { if (type_mask_without_null == MAY_BE_BOOL || ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY) { return NAMED_TYPE; } - /* null|false must be a union type */ - if (ZEND_TYPE_PURE_MASK(type) == (MAY_BE_NULL|MAY_BE_FALSE)) { - return UNION_TYPE; - } /* Check that only one bit is set. */ if ((type_mask_without_null & (type_mask_without_null - 1)) != 0) { return UNION_TYPE; diff --git a/ext/reflection/tests/union_types.phpt b/ext/reflection/tests/union_types.phpt index e5140bd5bdc80..a3ac53b54ab29 100644 --- a/ext/reflection/tests/union_types.phpt +++ b/ext/reflection/tests/union_types.phpt @@ -13,6 +13,13 @@ 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 { } @@ -24,8 +31,8 @@ class Test { dumpType((new ReflectionFunction('test1'))->getReturnType()); dumpType((new ReflectionFunction('test2'))->getReturnType()); -dumpType((new ReflectionFunction('test3'))->getReturnType()); -dumpType((new ReflectionFunction('test4'))->getReturnType()); +dumpBCType((new ReflectionFunction('test3'))->getReturnType()); +dumpBCType((new ReflectionFunction('test4'))->getReturnType()); $rc = new ReflectionClass(Test::class); $rp = $rc->getProperty('prop'); @@ -79,21 +86,13 @@ Allows null: false Name: bool String: bool Allows Null: false -Type false|null: -Allows null: true +Type ?false: Name: false - String: false - Allows Null: false - Name: null - String: null + String: ?false Allows Null: true -Type false|null: -Allows null: true +Type ?false: Name: false - String: false - Allows Null: false - Name: null - String: null + String: ?false Allows Null: true Type X|Y|int: Allows null: false