diff --git a/src/Files/File.php b/src/Files/File.php index ad1ca0b236..b17d71d327 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -1471,6 +1471,7 @@ public function getMethodParameters($stackPtr) case T_TYPE_UNION: case T_TYPE_INTERSECTION: case T_FALSE: + case T_TRUE: case T_NULL: // Part of a type hint or default value. if ($defaultStart === null) { @@ -1692,6 +1693,7 @@ public function getMethodProperties($stackPtr) T_PARENT => T_PARENT, T_STATIC => T_STATIC, T_FALSE => T_FALSE, + T_TRUE => T_TRUE, T_NULL => T_NULL, T_NAMESPACE => T_NAMESPACE, T_NS_SEPARATOR => T_NS_SEPARATOR, @@ -1893,6 +1895,7 @@ public function getMemberProperties($stackPtr) T_SELF => T_SELF, T_PARENT => T_PARENT, T_FALSE => T_FALSE, + T_TRUE => T_TRUE, T_NULL => T_NULL, T_NAMESPACE => T_NAMESPACE, T_NS_SEPARATOR => T_NS_SEPARATOR, diff --git a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php index 4d463f28fb..f5bb6dede2 100644 --- a/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php +++ b/src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php @@ -37,6 +37,7 @@ class LowerCaseTypeSniff implements Sniff 'mixed' => true, 'static' => true, 'false' => true, + 'true' => true, 'null' => true, 'never' => true, ]; diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc index 011adcd530..98d2aa4b56 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc @@ -92,3 +92,5 @@ function intersectionReturnTypes ($var): \Package\ClassName&\Package\Other_Class $arrow = fn (int $a, string $b, bool $c, array $d, Foo\Bar $e) : int => $a * $b; $arrow = fn (Int $a, String $b, BOOL $c, Array $d, Foo\Bar $e) : Float => $a * $b; + +$cl = function (False $a, TRUE $b, Null $c): ?True {} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed index d866101b6f..9f17f0f7b1 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed @@ -92,3 +92,5 @@ function intersectionReturnTypes ($var): \Package\ClassName&\Package\Other_Class $arrow = fn (int $a, string $b, bool $c, array $d, Foo\Bar $e) : int => $a * $b; $arrow = fn (int $a, string $b, bool $c, array $d, Foo\Bar $e) : float => $a * $b; + +$cl = function (false $a, true $b, null $c): ?true {} diff --git a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php index fa05aba6a3..1a19b78b31 100644 --- a/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php +++ b/src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php @@ -67,6 +67,7 @@ public function getErrorList() 82 => 2, 85 => 1, 94 => 5, + 96 => 4, ]; }//end getErrorList() diff --git a/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php b/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php index 8d90734311..45c8cd553a 100644 --- a/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php +++ b/src/Standards/PSR12/Sniffs/Functions/NullableTypeDeclarationSniff.php @@ -27,6 +27,9 @@ class NullableTypeDeclarationSniff implements Sniff T_SELF => true, T_PARENT => true, T_STATIC => true, + T_NULL => true, + T_FALSE => true, + T_TRUE => true, ]; diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc index e3a5a775f2..056d74c3f2 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc @@ -85,3 +85,11 @@ class testInstanceOf() { // PHP 8.0: static return type. function testStatic() : ? static {} + +// PHP 8.2: nullable true/false. +function fooG(): ? true {} +function fooH(): ? + false {} + +// Fatal error: null cannot be marked as nullable, but that's not the concern of this sniff. +function fooI(): ? null {} diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed index 6225d1b337..6cc418d074 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed @@ -83,3 +83,10 @@ class testInstanceOf() { // PHP 8.0: static return type. function testStatic() : ?static {} + +// PHP 8.2: nullable true/false. +function fooG(): ?true {} +function fooH(): ?false {} + +// Fatal error: null cannot be marked as nullable, but that's not the concern of this sniff. +function fooI(): ?null {} diff --git a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php index 2344b6d97d..a74131afd9 100644 --- a/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php +++ b/src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.php @@ -41,6 +41,9 @@ protected function getErrorList() 58 => 2, 59 => 2, 87 => 1, + 90 => 1, + 91 => 1, + 95 => 1, ]; }//end getErrorList() diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 3fc67b0c81..cffac4fd64 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -2803,6 +2803,7 @@ protected function processAdditional() T_PARENT => T_PARENT, T_STATIC => T_STATIC, T_FALSE => T_FALSE, + T_TRUE => T_TRUE, T_NULL => T_NULL, T_NAMESPACE => T_NAMESPACE, T_NS_SEPARATOR => T_NS_SEPARATOR, diff --git a/tests/Core/File/GetMemberPropertiesTest.inc b/tests/Core/File/GetMemberPropertiesTest.inc index f40b6021f4..51d11c2f1a 100644 --- a/tests/Core/File/GetMemberPropertiesTest.inc +++ b/tests/Core/File/GetMemberPropertiesTest.inc @@ -217,11 +217,11 @@ $anon = class() { public ?int|float $unionTypesNullable; /* testPHP8PseudoTypeNull */ - // Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. + // PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. public null $pseudoTypeNull; /* testPHP8PseudoTypeFalse */ - // Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. + // PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. public false $pseudoTypeFalse; /* testPHP8PseudoTypeFalseAndBool */ @@ -298,7 +298,22 @@ $anon = class() { // Intentional fatal error - types which are not allowed for intersection type, but that's not the concern of the method. public int&string $illegalIntersectionType; - /* testPHP81NulltableIntersectionType */ + /* testPHP81NullableIntersectionType */ // Intentional fatal error - nullability is not allowed with intersection type, but that's not the concern of the method. public ?Foo&Bar $nullableIntersectionType; }; + +$anon = class() { + /* testPHP82PseudoTypeTrue */ + public true $pseudoTypeTrue; + + /* testPHP82NullablePseudoTypeTrue */ + static protected ?true $pseudoTypeNullableTrue; + + /* testPHP82PseudoTypeTrueInUnion */ + private int|string|true $pseudoTypeTrueInUnion; + + /* testPHP82PseudoTypeFalseAndTrue */ + // Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method. + readonly true|FALSE $pseudoTypeFalseAndTrue; +}; diff --git a/tests/Core/File/GetMemberPropertiesTest.php b/tests/Core/File/GetMemberPropertiesTest.php index 934d8e2890..84dc29565a 100644 --- a/tests/Core/File/GetMemberPropertiesTest.php +++ b/tests/Core/File/GetMemberPropertiesTest.php @@ -764,6 +764,7 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => 'Foo&Bar', 'nullable_type' => false, ], @@ -774,6 +775,7 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => 'Foo&Bar&Baz', 'nullable_type' => false, ], @@ -784,20 +786,67 @@ public function dataGetMemberProperties() 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => 'int&string', 'nullable_type' => false, ], ], [ - '/* testPHP81NulltableIntersectionType */', + '/* testPHP81NullableIntersectionType */', [ 'scope' => 'public', 'scope_specified' => true, 'is_static' => false, + 'is_readonly' => false, 'type' => '?Foo&Bar', 'nullable_type' => true, ], ], + [ + '/* testPHP82PseudoTypeTrue */', + [ + 'scope' => 'public', + 'scope_specified' => true, + 'is_static' => false, + 'is_readonly' => false, + 'type' => 'true', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP82NullablePseudoTypeTrue */', + [ + 'scope' => 'protected', + 'scope_specified' => true, + 'is_static' => true, + 'is_readonly' => false, + 'type' => '?true', + 'nullable_type' => true, + ], + ], + [ + '/* testPHP82PseudoTypeTrueInUnion */', + [ + 'scope' => 'private', + 'scope_specified' => true, + 'is_static' => false, + 'is_readonly' => false, + 'type' => 'int|string|true', + 'nullable_type' => false, + ], + ], + [ + '/* testPHP82PseudoTypeFalseAndTrue */', + [ + 'scope' => 'public', + 'scope_specified' => false, + 'is_static' => false, + 'is_readonly' => true, + 'type' => 'true|FALSE', + 'nullable_type' => false, + ], + ], + ]; }//end dataGetMemberProperties() diff --git a/tests/Core/File/GetMethodParametersTest.inc b/tests/Core/File/GetMethodParametersTest.inc index dc46549140..546a2e5b94 100644 --- a/tests/Core/File/GetMethodParametersTest.inc +++ b/tests/Core/File/GetMethodParametersTest.inc @@ -66,11 +66,11 @@ function unionTypesAllPseudoTypes(false|mixed|self|parent|iterable|Resource $var $closure = function (?int|float $number) {}; /* testPHP8PseudoTypeNull */ -// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. +// PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. function pseudoTypeNull(null $var = null) {} /* testPHP8PseudoTypeFalse */ -// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. +// PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. function pseudoTypeFalse(false $var = false) {} /* testPHP8PseudoTypeFalseAndBool */ @@ -162,3 +162,10 @@ $closure = function (string&int $numeric_string) {}; /* testPHP81NullableIntersectionTypes */ // Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method. $closure = function (?Foo&Bar $object) {}; + +/* testPHP82PseudoTypeTrue */ +function pseudoTypeTrue(?true $var = true) {} + +/* testPHP82PseudoTypeFalseAndTrue */ +// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method. +function pseudoTypeFalseAndTrue(true|false $var = true) {} diff --git a/tests/Core/File/GetMethodParametersTest.php b/tests/Core/File/GetMethodParametersTest.php index ba4d754485..9e0f9af289 100644 --- a/tests/Core/File/GetMethodParametersTest.php +++ b/tests/Core/File/GetMethodParametersTest.php @@ -1124,6 +1124,54 @@ public function testPHP81NullableIntersectionTypes() }//end testPHP81NullableIntersectionTypes() + /** + * Verify recognition of PHP 8.2 stand-alone `true` type. + * + * @return void + */ + public function testPHP82PseudoTypeTrue() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => '?true $var = true', + 'default' => 'true', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => '?true', + 'nullable_type' => true, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP82PseudoTypeTrue() + + + /** + * Verify recognition of PHP 8.2 type declaration with (illegal) type false combined with type true. + * + * @return void + */ + public function testPHP82PseudoTypeFalseAndTrue() + { + $expected = []; + $expected[0] = [ + 'name' => '$var', + 'content' => 'true|false $var = true', + 'default' => 'true', + 'has_attributes' => false, + 'pass_by_reference' => false, + 'variable_length' => false, + 'type_hint' => 'true|false', + 'nullable_type' => false, + ]; + + $this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP82PseudoTypeFalseAndTrue() + + /** * Test helper. * diff --git a/tests/Core/File/GetMethodPropertiesTest.inc b/tests/Core/File/GetMethodPropertiesTest.inc index 0c592369a5..5ebee67947 100644 --- a/tests/Core/File/GetMethodPropertiesTest.inc +++ b/tests/Core/File/GetMethodPropertiesTest.inc @@ -102,11 +102,11 @@ function unionTypesAllPseudoTypes($var) : false|MIXED|self|parent|static|iterabl $closure = function () use($a) :?int|float {}; /* testPHP8PseudoTypeNull */ -// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. +// PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method. function pseudoTypeNull(): null {} /* testPHP8PseudoTypeFalse */ -// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. +// PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method. function pseudoTypeFalse(): false {} /* testPHP8PseudoTypeFalseAndBool */ @@ -150,3 +150,10 @@ $closure = function (): string&int {}; /* testPHP81NullableIntersectionTypes */ // Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method. $closure = function (): ?Foo&Bar {}; + +/* testPHP82PseudoTypeTrue */ +function pseudoTypeTrue(): ?true {} + +/* testPHP82PseudoTypeFalseAndTrue */ +// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method. +function pseudoTypeFalseAndTrue(): true|false {} diff --git a/tests/Core/File/GetMethodPropertiesTest.php b/tests/Core/File/GetMethodPropertiesTest.php index 66f4eea3ea..944cf3d15d 100644 --- a/tests/Core/File/GetMethodPropertiesTest.php +++ b/tests/Core/File/GetMethodPropertiesTest.php @@ -889,6 +889,52 @@ public function testPHP81NullableIntersectionTypes() }//end testPHP81NullableIntersectionTypes() + /** + * Verify recognition of PHP 8.2 stand-alone `true` type. + * + * @return void + */ + public function testPHP82PseudoTypeTrue() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => '?true', + 'nullable_return_type' => true, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP82PseudoTypeTrue() + + + /** + * Verify recognition of PHP 8.2 type declaration with (illegal) type false combined with type true. + * + * @return void + */ + public function testPHP82PseudoTypeFalseAndTrue() + { + $expected = [ + 'scope' => 'public', + 'scope_specified' => false, + 'return_type' => 'true|false', + 'nullable_return_type' => false, + 'is_abstract' => false, + 'is_final' => false, + 'is_static' => false, + 'has_body' => true, + ]; + + $this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected); + + }//end testPHP82PseudoTypeFalseAndTrue() + + /** * Test helper. * diff --git a/tests/Core/Tokenizer/BitwiseOrTest.inc b/tests/Core/Tokenizer/BitwiseOrTest.inc index bfdbdc18c1..843f6c57d7 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.inc +++ b/tests/Core/Tokenizer/BitwiseOrTest.inc @@ -129,6 +129,15 @@ $obj->fn($something | $else); /* testTypeUnionNonArrowFunctionDeclaration */ function &fn(int|false $something) {} +/* testTypeUnionPHP82TrueFirst */ +function trueTypeParam(true|null $param) {} + +/* testTypeUnionPHP82TrueMiddle */ +function trueTypeReturn($param): array|true|null {} + +/* testTypeUnionPHP82TrueLast */ +$closure = function ($param): array|true {} + /* testLiveCoding */ // Intentional parse error. This has to be the last test in the file. return function( type| diff --git a/tests/Core/Tokenizer/BitwiseOrTest.php b/tests/Core/Tokenizer/BitwiseOrTest.php index d56e7340aa..0ca94b3f28 100644 --- a/tests/Core/Tokenizer/BitwiseOrTest.php +++ b/tests/Core/Tokenizer/BitwiseOrTest.php @@ -130,6 +130,9 @@ public function dataTypeUnion() ['/* testTypeUnionArrowParam */'], ['/* testTypeUnionArrowReturnType */'], ['/* testTypeUnionNonArrowFunctionDeclaration */'], + ['/* testTypeUnionPHP82TrueFirst */'], + ['/* testTypeUnionPHP82TrueMiddle */'], + ['/* testTypeUnionPHP82TrueLast */'], ]; }//end dataTypeUnion()