Project call-target narrowings through stored booleans#5528
Merged
ondrejmirtes merged 2 commits into2.1.xfrom Apr 24, 2026
Merged
Project call-target narrowings through stored booleans#5528ondrejmirtes merged 2 commits into2.1.xfrom
ondrejmirtes merged 2 commits into2.1.xfrom
Conversation
Extends the conditional-expression machinery used for assignments like
`$ok = $x->foo() !== null` so the narrowing survives through a later
`if ($ok) { … }`. Previously only Variable/PropertyFetch/ArrayDimFetch/FuncCall
sure-type targets were projected, so method calls (and static/nullsafe calls)
were dropped entirely.
For call-typed sure-type targets (FuncCall, MethodCall, NullsafeMethodCall,
StaticCall) we only project when the sure-type expression is a sub-expression
of the assigned RHS — not the RHS itself. That keeps the narrowing of
`$x->foo()` in `$ok = $x->foo() !== null` while dropping the falsey-scalar
loop's `$this->nullable() === null` projections that would wrongly survive
across subsequent reassignments.
PHPStan virtual nodes (e.g. NativeTypeExpr), scalars and const-fetches are
rejected to avoid numeric-string array-key autocast and internal nodes
leaking into the conditional-expression map.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
df169d6 to
b3f4b84
Compare
This was referenced Apr 24, 2026
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
$ok = $x->foo() !== null) so the narrowing of$x->foo()survives through a laterif ($ok) { … }. Previously onlyVariable,PropertyFetch,ArrayDimFetchandFuncCallsure-type targets were projected, soMethodCall,NullsafeMethodCallandStaticCallwere silently dropped.FuncCall,MethodCall,NullsafeMethodCall,StaticCall) the projection only fires when the sure-type expression is a sub-expression of the assigned RHS — not the RHS itself. That keeps the narrowing of sub-call$x->foo()in$ok = $x->foo() !== null, while dropping the falsey-scalar loop's$this->nullable() === null-style projections that would otherwise wrongly re-narrow fresh calls after subsequent reassignments (the regression in this playground snippet where line 18 flipped fromint|nulltonull).NativeTypeExpr), scalars and const-fetches are rejected up front so internal nodes don't leak into the conditional-expression map and numeric-string exprStrings don't collide with PHP's array-key autocast.Test plan
conditional-expr-narrowing-through-variable.phpcovers the pure-method-call + stored-boolean case and thepreg_match(..., $matches)by-ref case (variable narrowing must survive even when the RHS has impure points).try-catch-reassign-method-narrowing.phplocks in the playground snippet: inside acatchblock,\$device = \$this->nullable();must beint|null, notnull— the original conditional holders from the first\$device = \$this->nullable()must not leak past the reassignment.tests/PHPStan/Analyser/NodeScopeResolverTest.php: 1532/1532.tests/PHPStan/Analyser/+tests/PHPStan/Rules/: 5646 tests, 59 pre-existing skips, 0 failures.🤖 Generated with Claude Code
closes phpstan/phpstan#9455
closes phpstan/phpstan#5207