fix(php): refuse a member call whose receiver type the file claims (#21, closes #16) - #32
Merged
Merged
Conversation
A PHP file that writes `use Vendor\Sdk\Client;` has said which `Client` it means, but `_resolve_php_member_calls` never read `use` statements: it bound the receiver's short type name through a corpus-wide index whose only refusal rule was "more than one candidate", so the lone unrelated `App\Local\Client` satisfied the single-definition guard and minted an INFERRED 0.8 edge into a class the file never imported (#16). `PhpNameResolver` mirrors `CsharpNameResolver`: it answers with a `(node_id, decisive)` verdict built from the `use` metadata on `imports` edges (#19), the declared-FQN payload (#14) and the same type-definition index the fallback uses, and is consulted in FRONT of that fallback exactly like the C# call site. A claimed name that lands on no in-corpus class refuses instead of falling back. Written qualified annotations (#20) resolve the same way, absolute or namespace-relative. Strictly subtractive by construction: every node the resolver returns is looked up under the receiver's WRITTEN short name in the very index the fallback consults, so a positive verdict is always the answer the fallback would have given, and the only behavior change is the refusal. Binding an alias to a class its short name does not name is a recall addition and stays with #22. Verified differentially against v8 over a 34-file corpus of PHP receiver-typing shapes: 3 edges deleted, none added, re-pointed or re-scored. The refusal needs no new persisted marker — the `use` map belongs to the calling file, which an incremental rebuild always re-dispatches — and a path SHORTER than the written name is read as a stripped composer prefix rather than as a contradiction, so a class off its PSR-4 path keeps its edge on both paths. This is the fix for #16; the issue stays open for the orchestrator's gate.
This was referenced Aug 5, 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.
Fixes #21. Fixes #16 (spec #18, ticket 3/5 — design risk 6, measured during 0.9.34 release verification).
What
A new
PhpNameResolver(graphify/extractors/php.py, mirroringCsharpNameResolver) returns a(node_id, decisive)verdict and is consulted in_resolve_php_member_callsin front of the corpus-widetype_def_nidsfallback, in the exact shape of the C# call site. A short receiver-type name claimed by the calling file — ausealias (#16's case) or a written qualified annotation (#20's field) — that does not resolve to the in-corpus definition now refuses instead of falling back to the unique-bare-name match.Strictly subtractive by construction: positive verdicts can only be the fallback's own answer (the resolver looks up the written short name in the same index), so the change can only delete edges. Positive alias binding stays #22.
Verification beyond the suite
Differential extract against
v8@e713e02over a 34-file corpus covering every receiver-typing shape (bare/aliased/groupuse,use function/const, absolute and relative written FQNs, promoted params, params,new-locals, inlinenew, first-class callables, interfaces, duplicate short names, non-PSR-4 layout, global namespace): 3 edges deleted, 0 added, 0 re-pointed, 0 confidence changes — the three deleted are exactly the #16 false-edge class (use Vendor\Sdk\Client;,\Vendor\Sdk\Clientannotation, relativeLocal\Client).A real full-vs-incremental divergence found by probe during development (stripped composer prefixes:
App\Weird\Oddatsrc/Odd.php) was fixed by treating too-short paths as absent evidence, pinned bytest_non_psr4_layout_keeps_its_edge_incrementally. The residual gap (declared namespace disagreeing with an alignable path on incremental runs) is #23's marker, recorded in the issue comments.Test evidence
🤖 Generated with Claude Code