diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 11bea7b52b6..f8219658d15 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3327,6 +3327,17 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self continue; } + if ( + !$typeSpecification['sure'] + && $expr instanceof Variable && is_string($expr->name) + && $scope->hasVariableType($expr->name)->no() + ) { + // removing type from a certainly-undefined variable cannot make + // it defined; a sure specification (e.g. is_string($a)) still can - + // the condition can only hold for a defined variable + continue; + } + if ($typeSpecification['sure']) { if ($specifiedTypes->shouldOverwrite()) { $scope = $scope->assignExpression($expr, $type, $type); diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index ca236634015..397dabed083 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1701,4 +1701,26 @@ public function testBug10090(): void ]); } + public function testBug2032(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-2032.php'], [ + [ + 'Undefined variable: $undefined', + 6, + ], + [ + 'Undefined variable: $undefined', + 9, + ], + [ + 'Undefined variable: $undefined', + 15, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-2032.php b/tests/PHPStan/Rules/Variables/data/bug-2032.php new file mode 100644 index 00000000000..65e9d1c5f62 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-2032.php @@ -0,0 +1,17 @@ +