Skip to content

Fix phpstan/phpstan#14214: coalesce is slow with lots of variables#5113

Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-amsqf5i
Closed

Fix phpstan/phpstan#14214: coalesce is slow with lots of variables#5113
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-amsqf5i

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

Coalesce chains with many variables (e.g., $x0 ?? $x1 ?? ... ?? $x16) were extremely slow to analyse (15-20 seconds reported). This is the same class of issue as phpstan/phpstan#14207 (fixed for BooleanAnd/BooleanOr), but for the null coalesce operator.

Changes

  • Modified src/Analyser/NodeScopeResolver.php (line ~3695): Skip the expensive $scope->getType($expr->right) call when the right operand of a coalesce is itself a Coalesce expression. The call is only needed to check for explicit NeverType (e.g., $x ?? throw new Exception()), which cannot occur for nested Coalesce expressions.
  • Added regression test in tests/PHPStan/Analyser/data/bug-14214.php with a 17-variable coalesce chain and assertType verification.
  • Added test method testBug14214 in tests/PHPStan/Analyser/AnalyserIntegrationTest.php.

Root cause

The coalesce operator (??) is right-associative, so $a ?? $b ?? $c parses as $a ?? ($b ?? $c). In the NodeScopeResolver coalesce handler, $scope->getType($expr->right) was called to check if the right side is an explicit NeverType. For a nested Coalesce right operand, getType calls getCoalesceType, which recursively evaluates the entire remaining chain — O(N) work per level. Since this happens at each of the N nesting levels, total complexity was O(N^2).

The fix skips this getType call when the right operand is a Coalesce node. A nested Coalesce expression can never be an explicit NeverType (it always includes the possibility of returning a non-never left operand value), so the check is unnecessary for this case.

Test

The regression test tests/PHPStan/Analyser/data/bug-14214.php reproduces the original issue with 17 nullable variables chained via ??, and verifies the inferred type is int<0, 33>|null. Before the fix, this test took ~2.2s; after the fix, it completes in ~0.7s.

Fixes phpstan/phpstan#14214

- 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 phpstan/phpstan#14214
$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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems really specific,

  • this at least need a comment
  • and maybe they is more case to skip ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be better maybe but it solves the problem. I recently did the same fix with Ternary.

@staabm staabm self-assigned this Mar 1, 2026
@staabm
Copy link
Copy Markdown
Contributor

staabm commented Mar 1, 2026

I can no longer reproduce slowness

@staabm staabm closed this Mar 1, 2026
@staabm staabm deleted the create-pull-request/patch-amsqf5i branch March 1, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants