Skip to content

Commit 5e900f8

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

File tree

3 files changed

+81
-7
lines changed

3 files changed

+81
-7
lines changed

src/Rules/Variables/DefinedVariableRule.php

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
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\Type\Constant\ConstantStringType;
12+
use PHPStan\Type\VerbosityLevel;
13+
use function array_map;
14+
use function array_merge;
1015
use function in_array;
1116
use function is_string;
1217
use function sprintf;
@@ -31,11 +36,33 @@ public function getNodeType(): string
3136

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

38-
if ($this->cliArgumentsVariablesRegistered && in_array($node->name, [
57+
return $errors;
58+
}
59+
60+
/**
61+
* @return list<IdentifierRuleError>
62+
*/
63+
private function processSingleVariable(Scope $scope, Variable $node, string $variableName): array
64+
{
65+
if ($this->cliArgumentsVariablesRegistered && in_array($variableName, [
3966
'argc',
4067
'argv',
4168
], true)) {
@@ -49,18 +76,18 @@ public function processNode(Node $node, Scope $scope): array
4976
return [];
5077
}
5178

52-
if ($scope->hasVariableType($node->name)->no()) {
79+
if ($scope->hasVariableType($variableName)->no()) {
5380
return [
54-
RuleErrorBuilder::message(sprintf('Undefined variable: $%s', $node->name))
81+
RuleErrorBuilder::message(sprintf('Undefined variable: $%s', $variableName))
5582
->identifier('variable.undefined')
5683
->build(),
5784
];
5885
} elseif (
5986
$this->checkMaybeUndefinedVariables
60-
&& !$scope->hasVariableType($node->name)->yes()
87+
&& !$scope->hasVariableType($variableName)->yes()
6188
) {
6289
return [
63-
RuleErrorBuilder::message(sprintf('Variable $%s might not be defined.', $node->name))
90+
RuleErrorBuilder::message(sprintf('Variable $%s might not be defined.', $variableName))
6491
->identifier('variable.undefined')
6592
->build(),
6693
];

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 26 additions & 0 deletions
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) {
Lines changed: 21 additions & 0 deletions
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)