Skip to content

Commit 7458d1e

Browse files
committed
Implement property name as an expression in TypesAssignedToPropertiesRule
This reverts commit 4c84c7e.
1 parent c28092e commit 7458d1e

File tree

5 files changed

+215
-4
lines changed

5 files changed

+215
-4
lines changed

src/Rules/Properties/FoundPropertyReflection.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Properties;
44

5+
use PHPStan\Analyser\Scope;
56
use PHPStan\Reflection\ClassReflection;
67
use PHPStan\Reflection\Php\PhpPropertyReflection;
78
use PHPStan\Reflection\PropertyReflection;
@@ -14,26 +15,44 @@ class FoundPropertyReflection implements PropertyReflection
1415

1516
private PropertyReflection $originalPropertyReflection;
1617

18+
private Scope $scope;
19+
20+
private string $propertyName;
21+
1722
private Type $readableType;
1823

1924
private Type $writableType;
2025

2126
public function __construct(
2227
PropertyReflection $originalPropertyReflection,
28+
Scope $scope,
29+
string $propertyName,
2330
Type $readableType,
2431
Type $writableType
2532
)
2633
{
2734
$this->originalPropertyReflection = $originalPropertyReflection;
35+
$this->scope = $scope;
36+
$this->propertyName = $propertyName;
2837
$this->readableType = $readableType;
2938
$this->writableType = $writableType;
3039
}
3140

41+
public function getScope(): Scope
42+
{
43+
return $this->scope;
44+
}
45+
3246
public function getDeclaringClass(): ClassReflection
3347
{
3448
return $this->originalPropertyReflection->getDeclaringClass();
3549
}
3650

51+
public function getName(): string
52+
{
53+
return $this->propertyName;
54+
}
55+
3756
public function isStatic(): bool
3857
{
3958
return $this->originalPropertyReflection->isStatic();

src/Rules/Properties/PropertyDescriptor.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77
class PropertyDescriptor
88
{
99

10+
public function describePropertyByName(PropertyReflection $property, string $propertyName): string
11+
{
12+
if (!$property->isStatic()) {
13+
return sprintf('Property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName);
14+
}
15+
16+
return sprintf('Static property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName);
17+
}
18+
1019
/**
1120
* @param \PHPStan\Reflection\PropertyReflection $property
1221
* @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch

src/Rules/Properties/PropertyReflectionFinder.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,97 @@
22

33
namespace PHPStan\Rules\Properties;
44

5+
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Scalar\String_;
7+
use PhpParser\Node\VarLikeIdentifier;
58
use PHPStan\Analyser\Scope;
9+
use PHPStan\Type\Constant\ConstantStringType;
610
use PHPStan\Type\ObjectType;
711
use PHPStan\Type\StaticType;
812
use PHPStan\Type\ThisType;
913
use PHPStan\Type\Type;
1014
use PHPStan\Type\TypeTraverser;
15+
use PHPStan\Type\TypeUtils;
1116

1217
class PropertyReflectionFinder
1318
{
1419

20+
/**
21+
* @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch
22+
* @param \PHPStan\Analyser\Scope $scope
23+
* @return FoundPropertyReflection[]
24+
*/
25+
public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): array
26+
{
27+
if ($propertyFetch instanceof \PhpParser\Node\Expr\PropertyFetch) {
28+
if ($propertyFetch->name instanceof \PhpParser\Node\Identifier) {
29+
$names = [$propertyFetch->name->name];
30+
} else {
31+
$names = array_map(static function (ConstantStringType $name): string {
32+
return $name->getValue();
33+
}, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name)));
34+
}
35+
36+
$reflections = [];
37+
$propertyHolderType = $scope->getType($propertyFetch->var);
38+
$fetchedOnThis = $propertyHolderType instanceof ThisType && $scope->isInClass();
39+
foreach ($names as $name) {
40+
$reflection = $this->findPropertyReflection(
41+
$propertyHolderType,
42+
$name,
43+
$propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical(
44+
$propertyFetch->name,
45+
new String_($name)
46+
)) : $scope,
47+
$fetchedOnThis
48+
);
49+
if ($reflection === null) {
50+
continue;
51+
}
52+
53+
$reflections[] = $reflection;
54+
}
55+
56+
return $reflections;
57+
}
58+
59+
if ($propertyFetch->class instanceof \PhpParser\Node\Name) {
60+
$propertyHolderType = new ObjectType($scope->resolveName($propertyFetch->class));
61+
} else {
62+
$propertyHolderType = $scope->getType($propertyFetch->class);
63+
}
64+
65+
$fetchedOnThis = $propertyHolderType instanceof ThisType && $scope->isInClass();
66+
67+
if ($propertyFetch->name instanceof VarLikeIdentifier) {
68+
$names = [$propertyFetch->name->name];
69+
} else {
70+
$names = array_map(static function (ConstantStringType $name): string {
71+
return $name->getValue();
72+
}, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name)));
73+
}
74+
75+
$reflections = [];
76+
foreach ($names as $name) {
77+
$reflection = $this->findPropertyReflection(
78+
$propertyHolderType,
79+
$name,
80+
$propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical(
81+
$propertyFetch->name,
82+
new String_($name)
83+
)) : $scope,
84+
$fetchedOnThis
85+
);
86+
if ($reflection === null) {
87+
continue;
88+
}
89+
90+
$reflections[] = $reflection;
91+
}
92+
93+
return $reflections;
94+
}
95+
1596
/**
1697
* @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch
1798
* @param \PHPStan\Analyser\Scope $scope
@@ -68,6 +149,8 @@ private function findPropertyReflection(Type $propertyHolderType, string $proper
68149

69150
return new FoundPropertyReflection(
70151
$originalProperty,
152+
$scope,
153+
$propertyName,
71154
$readableType,
72155
$writableType
73156
);

src/Rules/Properties/TypesAssignedToPropertiesRule.php

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Rules\RuleError;
78
use PHPStan\Rules\RuleErrorBuilder;
89
use PHPStan\Rules\RuleLevelHelper;
910
use PHPStan\Type\VerbosityLevel;
@@ -55,20 +56,39 @@ public function processNode(Node $node, Scope $scope): array
5556

5657
/** @var \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch */
5758
$propertyFetch = $node->var;
58-
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyFetch, $scope);
59-
if ($propertyReflection === null) {
60-
return [];
59+
$propertyReflections = $this->propertyReflectionFinder->findPropertyReflectionsFromNode($propertyFetch, $scope);
60+
61+
$errors = [];
62+
foreach ($propertyReflections as $propertyReflection) {
63+
$errors = array_merge($errors, $this->processSingleProperty(
64+
$propertyReflection,
65+
$node
66+
));
6167
}
6268

69+
return $errors;
70+
}
71+
72+
/**
73+
* @param FoundPropertyReflection $propertyReflection
74+
* @param Node\Expr $node
75+
* @return RuleError[]
76+
*/
77+
private function processSingleProperty(
78+
FoundPropertyReflection $propertyReflection,
79+
Node\Expr $node
80+
): array
81+
{
6382
$propertyType = $propertyReflection->getWritableType();
83+
$scope = $propertyReflection->getScope();
6484

6585
if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignRef) {
6686
$assignedValueType = $scope->getType($node->expr);
6787
} else {
6888
$assignedValueType = $scope->getType($node);
6989
}
7090
if (!$this->ruleLevelHelper->accepts($propertyType, $assignedValueType, $scope->isDeclareStrictTypes())) {
71-
$propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $propertyFetch);
91+
$propertyDescription = $this->propertyDescriptor->describePropertyByName($propertyReflection, $propertyReflection->getName());
7292
$verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType);
7393

7494
return [

tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,84 @@ public function testBug1216(): void
9595
]);
9696
}
9797

98+
public function testTypesAssignedToPropertiesExpressionNames(): void
99+
{
100+
$this->analyse([__DIR__ . '/data/properties-from-array-into-object.php'], [
101+
[
102+
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.',
103+
42,
104+
],
105+
[
106+
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.',
107+
54,
108+
],
109+
[
110+
'Property PropertiesFromArrayIntoObject\Foo::$test (int|null) does not accept stdClass.',
111+
66,
112+
],
113+
[
114+
'Property PropertiesFromArrayIntoObject\Foo::$float_test (float) does not accept float|int|string.',
115+
69,
116+
],
117+
[
118+
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.',
119+
69,
120+
],
121+
[
122+
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept float|int|string.',
123+
69,
124+
],
125+
[
126+
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept (float|int).',
127+
73,
128+
],
129+
[
130+
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float.',
131+
83,
132+
],
133+
[
134+
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.',
135+
97,
136+
],
137+
[
138+
'Property PropertiesFromArrayIntoObject\Foo::$float_test (float) does not accept float|int|string.',
139+
110,
140+
],
141+
[
142+
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.',
143+
110,
144+
],
145+
[
146+
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept float|int|string.',
147+
110,
148+
],
149+
[
150+
'Property PropertiesFromArrayIntoObject\Foo::$test (int|null) does not accept float|int|string.',
151+
110,
152+
],
153+
[
154+
'Property PropertiesFromArrayIntoObject\FooBar::$foo (string) does not accept float.',
155+
147,
156+
],
157+
]);
158+
}
159+
160+
public function testTypesAssignedToStaticPropertiesExpressionNames(): void
161+
{
162+
$this->analyse([__DIR__ . '/data/properties-from-array-into-static-object.php'], [
163+
[
164+
'Static property PropertiesFromArrayIntoStaticObject\Foo::$lall (stdClass|null) does not accept string.',
165+
29,
166+
],
167+
[
168+
'Static property PropertiesFromArrayIntoStaticObject\Foo::$foo (string) does not accept float.',
169+
36,
170+
],
171+
[
172+
'Static property PropertiesFromArrayIntoStaticObject\FooBar::$foo (string) does not accept float.',
173+
72,
174+
],
175+
]);
176+
}
177+
98178
}

0 commit comments

Comments
 (0)