Skip to content

Commit 8cc986c

Browse files
committed
Improve type inference for $object::class === static::class
1 parent 2dc5cb0 commit 8cc986c

3 files changed

Lines changed: 114 additions & 1 deletion

File tree

‎src/Analyser/TypeSpecifier.php‎

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ public function specifyTypesInCondition(
198198

199199
$rightType = $scope->getType($expr->right);
200200
if (
201+
$context->true() &&
201202
$expr->left instanceof ClassConstFetch &&
202203
$expr->left->class instanceof Expr &&
203204
$expr->left->name instanceof Node\Identifier &&
@@ -215,6 +216,41 @@ public function specifyTypesInCondition(
215216
$rootExpr,
216217
);
217218
}
219+
if (
220+
$context->true()
221+
&& $expr->left instanceof ClassConstFetch
222+
&& $expr->left->class instanceof Expr
223+
&& $expr->left->name instanceof Node\Identifier
224+
&& $expr->right instanceof ClassConstFetch
225+
&& $rightType instanceof GenericClassStringType
226+
&& strtolower($expr->left->name->toString()) === 'class'
227+
) {
228+
return $this->create(
229+
$expr->left->class,
230+
$rightType->getGenericType(),
231+
$context,
232+
false,
233+
$scope,
234+
$rootExpr,
235+
);
236+
}
237+
if (
238+
$context->true()
239+
&& $expr->left instanceof FuncCall
240+
&& $expr->left->name instanceof Name
241+
&& strtolower($expr->left->name->toString()) === 'get_class'
242+
&& isset($expr->left->getArgs()[0])
243+
&& $rightType instanceof GenericClassStringType
244+
) {
245+
return $this->create(
246+
$expr->left->getArgs()[0]->value,
247+
$rightType->getGenericType(),
248+
$context,
249+
false,
250+
$scope,
251+
$rootExpr,
252+
);
253+
}
218254
if ($context->false()) {
219255
$identicalType = $scope->getType($expr);
220256
if ($identicalType instanceof ConstantBooleanType) {
@@ -1150,6 +1186,24 @@ private function specifyTypesForConstantStringBinaryExpression(
11501186
);
11511187
}
11521188

1189+
if (
1190+
$context->true()
1191+
&& $exprNode instanceof FuncCall
1192+
&& $exprNode->name instanceof Name
1193+
&& strtolower($exprNode->name->toString()) === 'get_class'
1194+
&& isset($exprNode->getArgs()[0])
1195+
) {
1196+
return $this->specifyTypesInCondition(
1197+
$scope,
1198+
new Instanceof_(
1199+
$exprNode->getArgs()[0]->value,
1200+
new Name($constantType->getValue()),
1201+
),
1202+
$context,
1203+
$rootExpr,
1204+
);
1205+
}
1206+
11531207
return null;
11541208
}
11551209

@@ -1391,7 +1445,7 @@ private function processBooleanConditionalTypes(Scope $scope, SpecifiedTypes $le
13911445
}
13921446

13931447
/**
1394-
* @return (Expr|ConstantScalarType)[]|null
1448+
* @return array{Expr, ConstantScalarType}|null
13951449
*/
13961450
private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\BinaryOp $binaryOperation): ?array
13971451
{

‎tests/PHPStan/Analyser/NodeScopeResolverTest.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,8 @@ public function dataFileAsserts(): iterable
10991099

11001100
yield from $this->gatherAssertTypes(__DIR__ . '/data/callable-object.php');
11011101
yield from $this->gatherAssertTypes(__DIR__ . '/data/callable-string.php');
1102+
1103+
yield from $this->gatherAssertTypes(__DIR__ . '/data/static-class-const-equals.php');
11021104
}
11031105

11041106
/**
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace StaticClassConstEquals;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
interface A {
8+
public function equals(A $other): bool;
9+
}
10+
11+
class B implements A {
12+
public function equals(A $other): bool {
13+
if ($other::class === static::class) {
14+
assertType('static(StaticClassConstEquals\\B)', $other);
15+
return true;
16+
} else {
17+
assertType('StaticClassConstEquals\\A', $other);
18+
return false;
19+
}
20+
}
21+
}
22+
23+
class C implements A {
24+
public function equals(A $other): bool {
25+
if (get_class($other) === static::class) {
26+
assertType('static(StaticClassConstEquals\\C)', $other);
27+
return true;
28+
} else {
29+
assertType('StaticClassConstEquals\\A', $other);
30+
return false;
31+
}
32+
}
33+
}
34+
35+
class D implements A {
36+
public function equals(A $other): bool {
37+
if ($other::class === self::class) {
38+
assertType('StaticClassConstEquals\\D', $other);
39+
return true;
40+
} else {
41+
assertType('StaticClassConstEquals\\A', $other);
42+
return false;
43+
}
44+
}
45+
}
46+
47+
class E implements A {
48+
public function equals(A $other): bool {
49+
if (get_class($other) === self::class) {
50+
assertType('StaticClassConstEquals\\E', $other);
51+
return true;
52+
} else {
53+
assertType('StaticClassConstEquals\\A', $other);
54+
return false;
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)