Skip to content

Commit 1f7e3be

Browse files
Fix 13271
1 parent 5878035 commit 1f7e3be

File tree

3 files changed

+71
-15
lines changed

3 files changed

+71
-15
lines changed

src/Rules/Properties/AccessPropertiesCheck.php

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public function __construct(
4242
private bool $checkDynamicProperties,
4343
#[AutowiredParameter(ref: '%featureToggles.checkNonStringableDynamicAccess%')]
4444
private bool $checkNonStringableDynamicAccess,
45+
#[AutowiredParameter]
46+
private bool $checkThisOnly,
4547
)
4648
{
4749
}
@@ -58,20 +60,7 @@ public function check(PropertyFetch $node, Scope $scope, bool $write): array
5860
$names = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $scope->getType($node->name)->getConstantStrings());
5961

6062
if (!$write && $this->checkNonStringableDynamicAccess) {
61-
$nameTypeResult = $this->ruleLevelHelper->findTypeToCheck(
62-
$scope,
63-
$node->name,
64-
'',
65-
static fn (Type $type) => $type->toString()->isString()->yes(),
66-
);
67-
$nameType = $nameTypeResult->getType();
68-
if ($nameType instanceof ErrorType || $nameType->toString() instanceof ErrorType || !$nameType->toString()->isString()->yes()) {
69-
$originalNameType = $scope->getType($node->name);
70-
$className = $scope->getType($node->var)->describe(VerbosityLevel::typeOnly());
71-
$errors[] = RuleErrorBuilder::message(sprintf('Property name for %s must be a string, but %s was given.', $className, $originalNameType->describe(VerbosityLevel::precise())))
72-
->identifier('property.nameNotString')
73-
->build();
74-
}
63+
$errors = array_merge($errors, $this->checkNonStringableDynamicAccess($scope, $node->var, $node->name));
7564
}
7665
}
7766

@@ -82,6 +71,44 @@ public function check(PropertyFetch $node, Scope $scope, bool $write): array
8271
return $errors;
8372
}
8473

74+
/**
75+
* @return list<IdentifierRuleError>
76+
*/
77+
private function checkNonStringableDynamicAccess(Scope $scope, Expr $nodeVar, Expr $nodeName): array
78+
{
79+
if (
80+
$this->checkThisOnly
81+
&& !$this->ruleLevelHelper->isThis($nodeVar)
82+
) {
83+
return [];
84+
}
85+
86+
$nameTypeResult = $this->ruleLevelHelper->findTypeToCheck(
87+
$scope,
88+
$nodeName,
89+
'',
90+
static fn (Type $type) => $type->toString()->isString()->yes(),
91+
);
92+
$nameType = $nameTypeResult->getType();
93+
if (
94+
!$nameType instanceof ErrorType
95+
&& !$nameType->toString() instanceof ErrorType
96+
&& $nameType->toString()->isString()->yes()
97+
) {
98+
return [];
99+
}
100+
101+
$originalNameType = $scope->getType($nodeName);
102+
$className = $scope->getType($nodeVar)->describe(VerbosityLevel::typeOnly());
103+
return [
104+
RuleErrorBuilder::message(sprintf(
105+
'Property name for %s must be a string, but %s was given.',
106+
$className,
107+
$originalNameType->describe(VerbosityLevel::precise()),
108+
))->identifier('property.nameNotString')->build(),
109+
];
110+
}
111+
85112
/**
86113
* @return list<IdentifierRuleError>
87114
*/

tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ class AccessPropertiesRuleTest extends RuleTestCase
2626
protected function getRule(): Rule
2727
{
2828
$reflectionProvider = self::createReflectionProvider();
29-
return new AccessPropertiesRule(new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false, true), new PhpVersion(PHP_VERSION_ID), true, $this->checkDynamicProperties, true));
29+
return new AccessPropertiesRule(new AccessPropertiesCheck(
30+
$reflectionProvider,
31+
new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false, true),
32+
new PhpVersion(PHP_VERSION_ID),
33+
true,
34+
$this->checkDynamicProperties,
35+
true,
36+
$this->checkThisOnly,
37+
));
3038
}
3139

3240
public function testAccessProperties(): void
@@ -1092,6 +1100,14 @@ public function testNewIsAlwaysFinalClass(): void
10921100
]);
10931101
}
10941102

1103+
public function testBug13271(): void
1104+
{
1105+
$this->checkThisOnly = true;
1106+
$this->checkUnionTypes = false;
1107+
$this->checkDynamicProperties = true;
1108+
$this->analyse([__DIR__ . '/data/bug-13271.php'], []);
1109+
}
1110+
10951111
public function testPropertyExists(): void
10961112
{
10971113
$this->checkThisOnly = false;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug13271;
6+
7+
$object = new class{
8+
public string $example_one = "";
9+
public string $example_two = "";
10+
};
11+
12+
$field = rand() > 0.5 ? 'example_one' : 'example_two';
13+
$result = $object->$field;

0 commit comments

Comments
 (0)