Skip to content

Commit 8bb0670

Browse files
committed
Infer types of property fetch with dynamic name
1 parent ff6da9e commit 8bb0670

File tree

6 files changed

+59
-32
lines changed

6 files changed

+59
-32
lines changed

src/Analyser/MutatingScope.php

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,35 +2126,48 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
21262126
return $callType;
21272127
}
21282128

2129-
if ($node instanceof PropertyFetch && $node->name instanceof Node\Identifier) {
2130-
if ($this->nativeTypesPromoted) {
2131-
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this);
2132-
if ($propertyReflection === null) {
2133-
return new ErrorType();
2134-
}
2129+
if ($node instanceof PropertyFetch) {
2130+
if ($node->name instanceof Node\Identifier) {
2131+
if ($this->nativeTypesPromoted) {
2132+
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this);
2133+
if ($propertyReflection === null) {
2134+
return new ErrorType();
2135+
}
21352136

2136-
if (!$propertyReflection->hasNativeType()) {
2137-
return new MixedType();
2137+
if (!$propertyReflection->hasNativeType()) {
2138+
return new MixedType();
2139+
}
2140+
2141+
$nativeType = $propertyReflection->getNativeType();
2142+
2143+
return $this->getNullsafeShortCircuitingType($node->var, $nativeType);
21382144
}
21392145

2140-
$nativeType = $propertyReflection->getNativeType();
2146+
$typeCallback = function () use ($node): Type {
2147+
$returnType = $this->propertyFetchType(
2148+
$this->getType($node->var),
2149+
$node->name->name,
2150+
$node,
2151+
);
2152+
if ($returnType === null) {
2153+
return new ErrorType();
2154+
}
2155+
return $returnType;
2156+
};
21412157

2142-
return $this->getNullsafeShortCircuitingType($node->var, $nativeType);
2158+
return $this->getNullsafeShortCircuitingType($node->var, $typeCallback());
21432159
}
21442160

2145-
$typeCallback = function () use ($node): Type {
2146-
$returnType = $this->propertyFetchType(
2147-
$this->getType($node->var),
2148-
$node->name->name,
2149-
$node,
2161+
$nameType = $this->getType($node->name);
2162+
if (count($nameType->getConstantStrings()) > 0) {
2163+
return TypeCombinator::union(
2164+
...array_map(fn ($constantString) => $this
2165+
->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue())))
2166+
->getType(
2167+
new PropertyFetch($node->var, new Identifier($constantString->getValue())),
2168+
), $nameType->getConstantStrings()),
21502169
);
2151-
if ($returnType === null) {
2152-
return new ErrorType();
2153-
}
2154-
return $returnType;
2155-
};
2156-
2157-
return $this->getNullsafeShortCircuitingType($node->var, $typeCallback());
2170+
}
21582171
}
21592172

21602173
if ($node instanceof Expr\NullsafePropertyFetch) {

src/Rules/Properties/PropertyReflectionFinder.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Type\Constant\ConstantStringType;
1111
use PHPStan\Type\Type;
1212
use function array_map;
13+
use function count;
1314

1415
final class PropertyReflectionFinder
1516
{
@@ -86,11 +87,18 @@ public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): a
8687
public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?FoundPropertyReflection
8788
{
8889
if ($propertyFetch instanceof Node\Expr\PropertyFetch) {
89-
if (!$propertyFetch->name instanceof Node\Identifier) {
90-
return null;
91-
}
9290
$propertyHolderType = $scope->getType($propertyFetch->var);
93-
return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope);
91+
if ($propertyFetch->name instanceof Node\Identifier) {
92+
return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope);
93+
}
94+
95+
$nameType = $scope->getType($propertyFetch->name);
96+
$nameTypeConstantStrings = $nameType->getConstantStrings();
97+
if (count($nameTypeConstantStrings) === 1) {
98+
return $this->findPropertyReflection($propertyHolderType, $nameTypeConstantStrings[0]->getValue(), $scope);
99+
}
100+
101+
return null;
94102
}
95103

96104
if (!$propertyFetch->name instanceof Node\Identifier) {

tests/PHPStan/Analyser/nsrt/bug-12398.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,20 @@
77
class Foo
88
{
99

10+
public int $test;
11+
1012
public function doFoo(string $foo): void
1113
{
1214
$bar = 'foo';
1315
assertType('string', $$bar);
1416
}
1517

18+
19+
public function doBar(): void
20+
{
21+
$a = 'test';
22+
assertType('int', $this->$a);
23+
}
24+
1625
}
26+

tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,6 @@ public function testTypesAssignedToPropertiesExpressionNames(): void
163163
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.',
164164
69,
165165
],
166-
[
167-
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept (float|int).',
168-
73,
169-
],
170166
[
171167
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float.',
172168
83,

tests/PHPStan/Rules/Variables/IssetRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ public function testBug7109(): void
354354
67,
355355
],
356356
[
357-
'Using nullsafe property access "?->(Expression)" in isset() is unnecessary. Use -> instead.',
357+
'Expression in isset() is not nullable.',
358358
74,
359359
],
360360
]);

tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ public function testBug7109(): void
288288
66,
289289
],
290290
[
291-
'Using nullsafe property access "?->(Expression)" on left side of ?? is unnecessary. Use -> instead.',
291+
'Expression on left side of ?? is not nullable.',
292292
73,
293293
],
294294
]);

0 commit comments

Comments
 (0)