diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 3939aa9bff..90006b01cc 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -9,3 +9,4 @@ parameters: internalTag: true newStaticInAbstractClassStaticMethod: true checkExtensionsForComparisonOperators: true + reportTooWideBool: true diff --git a/conf/config.neon b/conf/config.neon index f922f49d60..0da8efee90 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -33,6 +33,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 9670a557bd..d3d04fad4e 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -36,6 +36,7 @@ parametersSchema: internalTag: bool() newStaticInAbstractClassStaticMethod: bool() checkExtensionsForComparisonOperators: bool() + reportTooWideBool: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() 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/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php index 4be9a14c9f..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, ) { } @@ -58,7 +61,9 @@ public function processNode(Node $node, Scope $scope): array $propertyReflection = $classReflection->getNativeProperty($propertyName); $propertyType = $propertyReflection->getWritableType(); if (!$propertyType instanceof UnionType) { - continue; + 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 8ea9a0cea4..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,12 +22,19 @@ final class TooWideTypeCheck { + public function __construct( + #[AutowiredParameter(ref: '%featureToggles.reportTooWideBool%')] + private bool $reportTooWideBool, + ) + { + } + /** * @return list */ public function checkProperty( ClassPropertyNode $property, - UnionType $propertyType, + Type $propertyType, string $propertyDescription, Type $assignedType, ): array @@ -34,7 +42,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; } @@ -68,8 +77,11 @@ public function checkFunction( ): array { $functionReturnType = TypeUtils::resolveLateResolvableTypes($functionReturnType); + if (!$functionReturnType instanceof UnionType) { - return []; + if (!$functionReturnType->isBoolean()->yes() || !$this->reportTooWideBool) { + return []; + } } $statementResult = $node->getStatementResult(); if ($statementResult->hasYield()) { @@ -114,7 +126,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/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index f6d7dceff2..fb76111856 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 @@ -1096,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/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index eee1500835..2bf3017098 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -182,4 +182,16 @@ public function testBug8926(): void $this->analyse([__DIR__ . '/data/bug-8926.php'], []); } + #[RequiresPhp('>= 8.0')] + 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/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..58aea82f3a 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php @@ -11,11 +11,9 @@ class TooWideFunctionThrowTypeRuleTest extends RuleTestCase { - 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 da40cc6d80..2db813da37 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php @@ -13,11 +13,9 @@ class TooWidePropertyHookThrowTypeRuleTest extends RuleTestCase { - 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')] 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 cce5fd6e0e..0fcb88b47c 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 @@ -66,4 +68,24 @@ public function testBug10312a(): void $this->analyse([__DIR__ . '/data/bug-10312a.php'], []); } + 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.', + 5, + ], + [ + 'Function Bug13384c\doFoo2() never returns false so it can be removed from the return type.', + 9, + ], + ]); + } + + 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 677e4570be..015aff19cf 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 @@ -218,4 +220,32 @@ public function testBug10312d(): void $this->analyse([__DIR__ . '/data/bug-10312d.php'], []); } + 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.', + 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, + ], + ]); + } + + 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 e590aea5eb..a48cf08fc1 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -14,12 +14,15 @@ class TooWidePropertyTypeRuleTest extends RuleTestCase { + private bool $reportTooWideBool = false; + protected function getRule(): Rule { return new TooWidePropertyTypeRule( new DirectReadWritePropertiesExtensionProvider([]), new PropertyReflectionFinder(), - new TooWideTypeCheck(), + new TooWideTypeCheck($this->reportTooWideBool), + $this->reportTooWideBool, ); } @@ -59,4 +62,30 @@ public function testBug11667(): void $this->analyse([__DIR__ . '/data/bug-11667.php'], []); } + 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.', + 9, + ], + [ + 'Static property Bug13384\ShutdownHandlerTrueDefault::$registered (bool) is never assigned false so it can be removed from the property type.', + 34, + ], + ]); + } + + 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'], []); + } + } 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; + }); + } +} 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 @@ +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.',