From 2777e5f09d3eaa8e7377d928f9c5bbc9790b17a2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 14 Aug 2025 12:51:43 +0200 Subject: [PATCH 01/18] Report bool properties as too wide when only used with true/false --- src/Php/PhpVersions.php | 5 ++ .../IfConstantConditionRuleTest.php | 11 ++++ .../TooWidePropertyTypeRuleTest.php | 26 +++++++++ .../Rules/TooWideTypehints/data/bug-13384.php | 55 +++++++++++++++++++ .../TooWideTypehints/data/bug-13384b.php | 32 +++++++++++ 5 files changed, 129 insertions(+) create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-13384.php create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-13384b.php diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index 96bf233209..cc2ea94c98 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -43,4 +43,9 @@ public function supportsNamedArgumentAfterUnpackedArgument(): TrinaryLogic return IntegerRangeType::fromInterval(80100, null)->isSuperTypeOf($this->phpVersions)->result; } + public function supportsTrueAndFalseStandaloneType(): TrinaryLogic + { + return IntegerRangeType::fromInterval(80200, null)->isSuperTypeOf($this->phpVersions)->result; + } + } diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index eee1500835..b5c06eca37 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -182,4 +182,15 @@ public function testBug8926(): void $this->analyse([__DIR__ . '/data/bug-8926.php'], []); } + public function testBug13384b(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/../TooWideTypehints/data/bug-13384b.php'], [ + [ + 'If condition is always false.', + 23, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index e590aea5eb..837e0e6979 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -59,4 +59,30 @@ public function testBug11667(): void $this->analyse([__DIR__ . '/data/bug-11667.php'], []); } + #[RequiresPhp('>= 8.2')] + public function testBug13384(): void + { + $this->analyse([__DIR__ . '/data/bug-13384.php'], [ + [ + 'Static property Bug13384\ShutdownHandlerFalseDefault::$registered (bool) is never assigned true so it can be removed from the property type.', + 9, + ], + [ + 'Static property Bug13384\ShutdownHandlerTrueDefault::$registered (bool) is never assigned false so it can be removed from the property type.', + 34, + ], + ]); + } + + #[RequiresPhp('< 8.2')] + public function testBug13384NoStandaloneTrueFalse(): void + { + $this->analyse([__DIR__ . '/data/bug-13384.php'], []); + } + + public function testBug13384b(): void + { + $this->analyse([__DIR__ . '/data/bug-13384b.php'], []); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384.php new file mode 100644 index 0000000000..45079faae8 --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384.php @@ -0,0 +1,55 @@ += 8.2 + +declare(strict_types=1); + +namespace Bug13384b; + +use function register_shutdown_function; + +final class ShutdownHandlerFooBar +{ + private static false $registered = false; + private static string $message = ''; + + public static function setMessage(string $message): void + { + self::register(); + + self::$message = $message; + } + + private static function register(): void + { + if (self::$registered) { + return; + } + + register_shutdown_function(static function (): void + { + print self::$message; + }); + } +} From 80f11ad1b7da145ec4c242f6b3563f8ba36b97cd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 14 Aug 2025 13:27:43 +0200 Subject: [PATCH 02/18] fix build --- build/ignore-by-php-version.neon.php | 4 ++++ build/never-assigned-true-false.neon | 6 ++++++ build/phpstan.neon | 1 + 3 files changed, 11 insertions(+) create mode 100644 build/never-assigned-true-false.neon diff --git a/build/ignore-by-php-version.neon.php b/build/ignore-by-php-version.neon.php index d9bb8452e3..9a49863692 100644 --- a/build/ignore-by-php-version.neon.php +++ b/build/ignore-by-php-version.neon.php @@ -11,6 +11,10 @@ $includes[] = __DIR__ . '/readonly-property.neon'; } +if (PHP_VERSION_ID >= 80200) { + $includes[] = __DIR__ . '/never-assigned-true-false.neon'; +} + if (PHP_VERSION_ID >= 70400) { $includes[] = __DIR__ . '/ignore-gte-php7.4-errors.neon'; } diff --git a/build/never-assigned-true-false.neon b/build/never-assigned-true-false.neon new file mode 100644 index 0000000000..e9cddf94bf --- /dev/null +++ b/build/never-assigned-true-false.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + identifier: property.unusedType + message: '#\(bool\) is never assigned (false|true) so it can be removed from the property type#' + path: ../tests/PHPStan/*RuleTest.php diff --git a/build/phpstan.neon b/build/phpstan.neon index 1e79bf303a..5277f7df3e 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -115,6 +115,7 @@ parameters: count: 1 path: ../src/Diagnose/PHPStanDiagnoseExtension.php - '#^Short ternary operator is not allowed#' + reportStaticMethodSignatures: true tmpDir: %rootDir%/tmp stubFiles: From d6fcae47fc95b827caaeaec68567b5677ea91231 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 14 Aug 2025 13:53:19 +0200 Subject: [PATCH 03/18] fix php7.4 build --- tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index b5c06eca37..2bf3017098 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -182,6 +182,7 @@ public function testBug8926(): void $this->analyse([__DIR__ . '/data/bug-8926.php'], []); } + #[RequiresPhp('>= 8.0')] public function testBug13384b(): void { $this->treatPhpDocTypesAsCertain = true; From 28531fad2ad67441f6dc23356ce454a5997409c0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 30 Aug 2025 11:17:08 +0200 Subject: [PATCH 04/18] simplify --- build/ignore-by-php-version.neon.php | 4 ---- build/never-assigned-true-false.neon | 6 ------ build/phpstan.neon | 1 - src/Php/PhpVersions.php | 5 ----- src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php | 2 +- src/Rules/TooWideTypehints/TooWideTypeCheck.php | 5 +++-- 6 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 build/never-assigned-true-false.neon diff --git a/build/ignore-by-php-version.neon.php b/build/ignore-by-php-version.neon.php index 9a49863692..d9bb8452e3 100644 --- a/build/ignore-by-php-version.neon.php +++ b/build/ignore-by-php-version.neon.php @@ -11,10 +11,6 @@ $includes[] = __DIR__ . '/readonly-property.neon'; } -if (PHP_VERSION_ID >= 80200) { - $includes[] = __DIR__ . '/never-assigned-true-false.neon'; -} - if (PHP_VERSION_ID >= 70400) { $includes[] = __DIR__ . '/ignore-gte-php7.4-errors.neon'; } diff --git a/build/never-assigned-true-false.neon b/build/never-assigned-true-false.neon deleted file mode 100644 index e9cddf94bf..0000000000 --- a/build/never-assigned-true-false.neon +++ /dev/null @@ -1,6 +0,0 @@ -parameters: - ignoreErrors: - - - identifier: property.unusedType - message: '#\(bool\) is never assigned (false|true) so it can be removed from the property type#' - path: ../tests/PHPStan/*RuleTest.php diff --git a/build/phpstan.neon b/build/phpstan.neon index 5277f7df3e..1e79bf303a 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -115,7 +115,6 @@ parameters: count: 1 path: ../src/Diagnose/PHPStanDiagnoseExtension.php - '#^Short ternary operator is not allowed#' - reportStaticMethodSignatures: true tmpDir: %rootDir%/tmp stubFiles: diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index cc2ea94c98..96bf233209 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -43,9 +43,4 @@ public function supportsNamedArgumentAfterUnpackedArgument(): TrinaryLogic return IntegerRangeType::fromInterval(80100, null)->isSuperTypeOf($this->phpVersions)->result; } - public function supportsTrueAndFalseStandaloneType(): TrinaryLogic - { - return IntegerRangeType::fromInterval(80200, null)->isSuperTypeOf($this->phpVersions)->result; - } - } diff --git a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php index 4be9a14c9f..eea671b0de 100644 --- a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php @@ -57,7 +57,7 @@ public function processNode(Node $node, Scope $scope): array $propertyReflection = $classReflection->getNativeProperty($propertyName); $propertyType = $propertyReflection->getWritableType(); - if (!$propertyType instanceof UnionType) { + if (!$propertyType instanceof UnionType && !$propertyType->isBoolean()->yes()) { continue; } foreach ($this->extensionProvider->getExtensions() as $extension) { diff --git a/src/Rules/TooWideTypehints/TooWideTypeCheck.php b/src/Rules/TooWideTypehints/TooWideTypeCheck.php index 8ea9a0cea4..753ac0f610 100644 --- a/src/Rules/TooWideTypehints/TooWideTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideTypeCheck.php @@ -26,7 +26,7 @@ final class TooWideTypeCheck */ public function checkProperty( ClassPropertyNode $property, - UnionType $propertyType, + Type $propertyType, string $propertyDescription, Type $assignedType, ): array @@ -34,7 +34,8 @@ public function checkProperty( $errors = []; $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType, $assignedType); - foreach ($propertyType->getTypes() as $type) { + $propertyTypes = $propertyType instanceof UnionType ? $propertyType->getTypes() : $propertyType->getFiniteTypes(); + foreach ($propertyTypes as $type) { if (!$type->isSuperTypeOf($assignedType)->no()) { continue; } From e771ad2cbca75643e5c8ceea7a29308008e2f27d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 30 Aug 2025 11:24:44 +0200 Subject: [PATCH 05/18] Update AnalyserIntegrationTest.php --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index f6d7dceff2..17d4370d07 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -440,11 +440,13 @@ public function testBug4715(): void public function testBug4734(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-4734.php'); - $this->assertCount(3, $errors); + $this->assertCount(5, $errors); // could be 3 - $this->assertSame('Unsafe access to private property Bug4734\Foo::$httpMethodParameterOverride through static::.', $errors[0]->getMessage()); - $this->assertSame('Access to an undefined static property static(Bug4734\Foo)::$httpMethodParameterOverride3.', $errors[1]->getMessage()); - $this->assertSame('Access to an undefined property Bug4734\Foo::$httpMethodParameterOverride4.', $errors[2]->getMessage()); + $this->assertSame('Static property Bug4734\Foo::$httpMethodParameterOverride (bool) is never assigned false so it can be removed from the property type.', $errors[0]->getMessage()); // should not error + $this->assertSame('Property Bug4734\Foo::$httpMethodParameterOverride2 (bool) is never assigned false so it can be removed from the property type.', $errors[1]->getMessage()); // should not error + $this->assertSame('Unsafe access to private property Bug4734\Foo::$httpMethodParameterOverride through static::.', $errors[2]->getMessage()); + $this->assertSame('Access to an undefined static property static(Bug4734\Foo)::$httpMethodParameterOverride3.', $errors[3]->getMessage()); + $this->assertSame('Access to an undefined property Bug4734\Foo::$httpMethodParameterOverride4.', $errors[4]->getMessage()); } public function testBug5231(): void From 45f620506a13b5607757dd24a1d6d93605fddbc3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 30 Aug 2025 11:34:04 +0200 Subject: [PATCH 06/18] test methods --- .../TooWideTypehints/TooWideTypeCheck.php | 15 +++-- .../TooWideFunctionReturnTypehintRuleTest.php | 14 ++++ .../TooWideMethodReturnTypehintRuleTest.php | 22 +++++++ .../TooWideTypehints/data/bug-13384c.php | 64 +++++++++++++++++++ 4 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php diff --git a/src/Rules/TooWideTypehints/TooWideTypeCheck.php b/src/Rules/TooWideTypehints/TooWideTypeCheck.php index 753ac0f610..f60ff315ec 100644 --- a/src/Rules/TooWideTypehints/TooWideTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideTypeCheck.php @@ -69,7 +69,7 @@ public function checkFunction( ): array { $functionReturnType = TypeUtils::resolveLateResolvableTypes($functionReturnType); - if (!$functionReturnType instanceof UnionType) { + if (!$functionReturnType instanceof UnionType && !$functionReturnType->isBoolean()->yes()) { return []; } $statementResult = $node->getStatementResult(); @@ -82,22 +82,22 @@ public function checkFunction( return []; } - $returnTypes = []; + $functionReturnTypes = []; foreach ($returnStatements as $returnStatement) { $returnNode = $returnStatement->getReturnNode(); if ($returnNode->expr === null) { - $returnTypes[] = new VoidType(); + $functionReturnTypes[] = new VoidType(); continue; } - $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); + $functionReturnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); } if (!$statementResult->isAlwaysTerminating()) { - $returnTypes[] = new VoidType(); + $functionReturnTypes[] = new VoidType(); } - $returnType = TypeCombinator::union(...$returnTypes); + $returnType = TypeCombinator::union(...$functionReturnTypes); if ( $returnType->isConstantScalarValue()->yes() @@ -115,7 +115,8 @@ public function checkFunction( } $messages = []; - foreach ($functionReturnType->getTypes() as $type) { + $functionReturnTypes = $functionReturnType instanceof UnionType ? $functionReturnType->getTypes() : $functionReturnType->getFiniteTypes(); + foreach ($functionReturnTypes as $type) { if (!$type->isSuperTypeOf($returnType)->no()) { continue; } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index cce5fd6e0e..a4d2a42a55 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -66,4 +66,18 @@ public function testBug10312a(): void $this->analyse([__DIR__ . '/data/bug-10312a.php'], []); } + public function testBug13384c(): void + { + $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ + [ + 'Function Bug13384c\doFoo() never returns true so it can be removed from the return type.', + 5, + ], + [ + 'Function Bug13384c\doFoo2() never returns false so it can be removed from the return type.', + 9, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index 677e4570be..bbdc7ea476 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -218,4 +218,26 @@ public function testBug10312d(): void $this->analyse([__DIR__ . '/data/bug-10312d.php'], []); } + public function testBug13384c(): void + { + $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ + [ + 'Method Bug13384c\Bug13384c::doBar() never returns true so it can be removed from the return type.', + 33, + ], + [ + 'Method Bug13384c\Bug13384c::doBar2() never returns false so it can be removed from the return type.', + 37, + ], + [ + 'Method Bug13384c\Bug13384Static::doBar() never returns true so it can be removed from the return type.', + 50, + ], + [ + 'Method Bug13384c\Bug13384Static::doBar2() never returns false so it can be removed from the return type.', + 54, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php new file mode 100644 index 0000000000..bd0d57f9fc --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php @@ -0,0 +1,64 @@ + Date: Sat, 30 Aug 2025 11:51:58 +0200 Subject: [PATCH 07/18] add bleeding-edge feature toggle --- conf/bleedingEdge.neon | 1 + conf/config.neon | 1 + conf/parametersSchema.neon | 1 + .../TooWideFunctionReturnTypehintRule.php | 4 ++++ .../TooWideMethodReturnTypehintRule.php | 3 +++ src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php | 9 +++++++-- src/Rules/TooWideTypehints/TooWideTypeCheck.php | 8 ++++++-- tests/PHPStan/Analyser/nsrt/assert-docblock.php | 1 + .../TooWideFunctionReturnTypehintRuleTest.php | 10 +++++++++- .../TooWideMethodReturnTypehintRuleTest.php | 10 +++++++++- .../TooWideTypehints/TooWidePropertyTypeRuleTest.php | 8 ++++++++ 11 files changed, 50 insertions(+), 6 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 8cccad6c7e..b5f69aee86 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -10,3 +10,4 @@ parameters: internalTag: true newStaticInAbstractClassStaticMethod: true checkExtensionsForComparisonOperators: true + reportTooWideBool: true diff --git a/conf/config.neon b/conf/config.neon index 9d7c9e061b..d2bd278a3c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -34,6 +34,7 @@ parameters: internalTag: false newStaticInAbstractClassStaticMethod: false checkExtensionsForComparisonOperators: false + reportTooWideBool: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 73e7f56b0f..eb7b172709 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -37,6 +37,7 @@ parametersSchema: internalTag: bool() newStaticInAbstractClassStaticMethod: bool() checkExtensionsForComparisonOperators: bool() + reportTooWideBool: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index 738203a6bc..5fd9043096 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\RegisteredRule; use PHPStan\Node\FunctionReturnStatementsNode; use PHPStan\Rules\Rule; @@ -18,6 +19,8 @@ final class TooWideFunctionReturnTypehintRule implements Rule public function __construct( private TooWideTypeCheck $check, + #[AutowiredParameter(ref: '%featureToggles.reportTooWideBool%')] + private bool $reportTooWideBool, ) { } @@ -40,6 +43,7 @@ public function processNode(Node $node, Scope $scope): array $function->getName(), ), false, + $this->reportTooWideBool, ); } diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 6b2c8f81b1..2ac524c2f5 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -21,6 +21,8 @@ public function __construct( #[AutowiredParameter(ref: '%checkTooWideReturnTypesInProtectedAndPublicMethods%')] private bool $checkProtectedAndPublicMethods, private TooWideTypeCheck $check, + #[AutowiredParameter(ref: '%featureToggles.reportTooWideBool%')] + private bool $reportTooWideBool, ) { } @@ -59,6 +61,7 @@ public function processNode(Node $node, Scope $scope): array $method->getName(), ), !$isFirstDeclaration && !$method->isPrivate(), + $this->reportTooWideBool, ); } diff --git a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php index eea671b0de..0585b96a08 100644 --- a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\RegisteredRule; use PHPStan\Node\ClassPropertiesNode; use PHPStan\Reflection\PropertyReflection; @@ -26,6 +27,8 @@ public function __construct( private ReadWritePropertiesExtensionProvider $extensionProvider, private PropertyReflectionFinder $propertyReflectionFinder, private TooWideTypeCheck $check, + #[AutowiredParameter(ref: '%featureToggles.reportTooWideBool%')] + private bool $reportTooWideBool, ) { } @@ -57,8 +60,10 @@ public function processNode(Node $node, Scope $scope): array $propertyReflection = $classReflection->getNativeProperty($propertyName); $propertyType = $propertyReflection->getWritableType(); - if (!$propertyType instanceof UnionType && !$propertyType->isBoolean()->yes()) { - continue; + if (!$propertyType instanceof UnionType) { + if (!$propertyType->isBoolean()->yes() || !$this->reportTooWideBool) { + continue; + } } foreach ($this->extensionProvider->getExtensions() as $extension) { if ($extension->isAlwaysRead($propertyReflection, $propertyName)) { diff --git a/src/Rules/TooWideTypehints/TooWideTypeCheck.php b/src/Rules/TooWideTypehints/TooWideTypeCheck.php index f60ff315ec..5fbfb50fe4 100644 --- a/src/Rules/TooWideTypehints/TooWideTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideTypeCheck.php @@ -66,11 +66,15 @@ public function checkFunction( Type $functionReturnType, string $functionDescription, bool $checkDescendantClass, + bool $reportTooWideBool, ): array { $functionReturnType = TypeUtils::resolveLateResolvableTypes($functionReturnType); - if (!$functionReturnType instanceof UnionType && !$functionReturnType->isBoolean()->yes()) { - return []; + + if (!$functionReturnType instanceof UnionType) { + if (!$functionReturnType->isBoolean()->yes() || !$reportTooWideBool) { + return []; + } } $statementResult = $node->getStatementResult(); if ($statementResult->hasYield()) { diff --git a/tests/PHPStan/Analyser/nsrt/assert-docblock.php b/tests/PHPStan/Analyser/nsrt/assert-docblock.php index 459b925ca3..e671f640c3 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-docblock.php +++ b/tests/PHPStan/Analyser/nsrt/assert-docblock.php @@ -13,6 +13,7 @@ function validateStringArray(array $arr) : void {} /** * @param mixed[] $arr * @phpstan-assert-if-true string[] $arr + * @return true */ function validateStringArrayIfTrue(array $arr) : bool { return true; diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index a4d2a42a55..a4d6072882 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -11,9 +11,11 @@ class TooWideFunctionReturnTypehintRuleTest extends RuleTestCase { + private bool $reportTooWideBool = false; + protected function getRule(): Rule { - return new TooWideFunctionReturnTypehintRule(new TooWideTypeCheck()); + return new TooWideFunctionReturnTypehintRule(new TooWideTypeCheck(), $this->reportTooWideBool); } public function testRule(): void @@ -68,6 +70,7 @@ public function testBug10312a(): void public function testBug13384c(): void { + $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ [ 'Function Bug13384c\doFoo() never returns true so it can be removed from the return type.', @@ -80,4 +83,9 @@ public function testBug13384c(): void ]); } + public function testBug13384cOff(): void + { + $this->analyse([__DIR__ . '/data/bug-13384c.php'], []); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index bbdc7ea476..04a4d56864 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -15,9 +15,11 @@ class TooWideMethodReturnTypehintRuleTest extends RuleTestCase private bool $checkProtectedAndPublicMethods = true; + private bool $reportTooWideBool = false; + protected function getRule(): Rule { - return new TooWideMethodReturnTypehintRule($this->checkProtectedAndPublicMethods, new TooWideTypeCheck()); + return new TooWideMethodReturnTypehintRule($this->checkProtectedAndPublicMethods, new TooWideTypeCheck(), $this->reportTooWideBool); } public function testPrivate(): void @@ -220,6 +222,7 @@ public function testBug10312d(): void public function testBug13384c(): void { + $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ [ 'Method Bug13384c\Bug13384c::doBar() never returns true so it can be removed from the return type.', @@ -240,4 +243,9 @@ public function testBug13384c(): void ]); } + public function testBug13384cOff(): void + { + $this->analyse([__DIR__ . '/data/bug-13384c.php'], []); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index 837e0e6979..e6c2120e86 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -13,6 +13,7 @@ */ class TooWidePropertyTypeRuleTest extends RuleTestCase { + private bool $reportTooWideBool = false; protected function getRule(): Rule { @@ -20,6 +21,7 @@ protected function getRule(): Rule new DirectReadWritePropertiesExtensionProvider([]), new PropertyReflectionFinder(), new TooWideTypeCheck(), + $this->reportTooWideBool, ); } @@ -81,6 +83,12 @@ public function testBug13384NoStandaloneTrueFalse(): void } public function testBug13384b(): void + { + $this->reportTooWideBool = true; + $this->analyse([__DIR__ . '/data/bug-13384b.php'], []); + } + + public function testBug13384bOff(): void { $this->analyse([__DIR__ . '/data/bug-13384b.php'], []); } From a3df808bbc4e67c5860b3ecf99260b5d6a02cb6a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 30 Aug 2025 12:06:59 +0200 Subject: [PATCH 08/18] fix build --- .../FileReadTrapStreamWrapper.php | 5 ++++ .../Analyser/AnalyserIntegrationTest.php | 26 ++++++++++++------- .../PHPStan/Analyser/nsrt/assert-docblock.php | 1 - .../LogicalXorConstantConditionRuleTest.php | 4 +-- .../TooWideFunctionThrowTypeRuleTest.php | 1 + .../TooWidePropertyHookThrowTypeRuleTest.php | 1 + .../TooWidePropertyTypeRuleTest.php | 2 ++ .../Variables/CompactVariablesRuleTest.php | 1 + 8 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php b/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php index 4a35d07ca2..047a6808e2 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php +++ b/src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php @@ -212,6 +212,9 @@ public function stream_eof(): bool return $this->readFromFile; } + /** + * @return true + */ public function stream_flush(): bool { return true; @@ -254,6 +257,8 @@ public function stream_seek($offset, $whence): bool * @param int $option * @param int $arg1 * @param int $arg2 + * + * @return false */ public function stream_set_option($option, $arg1, $arg2): bool { diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 17d4370d07..fb76111856 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1098,15 +1098,23 @@ public function testBug8376(): void public function testAssertDocblock(): void { $errors = $this->runAnalyse(__DIR__ . '/nsrt/assert-docblock.php'); - $this->assertCount(4, $errors); - $this->assertSame('Call to method AssertDocblock\A::testInt() with string will always evaluate to false.', $errors[0]->getMessage()); - $this->assertSame(218, $errors[0]->getLine()); - $this->assertSame('Call to method AssertDocblock\A::testNotInt() with string will always evaluate to true.', $errors[1]->getMessage()); - $this->assertSame(224, $errors[1]->getLine()); - $this->assertSame('Call to method AssertDocblock\A::testInt() with int will always evaluate to true.', $errors[2]->getMessage()); - $this->assertSame(232, $errors[2]->getLine()); - $this->assertSame('Call to method AssertDocblock\A::testNotInt() with int will always evaluate to false.', $errors[3]->getMessage()); - $this->assertSame(238, $errors[3]->getLine()); + $this->assertCount(8, $errors); + $this->assertSame('Function AssertDocblock\validateStringArrayIfTrue() never returns false so it can be removed from the return type.', $errors[0]->getMessage()); + $this->assertSame(17, $errors[0]->getLine()); + $this->assertSame('Function AssertDocblock\validateStringArrayIfFalse() never returns true so it can be removed from the return type.', $errors[1]->getMessage()); + $this->assertSame(25, $errors[1]->getLine()); + $this->assertSame('Function AssertDocblock\validateStringOrIntArray() never returns true so it can be removed from the return type.', $errors[2]->getMessage()); + $this->assertSame(34, $errors[2]->getLine()); + $this->assertSame('Function AssertDocblock\validateStringOrNonEmptyIntArray() never returns true so it can be removed from the return type.', $errors[3]->getMessage()); + $this->assertSame(44, $errors[3]->getLine()); + $this->assertSame('Call to method AssertDocblock\A::testInt() with string will always evaluate to false.', $errors[4]->getMessage()); + $this->assertSame(218, $errors[4]->getLine()); + $this->assertSame('Call to method AssertDocblock\A::testNotInt() with string will always evaluate to true.', $errors[5]->getMessage()); + $this->assertSame(224, $errors[5]->getLine()); + $this->assertSame('Call to method AssertDocblock\A::testInt() with int will always evaluate to true.', $errors[6]->getMessage()); + $this->assertSame(232, $errors[6]->getLine()); + $this->assertSame('Call to method AssertDocblock\A::testNotInt() with int will always evaluate to false.', $errors[7]->getMessage()); + $this->assertSame(238, $errors[7]->getLine()); } #[RequiresPhp('>= 8.0')] diff --git a/tests/PHPStan/Analyser/nsrt/assert-docblock.php b/tests/PHPStan/Analyser/nsrt/assert-docblock.php index e671f640c3..459b925ca3 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-docblock.php +++ b/tests/PHPStan/Analyser/nsrt/assert-docblock.php @@ -13,7 +13,6 @@ function validateStringArray(array $arr) : void {} /** * @param mixed[] $arr * @phpstan-assert-if-true string[] $arr - * @return true */ function validateStringArrayIfTrue(array $arr) : bool { return true; diff --git a/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php index 99e45cf088..7f9146b058 100644 --- a/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php @@ -11,8 +11,6 @@ class LogicalXorConstantConditionRuleTest extends RuleTestCase { - private bool $reportAlwaysTrueInLastCondition = false; - protected function getRule(): TRule { return new LogicalXorConstantConditionRule( @@ -26,7 +24,7 @@ protected function getRule(): TRule $this->shouldTreatPhpDocTypesAsCertain(), ), $this->shouldTreatPhpDocTypesAsCertain(), - $this->reportAlwaysTrueInLastCondition, + false, true, ); } diff --git a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php index 8de4ae7bed..697ab2cc3c 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php @@ -11,6 +11,7 @@ class TooWideFunctionThrowTypeRuleTest extends RuleTestCase { + /** @var true */ private bool $implicitThrows = true; protected function getRule(): Rule diff --git a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php index da40cc6d80..9b3056b93d 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php @@ -13,6 +13,7 @@ class TooWidePropertyHookThrowTypeRuleTest extends RuleTestCase { + /** @var true */ private bool $implicitThrows = true; protected function getRule(): Rule diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index e6c2120e86..1400d089da 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -13,6 +13,7 @@ */ class TooWidePropertyTypeRuleTest extends RuleTestCase { + private bool $reportTooWideBool = false; protected function getRule(): Rule @@ -64,6 +65,7 @@ public function testBug11667(): void #[RequiresPhp('>= 8.2')] public function testBug13384(): void { + $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384.php'], [ [ 'Static property Bug13384\ShutdownHandlerFalseDefault::$registered (bool) is never assigned true so it can be removed from the property type.', diff --git a/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php b/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php index ccd8f350fc..33e7b51ae3 100644 --- a/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php +++ b/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php @@ -11,6 +11,7 @@ class CompactVariablesRuleTest extends RuleTestCase { + /** @var true */ private bool $checkMaybeUndefinedVariables; protected function getRule(): Rule From dc0d1e2246ce7adaaa3c85b16395bd9158a16373 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 30 Aug 2025 12:16:15 +0200 Subject: [PATCH 09/18] simplify --- src/Rules/TooWideTypehints/TooWideTypeCheck.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Rules/TooWideTypehints/TooWideTypeCheck.php b/src/Rules/TooWideTypehints/TooWideTypeCheck.php index 5fbfb50fe4..139dd31bb0 100644 --- a/src/Rules/TooWideTypehints/TooWideTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideTypeCheck.php @@ -86,22 +86,22 @@ public function checkFunction( return []; } - $functionReturnTypes = []; + $returnTypes = []; foreach ($returnStatements as $returnStatement) { $returnNode = $returnStatement->getReturnNode(); if ($returnNode->expr === null) { - $functionReturnTypes[] = new VoidType(); + $returnTypes[] = new VoidType(); continue; } - $functionReturnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); + $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); } if (!$statementResult->isAlwaysTerminating()) { - $functionReturnTypes[] = new VoidType(); + $returnTypes[] = new VoidType(); } - $returnType = TypeCombinator::union(...$functionReturnTypes); + $returnType = TypeCombinator::union(...$returnTypes); if ( $returnType->isConstantScalarValue()->yes() From 7f84ee15cd162473b90422f75161090ba6e9bd64 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 30 Aug 2025 12:25:08 +0200 Subject: [PATCH 10/18] tests no longer depend on php version --- .../Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index 1400d089da..65b23bad46 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -62,7 +62,6 @@ public function testBug11667(): void $this->analyse([__DIR__ . '/data/bug-11667.php'], []); } - #[RequiresPhp('>= 8.2')] public function testBug13384(): void { $this->reportTooWideBool = true; @@ -78,12 +77,6 @@ public function testBug13384(): void ]); } - #[RequiresPhp('< 8.2')] - public function testBug13384NoStandaloneTrueFalse(): void - { - $this->analyse([__DIR__ . '/data/bug-13384.php'], []); - } - public function testBug13384b(): void { $this->reportTooWideBool = true; From b2f02f04a9e40520c3a59d52eb4267aaf844db7c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 1 Sep 2025 17:26:27 +0200 Subject: [PATCH 11/18] simplify tests --- .../Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php | 5 +---- .../Exceptions/TooWidePropertyHookThrowTypeRuleTest.php | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php index 697ab2cc3c..58aea82f3a 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php @@ -11,12 +11,9 @@ class TooWideFunctionThrowTypeRuleTest extends RuleTestCase { - /** @var true */ - private bool $implicitThrows = true; - protected function getRule(): Rule { - return new TooWideFunctionThrowTypeRule(new TooWideThrowTypeCheck($this->implicitThrows)); + return new TooWideFunctionThrowTypeRule(new TooWideThrowTypeCheck(true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php index 9b3056b93d..2db813da37 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php @@ -13,12 +13,9 @@ class TooWidePropertyHookThrowTypeRuleTest extends RuleTestCase { - /** @var true */ - private bool $implicitThrows = true; - protected function getRule(): Rule { - return new TooWidePropertyHookThrowTypeRule(self::getContainer()->getByType(FileTypeMapper::class), new TooWideThrowTypeCheck($this->implicitThrows)); + return new TooWidePropertyHookThrowTypeRule(self::getContainer()->getByType(FileTypeMapper::class), new TooWideThrowTypeCheck(true)); } #[RequiresPhp('>= 8.4')] From 1da7b680e6535e06153fc4f1aaadadd35c147a2d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 1 Sep 2025 17:28:31 +0200 Subject: [PATCH 12/18] Update CompactVariablesRuleTest.php --- tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php b/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php index 33e7b51ae3..030992cc17 100644 --- a/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php +++ b/tests/PHPStan/Rules/Variables/CompactVariablesRuleTest.php @@ -11,17 +11,13 @@ class CompactVariablesRuleTest extends RuleTestCase { - /** @var true */ - private bool $checkMaybeUndefinedVariables; - protected function getRule(): Rule { - return new CompactVariablesRule($this->checkMaybeUndefinedVariables); + return new CompactVariablesRule(true); } public function testCompactVariables(): void { - $this->checkMaybeUndefinedVariables = true; $this->analyse([__DIR__ . '/data/compact-variables.php'], [ [ 'Call to function compact() contains undefined variable $bar.', From fafbb0a4c0fb654c5f4dac3c1296862c8137b44c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 2 Sep 2025 09:26:52 +0200 Subject: [PATCH 13/18] use constructor arg --- .../TooWideFunctionReturnTypehintRule.php | 4 ---- .../TooWideMethodReturnTypehintRule.php | 3 --- src/Rules/TooWideTypehints/TooWideTypeCheck.php | 11 +++++++++-- .../TooWideArrowFunctionReturnTypehintRuleTest.php | 2 +- .../TooWideClosureReturnTypehintRuleTest.php | 2 +- .../TooWideFunctionReturnTypehintRuleTest.php | 2 +- .../TooWideMethodReturnTypehintRuleTest.php | 2 +- .../TooWideTypehints/TooWidePropertyTypeRuleTest.php | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index 5fd9043096..738203a6bc 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -4,7 +4,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; -use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\RegisteredRule; use PHPStan\Node\FunctionReturnStatementsNode; use PHPStan\Rules\Rule; @@ -19,8 +18,6 @@ final class TooWideFunctionReturnTypehintRule implements Rule public function __construct( private TooWideTypeCheck $check, - #[AutowiredParameter(ref: '%featureToggles.reportTooWideBool%')] - private bool $reportTooWideBool, ) { } @@ -43,7 +40,6 @@ public function processNode(Node $node, Scope $scope): array $function->getName(), ), false, - $this->reportTooWideBool, ); } diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 2ac524c2f5..6b2c8f81b1 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -21,8 +21,6 @@ public function __construct( #[AutowiredParameter(ref: '%checkTooWideReturnTypesInProtectedAndPublicMethods%')] private bool $checkProtectedAndPublicMethods, private TooWideTypeCheck $check, - #[AutowiredParameter(ref: '%featureToggles.reportTooWideBool%')] - private bool $reportTooWideBool, ) { } @@ -61,7 +59,6 @@ public function processNode(Node $node, Scope $scope): array $method->getName(), ), !$isFirstDeclaration && !$method->isPrivate(), - $this->reportTooWideBool, ); } diff --git a/src/Rules/TooWideTypehints/TooWideTypeCheck.php b/src/Rules/TooWideTypehints/TooWideTypeCheck.php index 139dd31bb0..bd3c0d96b0 100644 --- a/src/Rules/TooWideTypehints/TooWideTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideTypeCheck.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\TooWideTypehints; +use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\ClassPropertyNode; use PHPStan\Node\FunctionReturnStatementsNode; @@ -21,6 +22,13 @@ final class TooWideTypeCheck { + public function __construct( + #[AutowiredParameter(ref: '%featureToggles.reportTooWideBool%')] + private bool $reportTooWideBool, + ) + { + } + /** * @return list */ @@ -66,13 +74,12 @@ public function checkFunction( Type $functionReturnType, string $functionDescription, bool $checkDescendantClass, - bool $reportTooWideBool, ): array { $functionReturnType = TypeUtils::resolveLateResolvableTypes($functionReturnType); if (!$functionReturnType instanceof UnionType) { - if (!$functionReturnType->isBoolean()->yes() || !$reportTooWideBool) { + if (!$functionReturnType->isBoolean()->yes() || !$this->reportTooWideBool) { return []; } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php index daaac7cb2a..e41af50e31 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php @@ -14,7 +14,7 @@ class TooWideArrowFunctionReturnTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { return new TooWideArrowFunctionReturnTypehintRule( - new TooWideTypeCheck(), + new TooWideTypeCheck(true), ); } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php index e7dfbc6d94..33700aacc3 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php @@ -14,7 +14,7 @@ class TooWideClosureReturnTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { return new TooWideClosureReturnTypehintRule( - new TooWideTypeCheck(), + new TooWideTypeCheck(true), ); } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index a4d6072882..0fcb88b47c 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -15,7 +15,7 @@ class TooWideFunctionReturnTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new TooWideFunctionReturnTypehintRule(new TooWideTypeCheck(), $this->reportTooWideBool); + return new TooWideFunctionReturnTypehintRule(new TooWideTypeCheck($this->reportTooWideBool)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index 04a4d56864..015aff19cf 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -19,7 +19,7 @@ class TooWideMethodReturnTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new TooWideMethodReturnTypehintRule($this->checkProtectedAndPublicMethods, new TooWideTypeCheck(), $this->reportTooWideBool); + return new TooWideMethodReturnTypehintRule($this->checkProtectedAndPublicMethods, new TooWideTypeCheck($this->reportTooWideBool)); } public function testPrivate(): void diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index 65b23bad46..a48cf08fc1 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -21,7 +21,7 @@ protected function getRule(): Rule return new TooWidePropertyTypeRule( new DirectReadWritePropertiesExtensionProvider([]), new PropertyReflectionFinder(), - new TooWideTypeCheck(), + new TooWideTypeCheck($this->reportTooWideBool), $this->reportTooWideBool, ); } From 1cbc46dd89da38913ca98776134e7c3c774a4caf Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 5 Sep 2025 10:55:02 +0200 Subject: [PATCH 14/18] rework --- src/Php/PhpVersions.php | 5 ++ .../TooWideFunctionReturnTypehintRule.php | 5 +- .../TooWideMethodReturnTypehintRule.php | 5 +- .../TooWidePropertyTypeRule.php | 14 ++--- .../TooWideTypehints/TooWideTypeCheck.php | 58 ++++++++++++++++--- .../TooWideFunctionReturnTypehintRuleTest.php | 28 ++++++++- .../TooWideMethodReturnTypehintRuleTest.php | 29 +++++++++- .../TooWidePropertyTypeRuleTest.php | 1 - .../TooWideTypehints/data/bug-13384c.php | 42 ++++++++++++++ 9 files changed, 164 insertions(+), 23 deletions(-) diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index 96bf233209..cc2ea94c98 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -43,4 +43,9 @@ public function supportsNamedArgumentAfterUnpackedArgument(): TrinaryLogic return IntegerRangeType::fromInterval(80100, null)->isSuperTypeOf($this->phpVersions)->result; } + public function supportsTrueAndFalseStandaloneType(): TrinaryLogic + { + return IntegerRangeType::fromInterval(80200, null)->isSuperTypeOf($this->phpVersions)->result; + } + } diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index 738203a6bc..d52d2e1238 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -31,15 +31,16 @@ public function processNode(Node $node, Scope $scope): array { $function = $node->getFunctionReflection(); - $functionReturnType = $function->getReturnType(); return $this->check->checkFunction( $node, - $functionReturnType, + $function->getReturnType(), + $function->getPhpDocReturnType(), sprintf( 'Function %s()', $function->getName(), ), false, + $scope, ); } diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 6b2c8f81b1..b14b7dcb81 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -49,16 +49,17 @@ public function processNode(Node $node, Scope $scope): array } } - $methodReturnType = $method->getReturnType(); return $this->check->checkFunction( $node, - $methodReturnType, + $method->getReturnType(), + $method->getPhpDocReturnType(), sprintf( 'Method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName(), ), !$isFirstDeclaration && !$method->isPrivate(), + $scope, ); } diff --git a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php index 0585b96a08..9c6fcee1bf 100644 --- a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php @@ -4,7 +4,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; -use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\RegisteredRule; use PHPStan\Node\ClassPropertiesNode; use PHPStan\Reflection\PropertyReflection; @@ -12,7 +11,6 @@ use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\Rules\Rule; use PHPStan\Type\TypeCombinator; -use PHPStan\Type\UnionType; use function count; use function sprintf; @@ -27,8 +25,6 @@ public function __construct( private ReadWritePropertiesExtensionProvider $extensionProvider, private PropertyReflectionFinder $propertyReflectionFinder, private TooWideTypeCheck $check, - #[AutowiredParameter(ref: '%featureToggles.reportTooWideBool%')] - private bool $reportTooWideBool, ) { } @@ -60,11 +56,13 @@ public function processNode(Node $node, Scope $scope): array $propertyReflection = $classReflection->getNativeProperty($propertyName); $propertyType = $propertyReflection->getWritableType(); - if (!$propertyType instanceof UnionType) { - if (!$propertyType->isBoolean()->yes() || !$this->reportTooWideBool) { - continue; - } + $phpdocType = $propertyReflection->getPhpDocType(); + + $propertyType = $this->check->findTypeToCheck($propertyType, $phpdocType, $scope); + if ($propertyType === null) { + continue; } + foreach ($this->extensionProvider->getExtensions() as $extension) { if ($extension->isAlwaysRead($propertyReflection, $propertyName)) { continue 2; diff --git a/src/Rules/TooWideTypehints/TooWideTypeCheck.php b/src/Rules/TooWideTypehints/TooWideTypeCheck.php index bd3c0d96b0..5be43cccb3 100644 --- a/src/Rules/TooWideTypehints/TooWideTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideTypeCheck.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\TooWideTypehints; +use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\ClassPropertyNode; @@ -11,6 +12,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypehintHelper; use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -71,18 +73,18 @@ public function checkProperty( */ public function checkFunction( MethodReturnStatementsNode|FunctionReturnStatementsNode $node, - Type $functionReturnType, + Type $nativeFunctionReturnType, + Type $phpdocFunctionReturnType, string $functionDescription, bool $checkDescendantClass, + Scope $scope, ): array { - $functionReturnType = TypeUtils::resolveLateResolvableTypes($functionReturnType); - - if (!$functionReturnType instanceof UnionType) { - if (!$functionReturnType->isBoolean()->yes() || !$this->reportTooWideBool) { - return []; - } + $functionReturnType = $this->findTypeToCheck($nativeFunctionReturnType, $phpdocFunctionReturnType, $scope); + if ($functionReturnType === null) { + return []; } + $statementResult = $node->getStatementResult(); if ($statementResult->hasYield()) { return []; @@ -178,4 +180,46 @@ public function checkAnonymousFunction( return $messages; } + /** + * Returns null when type should not be checked, e.g. because it would be too annoying. + */ + public function findTypeToCheck( + Type $nativeType, + Type $phpdocType, + Scope $scope, + ): ?Type + { + $combinedType = TypeUtils::resolveLateResolvableTypes(TypehintHelper::decideType($nativeType, $phpdocType)); + if ($combinedType instanceof UnionType) { + return $combinedType; + } + + if (!$this->reportTooWideBool) { + return null; + } + + if ( + $phpdocType->isBoolean()->yes() + ) { + if ( + !$phpdocType->isTrue()->yes() + && !$phpdocType->isFalse()->yes() + ) { + return $combinedType; + } + } elseif ( + $scope->getPhpVersion()->supportsTrueAndFalseStandaloneType()->yes() + && $nativeType->isBoolean()->yes() + ) { + if ( + !$nativeType->isTrue()->yes() + && !$nativeType->isFalse()->yes() + ) { + return $combinedType; + } + } + + return null; + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index 0fcb88b47c..0ca27eeb77 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use PHPUnit\Framework\Attributes\RequiresPhp; /** * @extends RuleTestCase @@ -68,7 +69,8 @@ public function testBug10312a(): void $this->analyse([__DIR__ . '/data/bug-10312a.php'], []); } - public function testBug13384c(): void + #[RequiresPhp('>= 8.2')] + public function testBug13384cPhp82(): void { $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ @@ -80,6 +82,30 @@ public function testBug13384c(): void 'Function Bug13384c\doFoo2() never returns false so it can be removed from the return type.', 9, ], + [ + 'Function Bug13384c\doFooPhpdoc() never returns false so it can be removed from the return type.', + 93, + ], + [ + 'Function Bug13384c\doFooPhpdoc2() never returns true so it can be removed from the return type.', + 100, + ], + ]); + } + + #[RequiresPhp('< 8.2')] + public function testBug13384cPrePhp82(): void + { + $this->reportTooWideBool = true; + $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ + [ + 'Function Bug13384c\doFooPhpdoc() never returns false so it can be removed from the return type.', + 93, + ], + [ + 'Function Bug13384c\doFooPhpdoc2() never returns true so it can be removed from the return type.', + 100, + ], ]); } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index 015aff19cf..27fb2da518 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -220,6 +220,7 @@ public function testBug10312d(): void $this->analyse([__DIR__ . '/data/bug-10312d.php'], []); } + #[RequiresPhp('>= 8.2')] public function testBug13384c(): void { $this->reportTooWideBool = true; @@ -232,13 +233,37 @@ public function testBug13384c(): void 'Method Bug13384c\Bug13384c::doBar2() never returns false so it can be removed from the return type.', 37, ], + [ + 'Method Bug13384c\Bug13384c::doBarPhpdoc() never returns false so it can be removed from the return type.', + 55, + ], [ 'Method Bug13384c\Bug13384Static::doBar() never returns true so it can be removed from the return type.', - 50, + 62, ], [ 'Method Bug13384c\Bug13384Static::doBar2() never returns false so it can be removed from the return type.', - 54, + 66, + ], + [ + 'Method Bug13384c\Bug13384Static::doBarPhpdoc() never returns false so it can be removed from the return type.', + 84, + ], + ]); + } + + #[RequiresPhp('< 8.2')] + public function testBug13384cPrePhp82(): void + { + $this->reportTooWideBool = true; + $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ + [ + 'Method Bug13384c\Bug13384c::doBarPhpdoc() never returns false so it can be removed from the return type.', + 55, + ], + [ + 'Method Bug13384c\Bug13384Static::doBarPhpdoc() never returns false so it can be removed from the return type.', + 84, ], ]); } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index a48cf08fc1..c15f589e80 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule new DirectReadWritePropertiesExtensionProvider([]), new PropertyReflectionFinder(), new TooWideTypeCheck($this->reportTooWideBool), - $this->reportTooWideBool, ); } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php index bd0d57f9fc..0d0ad4efbf 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384c.php @@ -44,6 +44,18 @@ private function doBar3(): bool { } return false; } + + private function doBarMixed() { + return true; + } + + /** + * @return bool + */ + private function doBarPhpdoc() { + return true; + } + } class Bug13384Static { @@ -61,4 +73,34 @@ private static function doBar3(): bool { } return false; } + + private static function doBarMixed() { + return true; + } + + /** + * @return bool + */ + private static function doBarPhpdoc() { + return true; + } + +} + +/** + * @return bool + */ +function doFooPhpdoc() { + return true; +} + +/** + * @return bool + */ +function doFooPhpdoc2() { + return false; +} + +function doFooMixed() { + return true; } From 95479093d623335af245897a81f2b48c31946c53 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 5 Sep 2025 11:00:02 +0200 Subject: [PATCH 15/18] test properties per php version --- .../TooWidePropertyTypeRuleTest.php | 19 +++++++++++ .../data/bug-13384-phpdoc.php | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-13384-phpdoc.php diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index c15f589e80..8f7e5aff13 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -61,6 +61,7 @@ public function testBug11667(): void $this->analyse([__DIR__ . '/data/bug-11667.php'], []); } + #[RequiresPhp('>= 8.2')] public function testBug13384(): void { $this->reportTooWideBool = true; @@ -76,6 +77,24 @@ public function testBug13384(): void ]); } + #[RequiresPhp('< 8.2')] + public function testBug13384PrePhp82(): void + { + $this->reportTooWideBool = true; + $this->analyse([__DIR__ . '/data/bug-13384.php'], []); + } + + public function testBug13384Phpdoc(): void + { + $this->reportTooWideBool = true; + $this->analyse([__DIR__ . '/data/bug-13384-phpdoc.php'], [ + [ + 'Static property Bug13384Phpdoc\ShutdownHandlerPhpdocTypes::$registered (bool) is never assigned true so it can be removed from the property type.', + 12, + ], + ]); + } + public function testBug13384b(): void { $this->reportTooWideBool = true; diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384-phpdoc.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384-phpdoc.php new file mode 100644 index 0000000000..49a4287931 --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-13384-phpdoc.php @@ -0,0 +1,33 @@ + Date: Fri, 5 Sep 2025 11:04:01 +0200 Subject: [PATCH 16/18] fix build --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index fb76111856..dde23755ce 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -437,6 +437,7 @@ public function testBug4715(): void $this->assertNoErrors($errors); } + #[RequiresPhp('>= 8.2')] public function testBug4734(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-4734.php'); From 9fbad6b640c54b635bfec98609c35836245700a1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 5 Sep 2025 11:11:42 +0200 Subject: [PATCH 17/18] fix build --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index dde23755ce..f40bcd3d2c 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1095,7 +1095,7 @@ public function testBug8376(): void $this->assertNoErrors($errors); } - #[RequiresPhp('>= 8.0')] + #[RequiresPhp('>= 8.2')] public function testAssertDocblock(): void { $errors = $this->runAnalyse(__DIR__ . '/nsrt/assert-docblock.php'); From b39d601236e859bf385e9d299366e9e48a03fdc5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 5 Sep 2025 12:09:08 +0200 Subject: [PATCH 18/18] specific tooWideBool error message --- .../TooWideTypehints/TooWideTypeCheck.php | 31 +++++++++++++++++++ .../Analyser/AnalyserIntegrationTest.php | 12 +++---- .../TooWideFunctionReturnTypehintRuleTest.php | 12 +++---- .../TooWideMethodReturnTypehintRuleTest.php | 16 +++++----- .../TooWidePropertyTypeRuleTest.php | 6 ++-- 5 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/Rules/TooWideTypehints/TooWideTypeCheck.php b/src/Rules/TooWideTypehints/TooWideTypeCheck.php index 5be43cccb3..cd07f72145 100644 --- a/src/Rules/TooWideTypehints/TooWideTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideTypeCheck.php @@ -10,6 +10,7 @@ use PHPStan\Node\MethodReturnStatementsNode; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; @@ -54,6 +55,23 @@ public function checkProperty( continue; } + if ($propertyType->isBoolean()->yes()) { + $suggestedType = $type->isTrue()->yes() ? new ConstantBooleanType(false) : new ConstantBooleanType(true); + + $errors[] = RuleErrorBuilder::message(sprintf( + '%s (%s) is never assigned %s so the property type can be changed to %s.', + $propertyDescription, + $propertyType->describe($verbosityLevel), + $type->describe($verbosityLevel), + $suggestedType->describe($verbosityLevel), + )) + ->identifier('property.tooWideBool') + ->line($property->getStartLine()) + ->build(); + + continue; + } + $errors[] = RuleErrorBuilder::message(sprintf( '%s (%s) is never assigned %s so it can be removed from the property type.', $propertyDescription, @@ -144,6 +162,19 @@ public function checkFunction( } } + if ($functionReturnType->isBoolean()->yes()) { + $suggestedType = $type->isTrue()->yes() ? new ConstantBooleanType(false) : new ConstantBooleanType(true); + + $messages[] = RuleErrorBuilder::message(sprintf( + '%s never returns %s so the return type can be changed to %s.', + $functionDescription, + $type->describe(VerbosityLevel::getRecommendedLevelByType($type)), + $suggestedType->describe(VerbosityLevel::getRecommendedLevelByType($suggestedType)), + ))->identifier('return.tooWideBool')->build(); + + continue; + } + $messages[] = RuleErrorBuilder::message(sprintf( '%s never returns %s so it can be removed from the return type.', $functionDescription, diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index f40bcd3d2c..8876db5af4 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -443,8 +443,8 @@ public function testBug4734(): void $errors = $this->runAnalyse(__DIR__ . '/data/bug-4734.php'); $this->assertCount(5, $errors); // could be 3 - $this->assertSame('Static property Bug4734\Foo::$httpMethodParameterOverride (bool) is never assigned false so it can be removed from the property type.', $errors[0]->getMessage()); // should not error - $this->assertSame('Property Bug4734\Foo::$httpMethodParameterOverride2 (bool) is never assigned false so it can be removed from the property type.', $errors[1]->getMessage()); // should not error + $this->assertSame('Static property Bug4734\Foo::$httpMethodParameterOverride (bool) is never assigned false so the property type can be changed to true.', $errors[0]->getMessage()); // should not error + $this->assertSame('Property Bug4734\Foo::$httpMethodParameterOverride2 (bool) is never assigned false so the property type can be changed to true.', $errors[1]->getMessage()); // should not error $this->assertSame('Unsafe access to private property Bug4734\Foo::$httpMethodParameterOverride through static::.', $errors[2]->getMessage()); $this->assertSame('Access to an undefined static property static(Bug4734\Foo)::$httpMethodParameterOverride3.', $errors[3]->getMessage()); $this->assertSame('Access to an undefined property Bug4734\Foo::$httpMethodParameterOverride4.', $errors[4]->getMessage()); @@ -1100,13 +1100,13 @@ public function testAssertDocblock(): void { $errors = $this->runAnalyse(__DIR__ . '/nsrt/assert-docblock.php'); $this->assertCount(8, $errors); - $this->assertSame('Function AssertDocblock\validateStringArrayIfTrue() never returns false so it can be removed from the return type.', $errors[0]->getMessage()); + $this->assertSame('Function AssertDocblock\validateStringArrayIfTrue() never returns false so the return type can be changed to true.', $errors[0]->getMessage()); $this->assertSame(17, $errors[0]->getLine()); - $this->assertSame('Function AssertDocblock\validateStringArrayIfFalse() never returns true so it can be removed from the return type.', $errors[1]->getMessage()); + $this->assertSame('Function AssertDocblock\validateStringArrayIfFalse() never returns true so the return type can be changed to false.', $errors[1]->getMessage()); $this->assertSame(25, $errors[1]->getLine()); - $this->assertSame('Function AssertDocblock\validateStringOrIntArray() never returns true so it can be removed from the return type.', $errors[2]->getMessage()); + $this->assertSame('Function AssertDocblock\validateStringOrIntArray() never returns true so the return type can be changed to false.', $errors[2]->getMessage()); $this->assertSame(34, $errors[2]->getLine()); - $this->assertSame('Function AssertDocblock\validateStringOrNonEmptyIntArray() never returns true so it can be removed from the return type.', $errors[3]->getMessage()); + $this->assertSame('Function AssertDocblock\validateStringOrNonEmptyIntArray() never returns true so the return type can be changed to false.', $errors[3]->getMessage()); $this->assertSame(44, $errors[3]->getLine()); $this->assertSame('Call to method AssertDocblock\A::testInt() with string will always evaluate to false.', $errors[4]->getMessage()); $this->assertSame(218, $errors[4]->getLine()); diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index 0ca27eeb77..231eecf289 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -75,19 +75,19 @@ public function testBug13384cPhp82(): void $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ [ - 'Function Bug13384c\doFoo() never returns true so it can be removed from the return type.', + 'Function Bug13384c\doFoo() never returns true so the return type can be changed to false.', 5, ], [ - 'Function Bug13384c\doFoo2() never returns false so it can be removed from the return type.', + 'Function Bug13384c\doFoo2() never returns false so the return type can be changed to true.', 9, ], [ - 'Function Bug13384c\doFooPhpdoc() never returns false so it can be removed from the return type.', + 'Function Bug13384c\doFooPhpdoc() never returns false so the return type can be changed to true.', 93, ], [ - 'Function Bug13384c\doFooPhpdoc2() never returns true so it can be removed from the return type.', + 'Function Bug13384c\doFooPhpdoc2() never returns true so the return type can be changed to false.', 100, ], ]); @@ -99,11 +99,11 @@ public function testBug13384cPrePhp82(): void $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ [ - 'Function Bug13384c\doFooPhpdoc() never returns false so it can be removed from the return type.', + 'Function Bug13384c\doFooPhpdoc() never returns false so the return type can be changed to true.', 93, ], [ - 'Function Bug13384c\doFooPhpdoc2() never returns true so it can be removed from the return type.', + 'Function Bug13384c\doFooPhpdoc2() never returns true so the return type can be changed to false.', 100, ], ]); diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index 27fb2da518..bd74b550ed 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -226,27 +226,27 @@ public function testBug13384c(): void $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ [ - 'Method Bug13384c\Bug13384c::doBar() never returns true so it can be removed from the return type.', + 'Method Bug13384c\Bug13384c::doBar() never returns true so the return type can be changed to false.', 33, ], [ - 'Method Bug13384c\Bug13384c::doBar2() never returns false so it can be removed from the return type.', + 'Method Bug13384c\Bug13384c::doBar2() never returns false so the return type can be changed to true.', 37, ], [ - 'Method Bug13384c\Bug13384c::doBarPhpdoc() never returns false so it can be removed from the return type.', + 'Method Bug13384c\Bug13384c::doBarPhpdoc() never returns false so the return type can be changed to true.', 55, ], [ - 'Method Bug13384c\Bug13384Static::doBar() never returns true so it can be removed from the return type.', + 'Method Bug13384c\Bug13384Static::doBar() never returns true so the return type can be changed to false.', 62, ], [ - 'Method Bug13384c\Bug13384Static::doBar2() never returns false so it can be removed from the return type.', + 'Method Bug13384c\Bug13384Static::doBar2() never returns false so the return type can be changed to true.', 66, ], [ - 'Method Bug13384c\Bug13384Static::doBarPhpdoc() never returns false so it can be removed from the return type.', + 'Method Bug13384c\Bug13384Static::doBarPhpdoc() never returns false so the return type can be changed to true.', 84, ], ]); @@ -258,11 +258,11 @@ public function testBug13384cPrePhp82(): void $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384c.php'], [ [ - 'Method Bug13384c\Bug13384c::doBarPhpdoc() never returns false so it can be removed from the return type.', + 'Method Bug13384c\Bug13384c::doBarPhpdoc() never returns false so the return type can be changed to true.', 55, ], [ - 'Method Bug13384c\Bug13384Static::doBarPhpdoc() never returns false so it can be removed from the return type.', + 'Method Bug13384c\Bug13384Static::doBarPhpdoc() never returns false so the return type can be changed to true.', 84, ], ]); diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index 8f7e5aff13..ea7c4464c5 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -67,11 +67,11 @@ public function testBug13384(): void $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384.php'], [ [ - 'Static property Bug13384\ShutdownHandlerFalseDefault::$registered (bool) is never assigned true so it can be removed from the property type.', + 'Static property Bug13384\ShutdownHandlerFalseDefault::$registered (bool) is never assigned true so the property type can be changed to false.', 9, ], [ - 'Static property Bug13384\ShutdownHandlerTrueDefault::$registered (bool) is never assigned false so it can be removed from the property type.', + 'Static property Bug13384\ShutdownHandlerTrueDefault::$registered (bool) is never assigned false so the property type can be changed to true.', 34, ], ]); @@ -89,7 +89,7 @@ public function testBug13384Phpdoc(): void $this->reportTooWideBool = true; $this->analyse([__DIR__ . '/data/bug-13384-phpdoc.php'], [ [ - 'Static property Bug13384Phpdoc\ShutdownHandlerPhpdocTypes::$registered (bool) is never assigned true so it can be removed from the property type.', + 'Static property Bug13384Phpdoc\ShutdownHandlerPhpdocTypes::$registered (bool) is never assigned true so the property type can be changed to false.', 12, ], ]);