Skip to content

Commit 772f297

Browse files
committed
Object type narrowed after $a::class and get_class($a) cannot be a subclass
1 parent 5120049 commit 772f297

File tree

4 files changed

+87
-3
lines changed

4 files changed

+87
-3
lines changed

Diff for: phpstan-baseline.neon

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ parameters:
7575
-
7676
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#'
7777
identifier: phpstanApi.instanceofType
78-
count: 3
78+
count: 4
7979
path: src/Analyser/TypeSpecifier.php
8080

8181
-

Diff for: src/Analyser/TypeSpecifier.php

+25-1
Original file line numberDiff line numberDiff line change
@@ -2205,6 +2205,14 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
22052205
&& in_array(strtolower($unwrappedLeftExpr->name->toString()), ['get_class', 'get_debug_type'], true)
22062206
&& isset($unwrappedLeftExpr->getArgs()[0])
22072207
) {
2208+
if ($rightType instanceof ConstantStringType && $this->reflectionProvider->hasClass($rightType->getValue())) {
2209+
return $this->create(
2210+
$unwrappedLeftExpr->getArgs()[0]->value,
2211+
new ObjectType($rightType->getValue(), null, $this->reflectionProvider->getClass($rightType->getValue())->asFinal()),
2212+
$context,
2213+
$scope,
2214+
)->unionWith($this->create($leftExpr, $rightType, $context, $scope))->setRootExpr($expr);
2215+
}
22082216
if ($rightType->getClassStringObjectType()->isObject()->yes()) {
22092217
return $this->create(
22102218
$unwrappedLeftExpr->getArgs()[0]->value,
@@ -2215,7 +2223,6 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
22152223
}
22162224
}
22172225

2218-
// get_class($a) === 'Foo'
22192226
if (
22202227
$context->truthy()
22212228
&& $unwrappedLeftExpr instanceof FuncCall
@@ -2305,6 +2312,14 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
23052312
$rightType->getValue() !== '' &&
23062313
strtolower($unwrappedLeftExpr->name->toString()) === 'class'
23072314
) {
2315+
if ($this->reflectionProvider->hasClass($rightType->getValue())) {
2316+
return $this->create(
2317+
$unwrappedLeftExpr->class,
2318+
new ObjectType($rightType->getValue(), null, $this->reflectionProvider->getClass($rightType->getValue())->asFinal()),
2319+
$context,
2320+
$scope,
2321+
)->unionWith($this->create($leftExpr, $rightType, $context, $scope))->setRootExpr($expr);
2322+
}
23082323
return $this->specifyTypesInCondition(
23092324
$scope,
23102325
new Instanceof_(
@@ -2328,6 +2343,15 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
23282343
$leftType->getValue() !== '' &&
23292344
strtolower($unwrappedRightExpr->name->toString()) === 'class'
23302345
) {
2346+
if ($this->reflectionProvider->hasClass($leftType->getValue())) {
2347+
return $this->create(
2348+
$unwrappedRightExpr->class,
2349+
new ObjectType($leftType->getValue(), null, $this->reflectionProvider->getClass($leftType->getValue())->asFinal()),
2350+
$context,
2351+
$scope,
2352+
)->unionWith($this->create($rightExpr, $leftType, $context, $scope)->setRootExpr($expr));
2353+
}
2354+
23312355
return $this->specifyTypesInCondition(
23322356
$scope,
23332357
new Instanceof_(

Diff for: tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php

+20
Original file line numberDiff line numberDiff line change
@@ -506,12 +506,32 @@ public function testBug3632(): void
506506

507507
public function testNewIsAlwaysFinalClass(): void
508508
{
509+
if (PHP_VERSION_ID < 80000) {
510+
$this->markTestSkipped('This test needs PHP 8.0.');
511+
}
512+
509513
$this->treatPhpDocTypesAsCertain = true;
510514
$this->analyse([__DIR__ . '/data/impossible-instanceof-new-is-always-final.php'], [
511515
[
512516
'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.',
513517
17,
514518
],
519+
[
520+
'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.',
521+
33,
522+
],
523+
[
524+
'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.',
525+
43,
526+
],
527+
[
528+
'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.',
529+
53,
530+
],
531+
[
532+
'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.',
533+
63,
534+
],
515535
]);
516536
}
517537

Diff for: tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?php
1+
<?php // lint >= 8.0
22

33
namespace ImpossibleInstanceofNewIsAlwaysFinal;
44

@@ -24,3 +24,43 @@ function (Bar $bar): void {
2424

2525
}
2626
};
27+
28+
function (Bar $bar): void {
29+
if ($bar::class !== Bar::class) {
30+
return;
31+
}
32+
33+
if ($bar instanceof Foo) {
34+
35+
}
36+
};
37+
38+
function (Bar $bar): void {
39+
if (Bar::class !== $bar::class) {
40+
return;
41+
}
42+
43+
if ($bar instanceof Foo) {
44+
45+
}
46+
};
47+
48+
function (Bar $bar): void {
49+
if (get_class($bar) !== Bar::class) {
50+
return;
51+
}
52+
53+
if ($bar instanceof Foo) {
54+
55+
}
56+
};
57+
58+
function (Bar $bar): void {
59+
if (Bar::class !== get_class($bar)) {
60+
return;
61+
}
62+
63+
if ($bar instanceof Foo) {
64+
65+
}
66+
};

0 commit comments

Comments
 (0)