From 1b69ba882029fcf171f6aa50b5a6b4917874196e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 28 Jul 2026 16:49:44 +0200 Subject: [PATCH] Do not let type narrowing define a certainly-undefined variable Removing a type from a certainly-undefined variable proves nothing about its definedness, so a sureNot specification must not create a holder for it. A sure specification (e.g. is_string($a)) still can - the condition can only hold for a defined variable, so it makes the variable defined inside the branch: one error at the test site, no cascade. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019wqGgaD7iqL44t1KgpJS7b --- src/Analyser/MutatingScope.php | 11 ++++++++++ .../Variables/DefinedVariableRuleTest.php | 22 +++++++++++++++++++ .../PHPStan/Rules/Variables/data/bug-2032.php | 17 ++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-2032.php 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 @@ +