From d7da40a146f8033d6f6be01da56340b5c7d10c86 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Sat, 21 Feb 2026 20:14:30 +0000 Subject: [PATCH] Fix assignment inside match arm condition not recognized - Variable assignments in match arm conditions (e.g. `is_dir($baseDir = ...)`) were lost after commit 3beb8c626 replaced processExprNode with filterByTruthyValue - filterByTruthyValue only narrows types without walking the AST, so assignments within condition expressions were not discovered for the arm body scope - Fix transfers newly-defined variables from the condition processing scope to the body scope after applying truthiness filtering - New regression test in tests/PHPStan/Rules/Variables/data/bug-13981.php Closes https://github.com/phpstan/phpstan/issues/13981 --- src/Analyser/NodeScopeResolver.php | 26 +++++++++++++++++++ .../Variables/DefinedVariableRuleTest.php | 11 ++++++++ .../Rules/Variables/data/bug-13981.php | 10 +++++++ 3 files changed, 47 insertions(+) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-13981.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 607bf5bd19..1b84ebfa39 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -208,6 +208,7 @@ use UnhandledMatchError; use function array_fill_keys; use function array_filter; +use function array_flip; use function array_key_exists; use function array_key_last; use function array_keys; @@ -4355,6 +4356,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $filteringExprs = []; $armCondScope = $matchScope; $condNodes = []; + $armCondResultScope = $matchScope; foreach ($arm->conds as $j => $armCond) { if (isset($armCondsToSkip[$i][$j])) { continue; @@ -4376,6 +4378,30 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $filteringExpr = $this->getFilteringExprForMatchArm($expr, $filteringExprs); $bodyScope = $matchScope->filterByTruthyValue($filteringExpr); + $condResultScope = $armCondResultScope; + $matchScopeKnownVars = array_flip(array_merge($matchScope->getDefinedVariables(), $matchScope->getMaybeDefinedVariables())); + foreach ($condResultScope->getDefinedVariables() as $varName) { + if (isset($matchScopeKnownVars[$varName])) { + continue; + } + $bodyScope = $bodyScope->assignVariable( + $varName, + $condResultScope->getVariableType($varName), + $condResultScope->getNativeType(new Variable($varName)), + $condResultScope->hasVariableType($varName), + ); + } + foreach ($condResultScope->getMaybeDefinedVariables() as $varName) { + if (isset($matchScopeKnownVars[$varName])) { + continue; + } + $bodyScope = $bodyScope->assignVariable( + $varName, + $condResultScope->getVariableType($varName), + $condResultScope->getNativeType(new Variable($varName)), + $condResultScope->hasVariableType($varName), + ); + } $matchArmBody = new MatchExpressionArmBody($bodyScope, $arm->body); $armNodes[$i] = new MatchExpressionArm($matchArmBody, $condNodes, $arm->getStartLine()); diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 112a57d4cc..af3437d512 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1257,4 +1257,15 @@ public function testBug12944(): void $this->analyse([__DIR__ . '/data/bug-12944.php'], []); } + #[RequiresPhp('>= 8.0')] + public function testBug13981(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-13981.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-13981.php b/tests/PHPStan/Rules/Variables/data/bug-13981.php new file mode 100644 index 0000000000..03f2467591 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-13981.php @@ -0,0 +1,10 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug13981; + +$path = match (true) { + is_dir($baseDir = dirname(__DIR__).'/lang') => $baseDir, + default => '/translations', +};