Fix #14100: Ternary does not narrow type#4915
Merged
ondrejmirtes merged 2 commits into2.1.xfrom Feb 13, 2026
Merged
Conversation
- Generalized ternary handler in TypeSpecifier to handle all truthy contexts, not just the `$cond ? $expr : false` special case - Converts `$cond ? $a : $b` to equivalent `($cond && $a) || (!$cond && $b)` so existing BooleanOr/BooleanAnd narrowing logic applies - New regression test in tests/PHPStan/Analyser/nsrt/bug-14100.php covering instanceof, is_string/is_int, nested ternaries, and short ternary syntax - Root cause: the ternary handler required `$scope->getType($expr->else)->isFalse()->yes()` which excluded any ternary where the else branch wasn't literally `false` Closes phpstan/phpstan#14100
Automated fix attempt 2 for CI failures.
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
Ternary expressions inside
assert()(and other truthy contexts) did not narrow types. For example,assert($cond ? $x instanceof Foo : $x instanceof Bar)left$xunnarrowed, while the equivalentif/elsewith separateassert()calls worked correctly.Changes
src/Analyser/TypeSpecifier.php(lines 1062-1072) to handle all truthy/falsey contexts, not just the special case where the else branch isfalse$cond ? $a : $bis now converted to the equivalent($cond && $a) || (!$cond && $b), which delegates to the existing BooleanOr/BooleanAnd narrowing logictests/PHPStan/Analyser/nsrt/bug-14100.phpcovering:instanceofchecksis_string/is_int)Root cause
The ternary handler in
TypeSpecifier::specifyTypesInCondition()had two restrictive conditions:!$context->null()— excluded null context (though this didn't matter sinceAssertFunctionTypeSpecifyingExtensionuses truthy context)$scope->getType($expr->else)->isFalse()->yes()— required the else branch to evaluate tofalseThis meant only the pattern
$cond ? $expr : falsewas handled (converted to$cond && $expr). Any ternary where both branches carried meaningful type information (like$cond ? $x instanceof A : $x instanceof B) was silently ignored.The fix removes the
isFalse()restriction and generalizes the conversion to handle both branches, including the negated condition for the else branch.Test
tests/PHPStan/Analyser/nsrt/bug-14100.php— NSRT test verifying thatassert()with ternary conditions correctly narrows types. Coversinstanceof,is_*()functions, nested ternaries, and short ternary syntax.Fixes phpstan/phpstan#14100