Skip to content

Commit c1e85c2

Browse files
committed
Implement property name as an expression in TypesAssignedToPropertiesRule
1 parent 6dda018 commit c1e85c2

File tree

4 files changed

+108
-4
lines changed

4 files changed

+108
-4
lines changed

src/Rules/Properties/FoundPropertyReflection.php

+9
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@ class FoundPropertyReflection implements PropertyReflection
1414

1515
private PropertyReflection $originalPropertyReflection;
1616

17+
private string $propertyName;
18+
1719
private Type $readableType;
1820

1921
private Type $writableType;
2022

2123
public function __construct(
2224
PropertyReflection $originalPropertyReflection,
25+
string $propertyName,
2326
Type $readableType,
2427
Type $writableType
2528
)
2629
{
2730
$this->originalPropertyReflection = $originalPropertyReflection;
31+
$this->propertyName = $propertyName;
2832
$this->readableType = $readableType;
2933
$this->writableType = $writableType;
3034
}
@@ -34,6 +38,11 @@ public function getDeclaringClass(): ClassReflection
3438
return $this->originalPropertyReflection->getDeclaringClass();
3539
}
3640

41+
public function getName(): string
42+
{
43+
return $this->propertyName;
44+
}
45+
3746
public function isStatic(): bool
3847
{
3948
return $this->originalPropertyReflection->isStatic();

src/Rules/Properties/PropertyDescriptor.php

+9
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

+64
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,79 @@
22

33
namespace PHPStan\Rules\Properties;
44

5+
use PhpParser\Node\VarLikeIdentifier;
56
use PHPStan\Analyser\Scope;
7+
use PHPStan\Type\Constant\ConstantStringType;
68
use PHPStan\Type\ObjectType;
79
use PHPStan\Type\StaticType;
810
use PHPStan\Type\ThisType;
911
use PHPStan\Type\Type;
1012
use PHPStan\Type\TypeTraverser;
13+
use PHPStan\Type\TypeUtils;
1114

1215
class PropertyReflectionFinder
1316
{
1417

18+
/**
19+
* @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch
20+
* @param \PHPStan\Analyser\Scope $scope
21+
* @return FoundPropertyReflection[]
22+
*/
23+
public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): array
24+
{
25+
if ($propertyFetch instanceof \PhpParser\Node\Expr\PropertyFetch) {
26+
if ($propertyFetch->name instanceof \PhpParser\Node\Identifier) {
27+
$names = [$propertyFetch->name->name];
28+
} else {
29+
$names = array_map(static function (ConstantStringType $name): string {
30+
return $name->getValue();
31+
}, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name)));
32+
}
33+
34+
$reflections = [];
35+
$propertyHolderType = $scope->getType($propertyFetch->var);
36+
$fetchedOnThis = $propertyHolderType instanceof ThisType && $scope->isInClass();
37+
foreach ($names as $name) {
38+
$reflection = $this->findPropertyReflection($propertyHolderType, $name, $scope, $fetchedOnThis);
39+
if ($reflection === null) {
40+
continue;
41+
}
42+
43+
$reflections[] = $reflection;
44+
}
45+
46+
return $reflections;
47+
}
48+
49+
if ($propertyFetch->class instanceof \PhpParser\Node\Name) {
50+
$propertyHolderType = new ObjectType($scope->resolveName($propertyFetch->class));
51+
} else {
52+
$propertyHolderType = $scope->getType($propertyFetch->class);
53+
}
54+
55+
$fetchedOnThis = $propertyHolderType instanceof ThisType && $scope->isInClass();
56+
57+
if ($propertyFetch->name instanceof VarLikeIdentifier) {
58+
$names = [$propertyFetch->name->name];
59+
} else {
60+
$names = array_map(static function (ConstantStringType $name): string {
61+
return $name->getValue();
62+
}, TypeUtils::getConstantStrings($scope->getType($propertyFetch->name)));
63+
}
64+
65+
$reflections = [];
66+
foreach ($names as $name) {
67+
$reflection = $this->findPropertyReflection($propertyHolderType, $name, $scope, $fetchedOnThis);
68+
if ($reflection === null) {
69+
continue;
70+
}
71+
72+
$reflections[] = $reflection;
73+
}
74+
75+
return $reflections;
76+
}
77+
1578
/**
1679
* @param \PhpParser\Node\Expr\PropertyFetch|\PhpParser\Node\Expr\StaticPropertyFetch $propertyFetch
1780
* @param \PHPStan\Analyser\Scope $scope
@@ -68,6 +131,7 @@ private function findPropertyReflection(Type $propertyHolderType, string $proper
68131

69132
return new FoundPropertyReflection(
70133
$originalProperty,
134+
$propertyName,
71135
$readableType,
72136
$writableType
73137
);

src/Rules/Properties/TypesAssignedToPropertiesRule.php

+26-4
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;
@@ -54,11 +55,32 @@ public function processNode(Node $node, Scope $scope): array
5455

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

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

6486
if ($node instanceof Node\Expr\Assign) {
@@ -67,7 +89,7 @@ public function processNode(Node $node, Scope $scope): array
6789
$assignedValueType = $scope->getType($node);
6890
}
6991
if (!$this->ruleLevelHelper->accepts($propertyType, $assignedValueType, $scope->isDeclareStrictTypes())) {
70-
$propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $propertyFetch);
92+
$propertyDescription = $this->propertyDescriptor->describePropertyByName($propertyReflection, $propertyReflection->getName());
7193
$verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType);
7294

7395
return [

0 commit comments

Comments
 (0)