Skip to content

Narrow comparisons on a variable through its recorded defining call - #6383

Open
ondrejmirtes wants to merge 2 commits into
2.3.xfrom
dependent-types-result-provenance
Open

Narrow comparisons on a variable through its recorded defining call#6383
ondrejmirtes wants to merge 2 commits into
2.3.xfrom
dependent-types-result-provenance

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Adds a scope-threaded result-provenance map so that a comparison on a variable narrows like the same comparison on the call that produced it.

When $v = f(arg) is assigned, where f is one of count / sizeof / gettype / get_class / get_debug_type and arg is a plain variable, the scope records that $v holds that call's result. When a later === / == / match arm / switch case pins $v to a single-valued constant, PHPStan additionally runs the recorded call through the existing narrowing machinery (count-shape reconstruction, gettype string-to-type map, get_class object pin). This covers if, match and switch uniformly, both type flavours and both branch directions.

The entry is dropped on any write or impure invalidation of the target or the call's argument, kept across merges only when identical, and dropped at closure boundaries — so it is never less sound than the direct-call form.

Regression tests added; the full NodeScopeResolverTest stays green. A local issue-bot run confirms the three fixes and no regressions.

Closes phpstan/phpstan#14464
Closes phpstan/phpstan#13972
Closes phpstan/phpstan#523

🤖 Generated with Claude Code

When a variable is assigned the result of a pure information-carrying
call over a plain variable argument (count(), sizeof(), gettype(),
get_class(), get_debug_type()), the scope now records that provenance:
comparing the variable against a single-valued constant additionally
runs the call's own comparison narrowing, so

    $numParts = count($colParts);
    if ($numParts === 3) { ... }

reconstructs $colParts's shape exactly like the direct
`if (count($colParts) === 3)` form - through if/elseif, match arms and
switch cases alike, in both type flavours and both branch directions.

The provenance map is threaded through MutatingScope like the other
expression tables: merges keep only entries recording the identical
call on both sides, and invalidating any expression the entry mentions
(the target, or the call's argument - by printed-key containment)
drops it. Recording is a cheap name check at assignment; lookup is one
isset() per constant comparison on a variable.

None of the touched files is turbo-shadowed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGhnJQpUWRRpg6H5LuWJkA
@phpstan-bot

Copy link
Copy Markdown
Collaborator

You've opened the pull request against the latest branch 2.3.x. PHPStan 2.3 is not going to be released for months. If your code is relevant on 2.2.x and you want it to be released sooner, please rebase your pull request and change its target to 2.2.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants