Resolve ConditionalTypeForParameter children before converting to ConditionalType to prevent infinite traversal#5507
Merged
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom Apr 21, 2026
Conversation
…ConditionalType` to prevent infinite traversal - In `ResolvedFunctionVariantWithOriginal::resolveConditionalTypesForParameter()`, traverse children (target, if, else) of `ConditionalTypeForParameter` first, then convert to `ConditionalType` and return without further traversal. This prevents entering the subject (the passed argument type), which may contain its own `ConditionalTypeForParameter` with a colliding parameter name (e.g. `$value`), causing infinite recursion. - Applied the same fix to the analogous pattern in `TypeSpecifier::resolveAssertTypes()`. - Triggered by `is_X(is_Y(...))` first-class callable combinations where both functions have conditional return types using the same parameter name `$value`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PHPStan goes OOM (infinite loop) when analysing
is_callable(is_callable(...))and similar patterns where a first-class callable of a function with a conditional return type is passed as an argument to another function with a conditional return type using the same parameter name.Changes
src/Reflection/ResolvedFunctionVariantWithOriginal.php: InresolveConditionalTypesForParameter(), changed the traversal order — traverse children ofConditionalTypeForParameter(target, if, else) first via$traverse(), then convert toConditionalTypeand return directly without further traversal into the subject (passed argument).src/Analyser/TypeSpecifier.php: Applied the same fix to the analogousConditionalTypeForParameter→ConditionalTypeconversion in assert type resolution.tests/PHPStan/Analyser/nsrt/bug-13872.php: Regression test coveringis_callable(is_callable(...)),is_callable(is_array(...)),is_array(is_string(...)), and many otheris_X(is_Y(...))combinations.Root cause
When
resolveConditionalTypesForParameterencounters aConditionalTypeForParameternode, it converts it to aConditionalTypewhose subject is the passed argument type. TheTypeTraverserthen recurses into theConditionalType's children, including the subject. If the subject is aClosureType(from a first-class callable likeis_callable(...)), the traversal enters the closure's return type, which also contains aConditionalTypeForParameterwith the same parameter name ($value). This triggers the same conversion again with the same subject, creating an infinite loop.All
is_*type-checking functions (is_callable,is_array,is_string,is_int,is_float,is_bool,is_null,is_numeric,is_object,is_scalar,is_countable,is_iterable,is_resource) use$valueas their parameter name in their conditional return type@return ($value is T ? true : false), so anyis_X(is_Y(...))combination triggers this.The fix resolves children first (which only visits target/if/else — no subject), then converts, and returns without further traversal into the newly created subject.
Test
Added
tests/PHPStan/Analyser/nsrt/bug-13872.phpwith 13 assertions covering variousis_X(is_Y(...))first-class callable combinations that previously caused infinite loops, verifying both the absence of the hang and correct type inference results.Fixes phpstan/phpstan#13872