Skip to content

Commit de2d305

Browse files
Fix ImpossibleInstanceOfRule
1 parent 1f150cc commit de2d305

File tree

3 files changed

+69
-8
lines changed

3 files changed

+69
-8
lines changed

src/Rules/Classes/ImpossibleInstanceOfRule.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
use PHPStan\Parser\LastConditionVisitor;
1010
use PHPStan\Rules\Rule;
1111
use PHPStan\Rules\RuleErrorBuilder;
12+
use PHPStan\Rules\RuleLevelHelper;
1213
use PHPStan\Type\Constant\ConstantBooleanType;
14+
use PHPStan\Type\ErrorType;
1315
use PHPStan\Type\ObjectType;
1416
use PHPStan\Type\ObjectWithoutClassType;
1517
use PHPStan\Type\StringType;
18+
use PHPStan\Type\Type;
1619
use PHPStan\Type\TypeCombinator;
1720
use PHPStan\Type\VerbosityLevel;
1821
use function sprintf;
@@ -25,6 +28,7 @@ final class ImpossibleInstanceOfRule implements Rule
2528
{
2629

2730
public function __construct(
31+
private RuleLevelHelper $ruleLevelHelper,
2832
#[AutowiredParameter]
2933
private bool $treatPhpDocTypesAsCertain,
3034
#[AutowiredParameter]
@@ -42,11 +46,6 @@ public function getNodeType(): string
4246

4347
public function processNode(Node $node, Scope $scope): array
4448
{
45-
$instanceofType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node) : $scope->getNativeType($node);
46-
if (!$instanceofType instanceof ConstantBooleanType) {
47-
return [];
48-
}
49-
5049
if ($node->class instanceof Node\Name) {
5150
$className = $scope->resolveName($node->class);
5251
$classType = new ObjectType($className);
@@ -56,7 +55,13 @@ public function processNode(Node $node, Scope $scope): array
5655
new StringType(),
5756
new ObjectWithoutClassType(),
5857
);
59-
if (!$allowed->isSuperTypeOf($classType)->yes()) {
58+
$typeResult = $this->ruleLevelHelper->findTypeToCheck(
59+
$scope,
60+
$node->class,
61+
'',
62+
static fn (Type $type): bool => !$allowed->isSuperTypeOf($type)->yes(),
63+
);
64+
if (!$typeResult->getType() instanceof ErrorType && !$allowed->isSuperTypeOf($typeResult->getType())->yes()) {
6065
return [
6166
RuleErrorBuilder::message(sprintf(
6267
'Instanceof between %s and %s results in an error.',
@@ -67,6 +72,11 @@ public function processNode(Node $node, Scope $scope): array
6772
}
6873
}
6974

75+
$instanceofType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node) : $scope->getNativeType($node);
76+
if (!$instanceofType instanceof ConstantBooleanType) {
77+
return [];
78+
}
79+
7080
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder {
7181
if (!$this->treatPhpDocTypesAsCertain) {
7282
return $ruleErrorBuilder;

tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Rules\Classes;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67
use PHPStan\Testing\RuleTestCase;
78
use PHPUnit\Framework\Attributes\DataProvider;
89
use PHPUnit\Framework\Attributes\RequiresPhp;
@@ -19,7 +20,10 @@ class ImpossibleInstanceOfRuleTest extends RuleTestCase
1920

2021
protected function getRule(): Rule
2122
{
23+
$ruleLevelHelper = new RuleLevelHelper(self::createReflectionProvider(), true, false, true, false, false, false, true);
24+
2225
return new ImpossibleInstanceOfRule(
26+
$ruleLevelHelper,
2327
$this->treatPhpDocTypesAsCertain,
2428
$this->reportAlwaysTrueInLastCondition,
2529
true,
@@ -151,14 +155,14 @@ public function testInstanceof(): void
151155
'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarGrandChild will always evaluate to false.',
152156
322,
153157
],
154-
/*[
158+
[
155159
'Instanceof between mixed and int results in an error.',
156160
353,
157161
],
158162
[
159163
'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.',
160164
362,
161-
],*/
165+
],
162166
[
163167
'Instanceof between ImpossibleInstanceOf\Foo and ImpossibleInstanceOf\Foo will always evaluate to true.',
164168
388,
@@ -496,6 +500,22 @@ public function testBug3632(): void
496500
]);
497501
}
498502

503+
public function testBug10036(): void
504+
{
505+
$this->treatPhpDocTypesAsCertain = true;
506+
507+
$this->analyse([__DIR__ . '/data/bug-10036.php'], [
508+
[
509+
'Instanceof between stdClass and string|null results in an error.',
510+
11,
511+
],
512+
[
513+
'Instanceof between stdClass and string|null results in an error.',
514+
19,
515+
],
516+
]);
517+
}
518+
499519
#[RequiresPhp('>= 8.0')]
500520
public function testNewIsAlwaysFinalClass(): void
501521
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Bug10036;
4+
5+
use stdClass;
6+
7+
class HelloWorld
8+
{
9+
public function sayHello(?string $class): void
10+
{
11+
var_dump(new stdClass instanceof $class);
12+
}
13+
14+
/**
15+
* @param string|null $class
16+
*/
17+
public function sayWorld($class): void
18+
{
19+
var_dump(new stdClass instanceof $class);
20+
}
21+
22+
public function sayString(string $class): void
23+
{
24+
var_dump(new stdClass instanceof $class);
25+
}
26+
27+
public function sayMixed(mixed $class): void
28+
{
29+
var_dump(new stdClass instanceof $class);
30+
}
31+
}

0 commit comments

Comments
 (0)