Skip to content

Fix #14207: slow analysis on return with a big OR condition (wordpress)#5076

Merged
ondrejmirtes merged 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-0fbs9kw
Feb 27, 2026
Merged

Fix #14207: slow analysis on return with a big OR condition (wordpress)#5076
ondrejmirtes merged 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-0fbs9kw

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

Fixes extremely slow analysis when a function returns a large || (BooleanOr) condition chain, such as checking a variable against 80+ string constants (common in WordPress HTML tag checking code). Analysis time dropped from ~15 seconds to ~0.15 seconds for the reproducing case.

Changes

  • Modified src/Analyser/NodeScopeResolver.php: Changed the falsey scope callback for BooleanOr/LogicalOr processing from $rightResult->getScope()->filterByFalseyValue($expr) to $rightResult->getScope()->filterByFalseyValue($expr->right)
  • Added regression test tests/PHPStan/Analyser/nsrt/bug-14207.php reproducing the WordPress is_special() pattern with ~80 OR conditions
  • Updated tests/PHPStan/Rules/Functions/data/bug-7156.php assertion for semantically equivalent type description (non-empty-array vs non-empty-array<mixed, mixed>)

Root cause

When processing a left-associative chain like A || B || C || ... || Z, at each level of the BooleanOr AST, NodeScopeResolver calls $leftResult->getFalseyScope() to determine the scope for processing the right operand. This triggers the lazy falsey scope callback from the inner level, which called filterByFalseyValue($expr) on the entire inner BooleanOr chain. filterByFalseyValue invokes TypeSpecifier::specifyTypesInCondition() which recursively processes the chain. This resulted in O(N^2) total work — at each of N levels, the TypeSpecifier reprocessed a chain of length up to N.

The fix recognizes that $rightResult->getScope() is already derived from $leftResult->getFalseyScope() (the right side was processed in the left's falsey scope), so the left chain's falsey narrowing is already baked into the scope. We only need to additionally filter by the right operand being false, not the entire expression. This reduces the work to O(N) — each level does O(1) work filtering by a single leaf expression.

Test

Added tests/PHPStan/Analyser/nsrt/bug-14207.php which contains a class with a method returning a chain of ~80 || string comparisons (modeled after WordPress's WP_HTML_Processor::is_special()). Without the fix, this test takes ~15 seconds; with the fix, it completes in ~0.15 seconds.

Fixes phpstan/phpstan#14207

- Changed BooleanOr falsey scope computation to avoid reprocessing the
  entire chain through TypeSpecifier at each level
- Instead of filterByFalseyValue on the full expression, only filter by
  the right operand since the left's narrowing is already in the scope
- Reduces O(N^2) work to O(N) for chains of N conditions
- New regression test in tests/PHPStan/Analyser/nsrt/bug-14207.php
- Updated bug-7156 test assertion for equivalent type description

Closes phpstan/phpstan#14207
@ondrejmirtes ondrejmirtes force-pushed the create-pull-request/patch-0fbs9kw branch from 64495a4 to 850ddeb Compare February 27, 2026 16:42
@ondrejmirtes ondrejmirtes merged commit cd618ea into phpstan:2.1.x Feb 27, 2026
353 of 356 checks passed
@staabm
Copy link
Contributor

staabm commented Feb 27, 2026

great to see that triggering the bot for this issue yielded a waaaay better fix than my PR

@staabm staabm deleted the create-pull-request/patch-0fbs9kw branch February 27, 2026 17:16
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.

3 participants