diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index c55b6ba2fe..7d906b459f 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -84,6 +84,8 @@ public static function resolve($type, ?ClassReflection $classReflection): Type return new VoidType(); } elseif ($type === 'object') { return new ObjectWithoutClassType(); + } elseif ($type === 'true') { + return new ConstantBooleanType(true); } elseif ($type === 'false') { return new ConstantBooleanType(false); } elseif ($type === 'null') { diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index faea820712..a194e82657 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -29,6 +29,8 @@ private static function getTypeObjectFromTypehint(string $typeString, ?string $s return new BooleanType(); case 'false': return new ConstantBooleanType(false); + case 'true': + return new ConstantBooleanType(true); case 'string': return new StringType(); case 'float': @@ -118,6 +120,9 @@ public static function decideTypeFromReflection( if (str_ends_with(strtolower($reflectionTypeString), '\\mixed')) { $reflectionTypeString = 'mixed'; } + if (str_ends_with(strtolower($reflectionTypeString), '\\true')) { + $reflectionTypeString = 'true'; + } if (str_ends_with(strtolower($reflectionTypeString), '\\false')) { $reflectionTypeString = 'false'; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 3ea85cba74..24b72ff641 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -982,6 +982,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7788.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7809.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/composer-non-empty-array-after-unset.php'); + + if (PHP_VERSION_ID >= 80200) { + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/true-typehint.php'); + } yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-6000.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/prestashop-breakdowns-empty-array.php'); diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 48a67391bb..7c152e5d2e 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -273,4 +273,25 @@ public function testIntersectionTypes(int $phpVersion, array $errors): void $this->analyse([__DIR__ . '/data/intersection-types.php'], $errors); } + public function dataTrueTypes(): array + { + return [ + [ + 80200, + [], + ], + ]; + } + + /** + * @dataProvider dataTrueTypes + * @param mixed[] $errors + */ + public function testTrueTypehint(int $phpVersion, array $errors): void + { + $this->phpVersionId = $phpVersion; + + $this->analyse([__DIR__ . '/data/true-typehint.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/true-typehint.php b/tests/PHPStan/Rules/Functions/data/true-typehint.php new file mode 100644 index 0000000000..da84a9cb26 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/true-typehint.php @@ -0,0 +1,8 @@ += 8.2 + +namespace NativeTrueType; + +function alwaysTrue(): true +{ + return true; +} diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index 42b7d3fb8f..e7d23e3c74 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -291,4 +291,22 @@ public function testEnums(): void ]); } + public function dataTrueTypes(): array + { + return [ + [80200, []], + ]; + } + + /** + * @dataProvider dataTrueTypes + * @param mixed[] $errors + */ + public function testTrueTypehint(int $phpVersion, array $errors): void + { + $this->phpVersionId = $phpVersion; + + $this->analyse([__DIR__ . '/data/true-typehint.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/true-typehint.php b/tests/PHPStan/Rules/Methods/data/true-typehint.php new file mode 100644 index 0000000000..780c0a776a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/true-typehint.php @@ -0,0 +1,44 @@ += 8.2 + +namespace NativeTrueType; + +use function PHPStan\Testing\assertType; + +class Truthy { + public true $truthy = true; + + public function foo(true $v): true { + assertType('true', $v); + } + + function trueUnion(true|null $trueUnion): void + { + assertType('true|null', $trueUnion); + + if (is_null($trueUnion)) { + assertType('null', $trueUnion); + return; + } + + if (is_bool($trueUnion)) { + assertType('true', $trueUnion); + return; + } + + assertType('*NEVER*', $trueUnion); + } + + function trueUnionReturn(): true|null + { + if (rand(1, 0)) { + return true; + } + return null; + } +} + +function foo(Truthy $truthy) { + assertType('true', $truthy->truthy); + assertType('true', $truthy->foo(true)); + assertType('true|null', $truthy->trueUnionReturn()); +}