|
5 | 5 | use PhpParser\Node; |
6 | 6 | use PhpParser\Node\Expr; |
7 | 7 | use PHPStan\Analyser\Scope; |
| 8 | +use PHPStan\Analyser\VariableNameResolver; |
8 | 9 | use PHPStan\DependencyInjection\AutowiredParameter; |
9 | 10 | use PHPStan\DependencyInjection\AutowiredService; |
10 | 11 | use PHPStan\Node\Expr\PropertyInitializationExpr; |
|
13 | 14 | use PHPStan\Type\NeverType; |
14 | 15 | use PHPStan\Type\Type; |
15 | 16 | use PHPStan\Type\VerbosityLevel; |
16 | | -use function is_string; |
17 | 17 | use function sprintf; |
18 | 18 | use function str_starts_with; |
19 | 19 |
|
@@ -42,36 +42,23 @@ public function __construct( |
42 | 42 | public function check(Expr $expr, Scope $scope, string $operatorDescription, string $identifier, callable $typeMessageCallback, ?IdentifierRuleError $error = null): ?IdentifierRuleError |
43 | 43 | { |
44 | 44 | // mirrored in PHPStan\Analyser\MutatingScope::issetCheck() |
45 | | - if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { |
46 | | - $hasVariable = $scope->hasVariableType($expr->name); |
47 | | - if ($hasVariable->maybe()) { |
| 45 | + if ($expr instanceof Node\Expr\Variable) { |
| 46 | + $variableScopes = VariableNameResolver::resolveNamesWithScopes($scope, $expr); |
| 47 | + if ($variableScopes === null) { |
48 | 48 | return null; |
49 | 49 | } |
50 | 50 |
|
51 | | - if ($error === null) { |
52 | | - if ($hasVariable->yes()) { |
53 | | - if ($expr->name === '_SESSION') { |
54 | | - return null; |
55 | | - } |
56 | | - |
57 | | - $type = $this->treatPhpDocTypesAsCertain ? $scope->getScopeType($expr) : $scope->getScopeNativeType($expr); |
58 | | - if (!$type instanceof NeverType) { |
59 | | - return $this->generateError( |
60 | | - $type, |
61 | | - sprintf('Variable $%s %s always exists and', $expr->name, $operatorDescription), |
62 | | - $typeMessageCallback, |
63 | | - $identifier, |
64 | | - 'variable', |
65 | | - ); |
66 | | - } |
| 51 | + $variableErrors = []; |
| 52 | + foreach ($variableScopes as [$variableName, $variableScope]) { |
| 53 | + $variableError = $this->checkVariable($expr, $variableName, $variableScope, $operatorDescription, $identifier, $typeMessageCallback, $error); |
| 54 | + if ($variableError === null) { |
| 55 | + return null; |
67 | 56 | } |
68 | 57 |
|
69 | | - return RuleErrorBuilder::message(sprintf('Variable $%s %s is never defined.', $expr->name, $operatorDescription)) |
70 | | - ->identifier(sprintf('%s.variable', $identifier)) |
71 | | - ->build(); |
| 58 | + $variableErrors[] = $variableError; |
72 | 59 | } |
73 | 60 |
|
74 | | - return $error; |
| 61 | + return $variableErrors[0]; |
75 | 62 | } elseif ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { |
76 | 63 | $type = $this->treatPhpDocTypesAsCertain |
77 | 64 | ? $scope->getScopeType($expr->var) |
@@ -275,20 +262,74 @@ static function (Type $type) use ($typeMessageCallback): ?string { |
275 | 262 |
|
276 | 263 | /** |
277 | 264 | * @param ErrorIdentifier $identifier |
| 265 | + * @param callable(Type): ?string $typeMessageCallback |
278 | 266 | */ |
279 | | - private function checkUndefined(Expr $expr, Scope $scope, string $operatorDescription, string $identifier): ?IdentifierRuleError |
| 267 | + private function checkVariable( |
| 268 | + Expr\Variable $expr, |
| 269 | + string $variableName, |
| 270 | + Scope $scope, |
| 271 | + string $operatorDescription, |
| 272 | + string $identifier, |
| 273 | + callable $typeMessageCallback, |
| 274 | + ?IdentifierRuleError $error, |
| 275 | + ): ?IdentifierRuleError |
280 | 276 | { |
281 | | - if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { |
282 | | - $hasVariable = $scope->hasVariableType($expr->name); |
283 | | - if (!$hasVariable->no()) { |
284 | | - return null; |
| 277 | + $hasVariable = $scope->hasVariableType($variableName); |
| 278 | + if ($hasVariable->maybe()) { |
| 279 | + return null; |
| 280 | + } |
| 281 | + |
| 282 | + if ($error === null) { |
| 283 | + if ($hasVariable->yes()) { |
| 284 | + if ($variableName === '_SESSION') { |
| 285 | + return null; |
| 286 | + } |
| 287 | + |
| 288 | + $type = $this->treatPhpDocTypesAsCertain ? $scope->getScopeType($expr) : $scope->getScopeNativeType($expr); |
| 289 | + if (!$type instanceof NeverType) { |
| 290 | + return $this->generateError( |
| 291 | + $type, |
| 292 | + sprintf('Variable $%s %s always exists and', $variableName, $operatorDescription), |
| 293 | + $typeMessageCallback, |
| 294 | + $identifier, |
| 295 | + 'variable', |
| 296 | + ); |
| 297 | + } |
285 | 298 | } |
286 | 299 |
|
287 | | - return RuleErrorBuilder::message(sprintf('Variable $%s %s is never defined.', $expr->name, $operatorDescription)) |
| 300 | + return RuleErrorBuilder::message(sprintf('Variable $%s %s is never defined.', $variableName, $operatorDescription)) |
288 | 301 | ->identifier(sprintf('%s.variable', $identifier)) |
289 | 302 | ->build(); |
290 | 303 | } |
291 | 304 |
|
| 305 | + return $error; |
| 306 | + } |
| 307 | + |
| 308 | + /** |
| 309 | + * @param ErrorIdentifier $identifier |
| 310 | + */ |
| 311 | + private function checkUndefined(Expr $expr, Scope $scope, string $operatorDescription, string $identifier): ?IdentifierRuleError |
| 312 | + { |
| 313 | + if ($expr instanceof Node\Expr\Variable) { |
| 314 | + $variableScopes = VariableNameResolver::resolveNamesWithScopes($scope, $expr); |
| 315 | + if ($variableScopes === null) { |
| 316 | + return null; |
| 317 | + } |
| 318 | + |
| 319 | + $variableErrors = []; |
| 320 | + foreach ($variableScopes as [$variableName, $variableScope]) { |
| 321 | + if (!$variableScope->hasVariableType($variableName)->no()) { |
| 322 | + return null; |
| 323 | + } |
| 324 | + |
| 325 | + $variableErrors[] = RuleErrorBuilder::message(sprintf('Variable $%s %s is never defined.', $variableName, $operatorDescription)) |
| 326 | + ->identifier(sprintf('%s.variable', $identifier)) |
| 327 | + ->build(); |
| 328 | + } |
| 329 | + |
| 330 | + return $variableErrors[0]; |
| 331 | + } |
| 332 | + |
292 | 333 | if ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { |
293 | 334 | $type = $this->treatPhpDocTypesAsCertain ? $scope->getScopeType($expr->var) : $scope->getScopeNativeType($expr->var); |
294 | 335 | $dimType = $this->treatPhpDocTypesAsCertain ? $scope->getScopeType($expr->dim) : $scope->getScopeNativeType($expr->dim); |
|
0 commit comments