diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 677aa31845..0799c83f3c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4386,6 +4386,8 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $filteringExprs = []; $armCondScope = $matchScope; $condNodes = []; + $armCondResultScope = $matchScope; + $bodyScope = null; foreach ($arm->conds as $j => $armCond) { if (isset($armCondsToSkip[$i][$j])) { continue; @@ -4401,12 +4403,17 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto if ($armCondType->isTrue()->yes()) { $hasAlwaysTrueCond = true; } - $armCondScope = $armCondResult->getScope()->filterByFalseyValue($armCondExpr); + $armCondScope = $armCondResultScope->filterByFalseyValue($armCondExpr); + if ($bodyScope === null) { + $bodyScope = $armCondResultScope->filterByTruthyValue($armCondExpr); + } else { + $bodyScope = $bodyScope->mergeWith($armCondResultScope->filterByTruthyValue($armCondExpr)); + } $filteringExprs[] = $armCond; } $filteringExpr = $this->getFilteringExprForMatchArm($expr, $filteringExprs); - $bodyScope = $matchScope->filterByTruthyValue($filteringExpr); + $bodyScope ??= $matchScope->filterByTruthyValue($filteringExpr); $matchArmBody = new MatchExpressionArmBody($bodyScope, $arm->body); $armNodes[$i] = new MatchExpressionArm($matchArmBody, $condNodes, $arm->getStartLine()); @@ -4423,7 +4430,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $hasYield = $hasYield || $armResult->hasYield(); $throwPoints = array_merge($throwPoints, $armResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $armResult->getImpurePoints()); - $matchScope = $matchScope->filterByFalseyValue($filteringExpr); + $matchScope = $armCondScope->filterByFalseyValue($filteringExpr); } if (!$hasDefaultCond && !$hasAlwaysTrueCond && $condType->isBoolean()->yes() && $condType->isConstantScalarValue()->yes()) { diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 112a57d4cc..1a9cc01ae1 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1235,6 +1235,26 @@ public function testBug10909(): void $this->analyse([__DIR__ . '/data/bug-10909.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'], [ + [ + 'Undefined variable: $baseDir', + 34, + ], + [ + 'Variable $baseDir might not be defined.', + 46, + ], + ]); + } + #[RequiresPhp('>= 8.0')] public function testBug7705(): void { 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..336f0c2e69 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-13981.php @@ -0,0 +1,61 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug13981; + +function foo(): string +{ + $path = match (true) { + is_dir($baseDir = dirname(__DIR__).'/lang') => $baseDir, + default => '/translations', + }; + + return $path; +} + +function foo2(): string +{ + if (rand(0, 1)) { + $baseDir = ''; + } + + $path = match (true) { + is_dir($baseDir = dirname(__DIR__).'/lang') => $baseDir, + default => '/translations', + }; + + return $path; +} + +function foo3(): string +{ + $path = match (true) { + is_dir(dirname(__DIR__).'/lang2') => $baseDir, + is_dir($baseDir = dirname(__DIR__).'/lang') => $baseDir, + default => '/translations', + }; + + return $path; +} + +function foo4(): string +{ + $path = match (true) { + is_dir(dirname(__DIR__).'/lang2'), + is_dir($baseDir = dirname(__DIR__).'/lang') => $baseDir, + default => '/translations', + }; + + return $path; +} + +function foo5(): string +{ + $path = match (true) { + is_dir($baseDir = dirname(__DIR__).'/lang') => '$baseDir', + default => $baseDir, + }; + + return $path; +}