diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index a52cc8d718..f9b991c55e 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function sprintf; -use function strtolower; /** * @implements Rule @@ -37,9 +36,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/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index c4000b9aff..4d062efd56 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -9,9 +9,12 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; +use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\TypeCombinator; use function count; use function strtolower; @@ -47,9 +50,20 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $allowStringType = isset($node->getArgs()[2]) ? $scope->getType($node->getArgs()[2]->value) : new ConstantBooleanType(false); $allowString = !$allowStringType->equals(new ConstantBooleanType(false)); + $superType = $allowString + ? TypeCombinator::union(new ObjectWithoutClassType(), new ClassStringType()) + : new ObjectWithoutClassType(); + + $resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true); + + // prevent false-positives in IsAFunctionTypeSpecifyingHelper + if ($resultType->equals($superType) && $resultType->isSuperTypeOf($objectOrClassType)->yes()) { + return new SpecifiedTypes([], []); + } + return $this->typeSpecifier->create( $node->getArgs()[0]->value, - $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true), + $resultType, $context, false, $scope, diff --git a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php index 2d52ee99e1..0ca6cf4e76 100644 --- a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php @@ -9,9 +9,12 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\Generic\GenericClassStringType; +use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\TypeCombinator; use function count; use function strtolower; @@ -48,9 +51,20 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return new SpecifiedTypes([], []); } + $superType = $allowString + ? TypeCombinator::union(new ObjectWithoutClassType(), new ClassStringType()) + : new ObjectWithoutClassType(); + + $resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false); + + // prevent false-positives in IsAFunctionTypeSpecifyingHelper + if ($resultType->equals($superType) && $resultType->isSuperTypeOf($objectOrClassType)->yes()) { + return new SpecifiedTypes([], []); + } + return $this->typeSpecifier->create( $node->getArgs()[0]->value, - $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false), + $resultType, $context, false, $scope, diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index c2b21e92c4..279ab0e8d0 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1133,9 +1133,7 @@ public function dataCondition(): iterable new Arg(new Variable('stringOrNull')), new Arg(new Expr\ConstFetch(new Name('false'))), ]), - [ - '$object' => 'object', - ], + [], [], ], [ diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 88d9315d84..81a46f358c 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1101,4 +1101,11 @@ public function testAlwaysTruePregMatch(): void $this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []); } + public function testBug3979(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-3979.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3979.php b/tests/PHPStan/Rules/Comparison/data/bug-3979.php new file mode 100644 index 0000000000..f0f21220d1 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-3979.php @@ -0,0 +1,130 @@ +