Skip to content

Commit 263be67

Browse files
committed
Fix for handling non-stringable types in dynamic variable access
1 parent 3b421cd commit 263be67

File tree

3 files changed

+83
-7
lines changed

3 files changed

+83
-7
lines changed

Diff for: src/Rules/Variables/DefinedVariableRule.php

+36-7
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
use PhpParser\Node;
66
use PhpParser\Node\Expr\Variable;
77
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\IdentifierRuleError;
89
use PHPStan\Rules\Rule;
910
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Rules\RuleLevelHelper;
12+
use PHPStan\Type\Constant\ConstantStringType;
13+
use PHPStan\Type\VerbosityLevel;
14+
use function array_map;
15+
use function array_merge;
1016
use function in_array;
1117
use function is_string;
1218
use function sprintf;
@@ -20,6 +26,7 @@ final class DefinedVariableRule implements Rule
2026
public function __construct(
2127
private bool $cliArgumentsVariablesRegistered,
2228
private bool $checkMaybeUndefinedVariables,
29+
// private RuleLevelHelper $ruleLevelHelper,
2330
)
2431
{
2532
}
@@ -31,11 +38,33 @@ public function getNodeType(): string
3138

3239
public function processNode(Node $node, Scope $scope): array
3340
{
34-
if (!is_string($node->name)) {
35-
return [];
41+
$errors = [];
42+
if (is_string($node->name)) {
43+
$variableNames = [$node->name];
44+
} else {
45+
$fetchType = $scope->getType($node->name);
46+
$variableNames = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $fetchType->getConstantStrings());
47+
$fetchStringType = $fetchType->toString();
48+
if (! $fetchStringType->isString()->yes()) {
49+
$errors[] = RuleErrorBuilder::message(sprintf('Cannot access variable with a non-stringable type %s.', $fetchType->describe(VerbosityLevel::typeOnly())))
50+
->identifier('variable.fetchInvalidExpression')
51+
->build();
52+
}
53+
}
54+
55+
foreach ($variableNames as $name) {
56+
$errors = array_merge($errors, $this->processSingleVariable($scope, $node, $name));
3657
}
3758

38-
if ($this->cliArgumentsVariablesRegistered && in_array($node->name, [
59+
return $errors;
60+
}
61+
62+
/**
63+
* @return list<IdentifierRuleError>
64+
*/
65+
private function processSingleVariable(Scope $scope, Variable $node, string $variableName): array
66+
{
67+
if ($this->cliArgumentsVariablesRegistered && in_array($variableName, [
3968
'argc',
4069
'argv',
4170
], true)) {
@@ -49,18 +78,18 @@ public function processNode(Node $node, Scope $scope): array
4978
return [];
5079
}
5180

52-
if ($scope->hasVariableType($node->name)->no()) {
81+
if ($scope->hasVariableType($variableName)->no()) {
5382
return [
54-
RuleErrorBuilder::message(sprintf('Undefined variable: $%s', $node->name))
83+
RuleErrorBuilder::message(sprintf('Undefined variable: $%s', $variableName))
5584
->identifier('variable.undefined')
5685
->build(),
5786
];
5887
} elseif (
5988
$this->checkMaybeUndefinedVariables
60-
&& !$scope->hasVariableType($node->name)->yes()
89+
&& !$scope->hasVariableType($variableName)->yes()
6190
) {
6291
return [
63-
RuleErrorBuilder::message(sprintf('Variable $%s might not be defined.', $node->name))
92+
RuleErrorBuilder::message(sprintf('Variable $%s might not be defined.', $variableName))
6493
->identifier('variable.undefined')
6594
->build(),
6695
];

Diff for: tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

+26
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,32 @@ public function testBug9474(): void
971971
$this->analyse([__DIR__ . '/data/bug-9474.php'], []);
972972
}
973973

974+
public function testBug9475(): void
975+
{
976+
$this->cliArgumentsVariablesRegistered = true;
977+
$this->polluteScopeWithLoopInitialAssignments = true;
978+
$this->checkMaybeUndefinedVariables = true;
979+
$this->polluteScopeWithAlwaysIterableForeach = true;
980+
$this->analyse([__DIR__ . '/data/bug-9475.php'], [
981+
[
982+
'Cannot access variable with a non-stringable type $this(Bug9475\Variables).',
983+
12,
984+
],
985+
[
986+
'Cannot access variable with a non-stringable type $this(Bug9475\Variables).',
987+
13,
988+
],
989+
[
990+
'Cannot access variable with a non-stringable type object.',
991+
14,
992+
],
993+
[
994+
'Cannot access variable with a non-stringable type array.',
995+
15,
996+
],
997+
]);
998+
}
999+
9741000
public function testEnum(): void
9751001
{
9761002
if (PHP_VERSION_ID < 80100) {

Diff for: tests/PHPStan/Rules/Variables/data/bug-9475.php

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug9475;
4+
5+
use Stringable;
6+
7+
final class Variables
8+
{
9+
10+
public function test(string $name, Stringable $stringable, object $object, array $array): void
11+
{
12+
echo 'Hello, ' . $$this;
13+
echo 'Hello, ' . ${$this};
14+
echo 'Hello, ' . ${$object};
15+
echo 'Hello, ' . ${$array};
16+
17+
echo 'Hello, ' . ${$name}; // valid
18+
echo 'Hello, ' . ${$stringable}; // valid
19+
}
20+
21+
}

0 commit comments

Comments
 (0)