fix(php): bind namespaced receivers in ::class and constructor position - #1775
Open
mwebber92 wants to merge 2 commits into
Open
fix(php): bind namespaced receivers in ::class and constructor position#1775mwebber92 wants to merge 2 commits into
mwebber92 wants to merge 2 commits into
Conversation
PHP writes every namespaced name as a `qualified_name` node. Two extractor paths only accepted the simple forms, so a class referenced through a namespace was invisible to the graph: - extractStaticMemberRef accepted `identifier | type_identifier | simple_identifier | name | scoped_type_identifier`, none of which a namespaced receiver produces. `Foo\Bar::class`, `\App\Models\User::TABLE` and the alias form `Type\Bankverbindung::class` were dropped with no edge and no unresolved ref, so `impact` reported the class had no consumers. - extractInstantiation strips a `.` or `::` qualifier but not PHP's `\`, so `new Foo\Bar()` pushed the unresolvable literal `Foo\Bar`. Both now match on the trailing simple name, which is what walkPhpTypePosition already does for type hints and what the class node is stored as. The instantiation strip is scoped to PHP because a backslash carries no qualifier meaning in the other languages sharing that path. Measured on a 327-file PHP tree: `references` edges 426 -> 728, with node count and every other edge kind unchanged. `impact` on a class used only through an alias goes from 3 of 6 consumer files to 6 of 6. The alias is made moot rather than resolved: `Type\Bankverbindung` and `\App\SoapTypes\Bankverbindung` both reduce to `Bankverbindung`, so a same-named class in another namespace stays ambiguous here, exactly as it is for a type hint today. codegraph-kernel/src/php.rs carries the same two defects and is unchanged, so kernel-php-parity will diverge once a kernel binary is staged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014gmQvNnfH9URudZJeJU4ck
php.rs carried both defects the TypeScript walker did, so with php in DEFAULT_ROUTED a default build's output was unchanged by the previous commit. - extract_static_member_ref repeated the same receiver-kind list, dropping a `qualified_name` receiver: `Foo\Bar::class`, `\App\Models\User::TABLE`, and the alias form after `use X as Type;`. - strip_generic_and_qualifier split on `.` and `::` but not `\`, so `new \App\Models\User()` kept the whole qualified text. Both edits are local to php.rs and need no language gate: the file is PHP-only and it carries its own private copy of strip_generic_and_qualifier rather than sharing one. kernel-php-parity needed no fixture change — it compares the two walkers against each other at runtime rather than against goldens, so it goes green once both agree. Its docstring described the old instantiation shape and is updated to match. Verified with the kernel staged, which is also what routes php through it: kernel-php-parity green (8 tests), and php-qualified-static-member-refs green (5 tests) now exercising the native path rather than the wasm fallback. Full suite 4,336 passed / 3 failed, those three (object-literal-methods, ui-steps-api x2) failing identically on clean main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014gmQvNnfH9URudZJeJU4ck
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.
Problem
PHP writes every namespaced name as a
qualified_namenode. Two extractor paths accept only the simple forms, so a class referenced through a namespace produces no edge and nounresolved_refsrow — there is no trace of it anywhere in the index, andimpacttruthfully reports the class has no consumers.extractStaticMemberRefaccepts a receiver only when its node kind isidentifier | type_identifier | simple_identifier | name | scoped_type_identifier. None of those is what a namespaced receiver produces, so all of these are dropped:A bare
Bankverbindung::classis anameand was always handled — which makes this easy to miss.extractInstantiationstrips a.or::qualifier but not PHP's\, sonew App\SoapTypes\Bankverbindung()pushes the unresolvable literalApp\SoapTypes\Bankverbindung.The diagnostic tell is that a type hint binds while
::classon the next line does not:Fix
walkPhpTypePositionalready handlesqualified_namecorrectly, with the comment "match on the trailing simple name — what the class node is stored as, and what auseimport brings into scope." This applies the same rule in the two places that lack it.Both walkers, in two commits.
codegraph-kernel/src/php.rscarried the identical pair of defects, and since PHP is inDEFAULT_ROUTEDthe kernel is what actually runs — fixing only the TypeScript side would leave a default build's output unchanged. The Rust commit needs no language gating:php.rsis PHP-only and carries its own private copy ofstrip_generic_and_qualifierrather than sharing one.The TypeScript changes are gated to PHP: C# also has a
qualified_namenode and is inSTATIC_MEMBER_LANGS, so an ungated change would add unrelated C# edges, and a backslash carries no qualifier meaning in the other languages sharing the instantiation path.kernel-php-parityneeded no fixture change — it compares the two walkers against each other at runtime rather than against goldens, so it stays green once both agree. Its docstring described the old instantiation shape and is updated to match.This makes the alias moot rather than resolved —
Type\Bankverbindungand\App\SoapTypes\Bankverbindungboth reduce toBankverbindung— so a same-named class in another namespace stays ambiguous here, exactly as it is for a type hint today. Resolving aliases properly is a separate change;extractPHPImportsrecordsuse X\Y as ZwithexportedNameset to the last segment as though it were a class, so it cannot expand a namespace alias.Evidence
Measured on real PHP codebases (Symfony-style SOAP clients and WordPress plugins), 12 indexes:
references+instantiatesedgesNode count and every other edge kind are unchanged — the change is purely additive. On one repo,
impactfor a class used through an alias went from 3 of 6 consumer files to 6 of 6.Re-indexing all twelve a second time through the kernel rather than the WASM walker reproduced 34,336 exactly, every index identical — a broader parity check than the three torture fixtures.
Tests
__tests__/php-qualified-static-member-refs.test.ts— five cases: aliased, fully-qualified and namespace-relative::class, a qualified constructor, and a negative case pinning the capitalization guard. Each block names the mutation it catches.Verified they can fail: with the test file in place and the
tree-sitter.tschanges reverted, 4 go red and the negative case stays green.Full suite with the kernel staged, so every
kernel-*-paritysuite actually runs rather than skipping: 3,234 passed, 0 failed on the v1.6.0-based branch. Onmainit is 4,336 passed / 3 failed, and those three (object-literal-methods,ui-steps-api×2) fail identically on cleanmain— pre-existing and unrelated.Cargo release build of the kernel is ~60 s warm, ~110 s cold.