diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_true_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_true_type.phpt new file mode 100644 index 0000000000000..6a184cf659a8b --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_true_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +true type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type true cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/literal_types/false_no_coercion.phpt b/Zend/tests/type_declarations/literal_types/false_no_coercion.phpt new file mode 100644 index 0000000000000..61900f993c0e5 --- /dev/null +++ b/Zend/tests/type_declarations/literal_types/false_no_coercion.phpt @@ -0,0 +1,28 @@ +--TEST-- +No coercion should be applied to type false +--FILE-- +getMessage(), \PHP_EOL; +} +try { + test(''); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + test([]); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +--EXPECTF-- +test(): Argument #1 ($v) must be of type false, int given, called in %s on line %d +test(): Argument #1 ($v) must be of type false, string given, called in %s on line %d +test(): Argument #1 ($v) must be of type false, array given, called in %s on line %d diff --git a/Zend/tests/type_declarations/literal_types/false_no_coercion_on_overload.phpt b/Zend/tests/type_declarations/literal_types/false_no_coercion_on_overload.phpt new file mode 100644 index 0000000000000..8ee291cdd32e2 --- /dev/null +++ b/Zend/tests/type_declarations/literal_types/false_no_coercion_on_overload.phpt @@ -0,0 +1,31 @@ +--TEST-- +No coercion should be applied to type false even if it's an overide +--FILE-- +foo(0)); +try { + var_dump($c->foo(0)); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +--EXPECT-- +bool(false) +C::foo(): Return value must be of type array|false, int returned diff --git a/Zend/tests/type_declarations/literal_types/false_standalone.phpt b/Zend/tests/type_declarations/literal_types/false_standalone.phpt new file mode 100644 index 0000000000000..f7dd4ad384202 --- /dev/null +++ b/Zend/tests/type_declarations/literal_types/false_standalone.phpt @@ -0,0 +1,14 @@ +--TEST-- +False can be used as a standalone type +--FILE-- + +--EXPECT-- +bool(false) diff --git a/Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt b/Zend/tests/type_declarations/literal_types/false_standalone_implicit_nullability.phpt similarity index 50% rename from Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt rename to Zend/tests/type_declarations/literal_types/false_standalone_implicit_nullability.phpt index 3baac82f7e905..c5fe5f76cf79d 100644 --- a/Zend/tests/type_declarations/union_types/standalone_false_implicit_nullability.phpt +++ b/Zend/tests/type_declarations/literal_types/false_standalone_implicit_nullability.phpt @@ -3,9 +3,11 @@ False can be used as a standalone type even with implicit nullability --FILE-- -===DONE=== --EXPECT-- -===DONE=== +bool(false) +NULL diff --git a/Zend/tests/type_declarations/literal_types/true_no_coercion.phpt b/Zend/tests/type_declarations/literal_types/true_no_coercion.phpt new file mode 100644 index 0000000000000..85d6c90cd057c --- /dev/null +++ b/Zend/tests/type_declarations/literal_types/true_no_coercion.phpt @@ -0,0 +1,34 @@ +--TEST-- +No coercion should be applied to type true +--FILE-- +getMessage(), \PHP_EOL; +} +try { + test('1'); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + test([1]); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + test(new stdClass()); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +--EXPECTF-- +test(): Argument #1 ($v) must be of type true, int given, called in %s on line %d +test(): Argument #1 ($v) must be of type true, string given, called in %s on line %d +test(): Argument #1 ($v) must be of type true, array given, called in %s on line %d +test(): Argument #1 ($v) must be of type true, stdClass given, called in %s on line %d diff --git a/Zend/tests/type_declarations/literal_types/true_no_coercion_on_overload.phpt b/Zend/tests/type_declarations/literal_types/true_no_coercion_on_overload.phpt new file mode 100644 index 0000000000000..93f2a8a9f11f7 --- /dev/null +++ b/Zend/tests/type_declarations/literal_types/true_no_coercion_on_overload.phpt @@ -0,0 +1,31 @@ +--TEST-- +No coercion should be applied to type true even if it's an overide +--FILE-- +foo(1)); +try { + var_dump($c->foo(1)); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +--EXPECT-- +bool(true) +C::foo(): Return value must be of type array|true, int returned diff --git a/Zend/tests/type_declarations/literal_types/true_standalone.phpt b/Zend/tests/type_declarations/literal_types/true_standalone.phpt new file mode 100644 index 0000000000000..d2db891d9787f --- /dev/null +++ b/Zend/tests/type_declarations/literal_types/true_standalone.phpt @@ -0,0 +1,14 @@ +--TEST-- +true can be used as a standalone type +--FILE-- + +--EXPECT-- +bool(true) diff --git a/Zend/tests/type_declarations/literal_types/true_standalone_implicit_nullability.phpt b/Zend/tests/type_declarations/literal_types/true_standalone_implicit_nullability.phpt new file mode 100644 index 0000000000000..d460742689e23 --- /dev/null +++ b/Zend/tests/type_declarations/literal_types/true_standalone_implicit_nullability.phpt @@ -0,0 +1,13 @@ +--TEST-- +true can be used as a standalone type even with implicit nullability +--FILE-- + +--EXPECT-- +bool(true) +NULL diff --git a/Zend/tests/type_declarations/typed_properties_112.phpt b/Zend/tests/type_declarations/typed_properties_112.phpt new file mode 100644 index 0000000000000..5d54aa3234fe6 --- /dev/null +++ b/Zend/tests/type_declarations/typed_properties_112.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test typed properties allow true +--FILE-- +value = false; +} catch (\TypeError $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Cannot assign bool to property Foo::$value of type true diff --git a/Zend/tests/type_declarations/union_types/redundant_types/bool_and_true.phpt b/Zend/tests/type_declarations/union_types/redundant_types/bool_and_true.phpt new file mode 100644 index 0000000000000..cf93425250ffd --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/bool_and_true.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both bool and true in a union +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type true is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/bool_instead_false_and_true.phpt b/Zend/tests/type_declarations/union_types/redundant_types/bool_instead_false_and_true.phpt new file mode 100644 index 0000000000000..7386f4aa76735 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/bool_instead_false_and_true.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both false and true in a union instead of bool +--FILE-- + +--EXPECTF-- +Fatal error: Type contains both true and false, bool should be used instead in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/bool_instead_true_and_false.phpt b/Zend/tests/type_declarations/union_types/redundant_types/bool_instead_true_and_false.phpt new file mode 100644 index 0000000000000..3dacf3e5dbdf5 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/bool_instead_true_and_false.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both true and false in a union instead of bool +--FILE-- + +--EXPECTF-- +Fatal error: Type contains both true and false, bool should be used instead 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 deleted file mode 100644 index 1a31e23d22988..0000000000000 --- a/Zend/tests/type_declarations/union_types/standalone_false.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -False can be used as a standalone type ---FILE-- - -===DONE=== ---EXPECT-- -===DONE=== diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0ec50dc58922c..c30e9d2f73194 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -216,6 +216,7 @@ typedef struct _builtin_type_info { static const builtin_type_info builtin_types[] = { {ZEND_STRL("null"), IS_NULL}, + {ZEND_STRL("true"), IS_TRUE}, {ZEND_STRL("false"), IS_FALSE}, {ZEND_STRL("int"), IS_LONG}, {ZEND_STRL("float"), IS_DOUBLE}, @@ -1246,6 +1247,8 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL), /* is_intersection */ false); } else if (type_mask & MAY_BE_FALSE) { str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE), /* is_intersection */ false); + } else if (type_mask & MAY_BE_TRUE) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_TRUE), /* is_intersection */ false); } if (type_mask & MAY_BE_VOID) { str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID), /* is_intersection */ false); @@ -6226,6 +6229,11 @@ static zend_type zend_compile_typename( zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(overlap_type_str)); } + if ( ((ZEND_TYPE_PURE_MASK(type) & MAY_BE_TRUE) && (single_type_mask == MAY_BE_FALSE)) + || ((ZEND_TYPE_PURE_MASK(type) & MAY_BE_FALSE) && (single_type_mask == MAY_BE_TRUE)) ) { + zend_error_noreturn(E_COMPILE_ERROR, + "Type contains both true and false, bool should be used instead"); + } ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type); ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK; diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 7c346077a3adc..ef67daad74bbf 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -578,6 +578,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_VOID, "void") \ _(ZEND_STR_NEVER, "never") \ _(ZEND_STR_FALSE, "false") \ + _(ZEND_STR_TRUE, "true") \ _(ZEND_STR_NULL_LOWERCASE, "null") \ _(ZEND_STR_MIXED, "mixed") \ _(ZEND_STR_TRAVERSABLE, "Traversable") \ diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 5c9bcc731b475..0b1510375d527 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3151,6 +3151,8 @@ ZEND_METHOD(ReflectionUnionType, getTypes) } if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { append_type_mask(return_value, MAY_BE_BOOL); + } else if (type_mask & MAY_BE_TRUE) { + append_type_mask(return_value, MAY_BE_TRUE); } else if (type_mask & MAY_BE_FALSE) { append_type_mask(return_value, MAY_BE_FALSE); } diff --git a/ext/reflection/tests/ReflectionType_possible_types.phpt b/ext/reflection/tests/ReflectionType_possible_types.phpt index 9162b71df98df..45a2d438e712c 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(): null {}, function(): false {}, + function(): true {}, function(): StdClass {} ]; @@ -32,4 +33,5 @@ string(5) "array" string(8) "callable" string(4) "null" string(5) "false" +string(4) "true" string(8) "StdClass" diff --git a/ext/reflection/tests/union_types.phpt b/ext/reflection/tests/union_types.phpt index e670567712f3b..22de6b8dfe40c 100644 --- a/ext/reflection/tests/union_types.phpt +++ b/ext/reflection/tests/union_types.phpt @@ -24,6 +24,9 @@ function test1(): X|Y|int|float|false|null { } function test2(): X|iterable|bool { } function test3(): null|false { } function test4(): ?false { } +function test5(): X|iterable|true { } +function test6(): null|true { } +function test7(): ?true { } class Test { public X|Y|int $prop; @@ -33,6 +36,9 @@ dumpType((new ReflectionFunction('test1'))->getReturnType()); dumpType((new ReflectionFunction('test2'))->getReturnType()); dumpBCType((new ReflectionFunction('test3'))->getReturnType()); dumpBCType((new ReflectionFunction('test4'))->getReturnType()); +dumpType((new ReflectionFunction('test5'))->getReturnType()); +dumpBCType((new ReflectionFunction('test6'))->getReturnType()); +dumpBCType((new ReflectionFunction('test7'))->getReturnType()); $rc = new ReflectionClass(Test::class); $rp = $rc->getProperty('prop'); @@ -97,6 +103,28 @@ Type ?false: Name: false String: ?false Allows Null: true +Type X|Traversable|array|true: +Allows null: false + Name: X + String: X + Allows Null: false + Name: Traversable + String: Traversable + Allows Null: false + Name: array + String: array + Allows Null: false + Name: true + String: true + Allows Null: false +Type ?true: + Name: true + String: ?true + Allows Null: true +Type ?true: + Name: true + String: ?true + Allows Null: true Type X|Y|int: Allows null: false Name: X