From de2d305b2f86048da2b18d52fc782af1ba189265 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 24 Jul 2025 11:41:54 +0200 Subject: [PATCH 1/2] Fix ImpossibleInstanceOfRule --- .../Classes/ImpossibleInstanceOfRule.php | 22 +++++++++---- .../Classes/ImpossibleInstanceOfRuleTest.php | 24 ++++++++++++-- .../PHPStan/Rules/Classes/data/bug-10036.php | 31 +++++++++++++++++++ 3 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-10036.php diff --git a/src/Rules/Classes/ImpossibleInstanceOfRule.php b/src/Rules/Classes/ImpossibleInstanceOfRule.php index 84c763638d..7c866cc30e 100644 --- a/src/Rules/Classes/ImpossibleInstanceOfRule.php +++ b/src/Rules/Classes/ImpossibleInstanceOfRule.php @@ -9,10 +9,13 @@ use PHPStan\Parser\LastConditionVisitor; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Rules\RuleLevelHelper; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\ErrorType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; +use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -25,6 +28,7 @@ final class ImpossibleInstanceOfRule implements Rule { public function __construct( + private RuleLevelHelper $ruleLevelHelper, #[AutowiredParameter] private bool $treatPhpDocTypesAsCertain, #[AutowiredParameter] @@ -42,11 +46,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - $instanceofType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node) : $scope->getNativeType($node); - if (!$instanceofType instanceof ConstantBooleanType) { - return []; - } - if ($node->class instanceof Node\Name) { $className = $scope->resolveName($node->class); $classType = new ObjectType($className); @@ -56,7 +55,13 @@ public function processNode(Node $node, Scope $scope): array new StringType(), new ObjectWithoutClassType(), ); - if (!$allowed->isSuperTypeOf($classType)->yes()) { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->class, + '', + static fn (Type $type): bool => !$allowed->isSuperTypeOf($type)->yes(), + ); + if (!$typeResult->getType() instanceof ErrorType && !$allowed->isSuperTypeOf($typeResult->getType())->yes()) { return [ RuleErrorBuilder::message(sprintf( 'Instanceof between %s and %s results in an error.', @@ -67,6 +72,11 @@ public function processNode(Node $node, Scope $scope): array } } + $instanceofType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node) : $scope->getNativeType($node); + if (!$instanceofType instanceof ConstantBooleanType) { + return []; + } + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { if (!$this->treatPhpDocTypesAsCertain) { return $ruleErrorBuilder; diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index ffddab32c4..41c62dcc03 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Classes; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\RequiresPhp; @@ -19,7 +20,10 @@ class ImpossibleInstanceOfRuleTest extends RuleTestCase protected function getRule(): Rule { + $ruleLevelHelper = new RuleLevelHelper(self::createReflectionProvider(), true, false, true, false, false, false, true); + return new ImpossibleInstanceOfRule( + $ruleLevelHelper, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, true, @@ -151,14 +155,14 @@ public function testInstanceof(): void 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarGrandChild will always evaluate to false.', 322, ], - /*[ + [ 'Instanceof between mixed and int results in an error.', 353, ], [ 'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.', 362, - ],*/ + ], [ 'Instanceof between ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Foo will always evaluate to true.', 388, @@ -496,6 +500,22 @@ public function testBug3632(): void ]); } + public function testBug10036(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-10036.php'], [ + [ + 'Instanceof between stdClass and string|null results in an error.', + 11, + ], + [ + 'Instanceof between stdClass and string|null results in an error.', + 19, + ], + ]); + } + #[RequiresPhp('>= 8.0')] public function testNewIsAlwaysFinalClass(): void { diff --git a/tests/PHPStan/Rules/Classes/data/bug-10036.php b/tests/PHPStan/Rules/Classes/data/bug-10036.php new file mode 100644 index 0000000000..0cfedd20fe --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-10036.php @@ -0,0 +1,31 @@ + Date: Thu, 24 Jul 2025 20:56:12 +0200 Subject: [PATCH 2/2] Add test --- .../Rules/Classes/ImpossibleInstanceOfRuleTest.php | 4 ++++ tests/PHPStan/Rules/Classes/data/bug-10036.php | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index 41c62dcc03..f62a3e1d0d 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -513,6 +513,10 @@ public function testBug10036(): void 'Instanceof between stdClass and string|null results in an error.', 19, ], + [ + 'Instanceof between stdClass and array results in an error.', + 39, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/bug-10036.php b/tests/PHPStan/Rules/Classes/data/bug-10036.php index 0cfedd20fe..e2894e9a1d 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-10036.php +++ b/tests/PHPStan/Rules/Classes/data/bug-10036.php @@ -28,4 +28,14 @@ public function sayMixed(mixed $class): void { var_dump(new stdClass instanceof $class); } + + public function sayObject(object $class): void + { + var_dump(new stdClass instanceof $class); + } + + public function sayArray(array $class): void + { + var_dump(new stdClass instanceof $class); + } }