From bde9ba0af015b9500107c3792e940a42466f3daf Mon Sep 17 00:00:00 2001 From: staabm <120441+staabm@users.noreply.github.com> Date: Sun, 1 Mar 2026 11:41:48 +0000 Subject: [PATCH] Fix slow analysis on large coalesce chains - Avoided expensive $scope->getType($expr->right) call when the right side of a coalesce is itself a Coalesce expression - For right-associative chains like $a ?? $b ?? $c ?? $d, getType on the nested chain recursively evaluated the entire remaining chain at each level, causing O(N^2) complexity - Since a nested Coalesce expression is never an explicit NeverType, the getType call can be safely skipped for Coalesce right operands - New regression test in tests/PHPStan/Analyser/data/bug-14214.php Closes https://github.com/phpstan/phpstan/issues/14214 --- src/Analyser/NodeScopeResolver.php | 2 +- .../Analyser/AnalyserIntegrationTest.php | 6 ++ tests/PHPStan/Analyser/data/bug-14214.php | 88 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/bug-14214.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index d5f62a9b8a7..dd0641824c1 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3692,7 +3692,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto $rightScope = $scope->filterByFalseyValue($expr); $rightResult = $this->processExprNode($stmt, $expr->right, $rightScope, $storage, $nodeCallback, $context->enterDeep()); - $rightExprType = $scope->getType($expr->right); + $rightExprType = $expr->right instanceof Coalesce ? null : $scope->getType($expr->right); if ($rightExprType instanceof NeverType && $rightExprType->isExplicit()) { $scope = $scope->filterByTruthyValue(new Expr\Isset_([$expr->left])); } else { diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 820338325d9..7d17f188a2e 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1644,6 +1644,12 @@ public function testBug14207And(): void $this->assertNoErrors($errors); } + public function testBug14214(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-14214.php'); + $this->assertNoErrors($errors); + } + public function testBug13945(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-13945.php'); diff --git a/tests/PHPStan/Analyser/data/bug-14214.php b/tests/PHPStan/Analyser/data/bug-14214.php new file mode 100644 index 00000000000..2539d106d84 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-14214.php @@ -0,0 +1,88 @@ +|null', $x); + } +}