From 15b7ed071b50b507389b032bbb72e2be90767df5 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Fri, 13 Mar 2026 07:58:20 +0000 Subject: [PATCH 1/3] Fix phpstan/phpstan#14274: "Variable might not be defined" false positive - Fixed invalidateExpression() dropping all ConditionalExpressionHolders for a variable when only some holders had an invalidated condition guard - Previously, `continue 3` skipped the entire variable's holders when any single holder's condition guard matched the invalidated expression - Now filters holders individually, preserving valid holders (e.g. those guarded by $has_staged_update) when unrelated holders (e.g. those guarded by $messages) are invalidated - New regression test in tests/PHPStan/Rules/Variables/data/bug-14274.php --- src/Analyser/MutatingScope.php | 18 +++++-- .../Variables/DefinedVariableRuleTest.php | 11 +++++ .../Rules/Variables/data/bug-14274.php | 49 +++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-14274.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 88a11de1331..58ab8694055 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2893,16 +2893,28 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require $invalidated = true; continue; } - foreach ($holders as $holder) { + $filteredHolders = []; + foreach ($holders as $key => $holder) { + $shouldKeep = true; $conditionalTypeHolders = $holder->getConditionExpressionTypeHolders(); foreach ($conditionalTypeHolders as $conditionalTypeHolderExprString => $conditionalTypeHolder) { if ($this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $conditionalTypeHolder->getExpr(), $conditionalTypeHolderExprString, false, $invalidatingClass)) { $invalidated = true; - continue 3; + $shouldKeep = false; + break; } } + if (!$shouldKeep) { + continue; + } + + $filteredHolders[$key] = $holder; + } + if (count($filteredHolders) <= 0) { + continue; } - $newConditionalExpressions[$conditionalExprString] = $holders; + + $newConditionalExpressions[$conditionalExprString] = $filteredHolders; } if (!$invalidated) { diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 2dde59f1423..9a8f17e7bfa 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1403,6 +1403,17 @@ public function testBug14019(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-14019.php'], []); } + #[RequiresPhp('>= 8.0')] + public function testBug14274(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-14274.php'], []); + } + public function testBug14117(): void { $this->cliArgumentsVariablesRegistered = true; diff --git a/tests/PHPStan/Rules/Variables/data/bug-14274.php b/tests/PHPStan/Rules/Variables/data/bug-14274.php new file mode 100644 index 00000000000..36d70a29fc4 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-14274.php @@ -0,0 +1,49 @@ += 8.0 + +declare(strict_types=1); + +namespace Bug14274; + +class PreApplyEvent {} + +final class ComposerPatchesValidator { + /** + * Validates the status of the patcher plugin. + */ + public function validate(mixed $event): void { + $messages = []; + + [$plugin_installed_in_active, $is_active_root_requirement, $active_configuration_ok] = $this->computePatcherStatus(); + if ($event instanceof PreApplyEvent) { + [$plugin_installed_in_stage, $is_stage_root_requirement, $stage_configuration_ok] = $this->computePatcherStatus(); + $has_staged_update = TRUE; + } + else { + // No staged update exists. + $has_staged_update = FALSE; + } + + if ($has_staged_update && $plugin_installed_in_active !== $plugin_installed_in_stage) { + $messages[] = 'package-manager-faq-composer-patches-installed-or-removed'; + } + + // If the patcher is not listed in the runtime or dev dependencies, that's + // an error as well. + if (($plugin_installed_in_active && !$is_active_root_requirement) || ($has_staged_update && $plugin_installed_in_stage && !$is_stage_root_requirement)) { + $messages[] = 'It must be a root dependency.'; + } + + // If the plugin is misconfigured in either the active or stage directories, + // flag an error. + if (($plugin_installed_in_active && !$active_configuration_ok) || ($has_staged_update && $plugin_installed_in_stage && !$stage_configuration_ok)) { + $messages[] = 'The composer-exit-on-patch-failure key is not set to true.'; + } + } + + /** + * @return bool[] + */ + private function computePatcherStatus(): array { + return []; + } +} From 7a94378245056bd1e8beab35273573ca91064704 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Fri, 13 Mar 2026 08:16:45 +0000 Subject: [PATCH 2/3] Add regression test for phpstan/phpstan#12373 Co-Authored-By: Claude Opus 4.6 --- .../Variables/DefinedVariableRuleTest.php | 11 ++++++++ .../Rules/Variables/data/bug-12373.php | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-12373.php diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 9a8f17e7bfa..add6c66beb2 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1414,6 +1414,17 @@ public function testBug14274(): void $this->analyse([__DIR__ . '/data/bug-14274.php'], []); } + #[RequiresPhp('>= 8.0')] + public function testBug12373(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-12373.php'], []); + } + public function testBug14117(): void { $this->cliArgumentsVariablesRegistered = true; diff --git a/tests/PHPStan/Rules/Variables/data/bug-12373.php b/tests/PHPStan/Rules/Variables/data/bug-12373.php new file mode 100644 index 00000000000..4db53c3c1b9 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-12373.php @@ -0,0 +1,28 @@ += 8.0 + +namespace Bug12373; + +function test(): void +{ + $foo = []; + + [$always_a, $always_b, $always_c] = [rand(0, 1), rand(0, 1), rand(0, 1)]; + if (rand(0, 1)) { + [$maybe_a, $maybe_b, $maybe_c] = [rand(0, 1), rand(0, 1), rand(0, 1)]; + $flag = true; + } else { + $flag = false; + } + + if ($flag && $always_a !== $maybe_a) { + $foo[] = 'first'; + } + + if (($always_a && !$always_b) || ($flag && $maybe_a && !$maybe_b)) { + $foo[] = 'second'; + } + + if (($always_a && !$always_c) || ($flag && $maybe_a && !$maybe_c)) { + $foo[] = 'third'; + } +} From 304c35e5284f02874836a37f77cbabbabfc7352c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 13 Mar 2026 09:18:42 +0100 Subject: [PATCH 3/3] Update bug-12373.php --- .../Rules/Variables/data/bug-12373.php | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/PHPStan/Rules/Variables/data/bug-12373.php b/tests/PHPStan/Rules/Variables/data/bug-12373.php index 4db53c3c1b9..456068d4df3 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-12373.php +++ b/tests/PHPStan/Rules/Variables/data/bug-12373.php @@ -1,28 +1,29 @@ = 8.0 +declare(strict_types=1); + namespace Bug12373; -function test(): void +class HelloWorld { - $foo = []; + public function sayHello(int $id): void + { + $foo = []; - [$always_a, $always_b, $always_c] = [rand(0, 1), rand(0, 1), rand(0, 1)]; - if (rand(0, 1)) { - [$maybe_a, $maybe_b, $maybe_c] = [rand(0, 1), rand(0, 1), rand(0, 1)]; - $flag = true; - } else { - $flag = false; - } + if ($id) + { + $foo = 'foo'; + } + else + { + $value = 'my value'; + } - if ($flag && $always_a !== $maybe_a) { - $foo[] = 'first'; - } - - if (($always_a && !$always_b) || ($flag && $maybe_a && !$maybe_b)) { - $foo[] = 'second'; - } + $foo = "foo"; - if (($always_a && !$always_c) || ($flag && $maybe_a && !$maybe_c)) { - $foo[] = 'third'; + if (!$id) + { + echo 'value: ' . $value; + } } }