diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index f873a373325..3d9abb8dbc3 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -41,9 +41,6 @@ public function processNode(Node $node, Scope $scope): array } $functionName = (string) $node->name; - if (strtolower($functionName) === 'is_a') { - return []; - } $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); if ($isAlways === null) { return []; diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 42781da8f61..f801ed2b4fd 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -10,12 +10,14 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; +use PHPStan\Type\StringType; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; use PHPStan\Type\TypeWithClassName; @@ -157,6 +159,20 @@ public function findSpecifiedType( } } } + } elseif ($functionName === 'is_a') { + $allowString = TrinaryLogic::createNo(); + if (count($node->args) > 2) { + $allowStringParameterType = $scope->getType($node->args[2]->value); + $allowString = $allowStringParameterType instanceof ConstantBooleanType + ? TrinaryLogic::createFromBoolean($allowStringParameterType->getValue()) + : TrinaryLogic::createMaybe(); + } + $objectType = $scope->getType($node->args[0]->value); + if ($allowString->no() && (new StringType())->isSuperTypeOf($objectType)) { + return false; + } + + return null; } } } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 0a51ca252ff..dc4161b4272 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -385,4 +385,16 @@ public function testBug3994(): void $this->analyse([__DIR__ . '/data/bug-3994.php'], []); } + public function testBug4371(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4371.php'], [ + [ + "Call to function is_a() with 'Bug4371\\\\Bar' and 'Bug4371\\\\Foo' will always evaluate to false.", + 12, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-4371.php b/tests/PHPStan/Rules/Comparison/data/bug-4371.php new file mode 100644 index 00000000000..196ec65184e --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-4371.php @@ -0,0 +1,14 @@ +