feat(resolution): PHP @property PHPDoc synthesis + interface override bridging#554
Open
hou3301-byte wants to merge 4 commits into
Open
feat(resolution): PHP @property PHPDoc synthesis + interface override bridging#554hou3301-byte wants to merge 4 commits into
hou3301-byte wants to merge 4 commits into
Conversation
… bridging Add two capabilities that close dynamic-dispatch holes in PHP codebases: 1. **PHP added to IFACE_OVERRIDE_LANGS**: interface/abstract method → implementation method bridging now works for PHP, producing `calls` edges that let trace/callees flow through `implements`/`extends` boundaries. 2. **@Property PHPDoc synthesizer** (`phpPhpdocPropertyEdges`): parses `@property TypeName $propName` annotations from class/interface/trait docblocks and emits `references` edges to the declared types. This captures the dependency graph of PHP service locators (e.g. `Ctx` with `__get()`), DI containers, and ORM dynamic properties that tree-sitter extraction alone cannot see. 3. **php-phpdoc framework resolver**: detects PHP projects using @Property annotations and resolves `phpdoc-property:TypeName` references through the standard resolution pipeline. Validated on a 4,374-file PHP codebase (MPF framework with heavy service-locator usage): +1,677 edges (1,108 references + 569 calls), a 1.3% graph increase with zero false positives. All 1,086 existing tests pass. Co-authored-by: Cursor <cursoragent@cursor.com>
Add 27 tests covering: - phpPhpdocResolver: detect/extract/resolve/claimsReference unit tests (primitive filtering, union types, @property-read/-write, namespaced types, abstract/final classes, multiple classes per file) - phpPhpdocPropertyEdges: end-to-end synthesizer tests with fixture PHP projects verifying references (heuristic) edges - PHP interface override: end-to-end tests verifying calls (heuristic) edges for interface→impl and abstract→concrete bridging Fix two bugs found by tests: - @Property regex now supports leading backslash for absolute namespace paths (\Vendor\Cache\Redis) - Synthesizer `via` metadata now preserves the exact annotation variant (@property-read/@property-write) instead of hardcoding @Property Co-authored-by: Cursor <cursoragent@cursor.com>
The phpPhpdocPropertyEdges synthesizer previously only created class→class references edges. Now it also scans all PHP method bodies for chained property calls (->propName->methodName()) and creates method→method calls edges through the @Property mapping, bridging the __get() magic method indirection that tree-sitter cannot statically resolve. This enables callers/trace queries to work for patterns like: $this->ctx->user_factory->findByUid($uid) $this->ctx->pay->firstcharge->showFirstCharge($uid) Co-authored-by: Cursor <cursoragent@cursor.com>
- P0: strip comments (via stripCommentsForRegex) and string interiors (via blankPhpStrings) before regex matching to prevent false hits from code examples in comments or string literals - P2: support variable-dereference pattern where a @Property target is assigned to a local variable then called through it: $factory = $this->ctx->user_factory; $factory->findByUid($uid); - P3: skip methods whose body doesn't contain any known @Property name (propName pre-check) to reduce unnecessary regex work - P1 (doc): document the no-receiver-type-verification limitation in the function's JSDoc comment as a known trade-off Co-authored-by: Cursor <cursoragent@cursor.com>
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
IFACE_OVERRIDE_LANGS: interface/abstract method → implementation method bridging now works for PHP, producingcallsedges that lettrace/calleesflow throughimplements/extendsboundaries.@propertyPHPDoc synthesizer (phpPhpdocPropertyEdges):referencesedges: parses@property TypeName $propNameannotations from class/interface/trait docblocks and emitsreferencesedges to the declared types. Captures the dependency graph of PHP service locators (e.g.Ctxwith__get()), DI containers, and ORM dynamic properties that tree-sitter extraction alone cannot see.callsedges (NEW): scans all PHP method bodies for chained property calls (->propName->methodName()) and synthesizes method→methodcallsedges through the@propertymapping. This bridges the__get()magic-method indirection that tree-sitter cannot statically resolve, enablingcallers/tracequeries to work for patterns like$this->ctx->user_factory->findByUid($uid)and$this->ctx->pay->firstcharge->showFirstCharge($uid).php-phpdocframework resolver: detects PHP projects using@propertyannotations and resolvesphpdoc-property:TypeNamereferences through the standard resolution pipeline.Motivation
PHP codebases using service locator patterns (e.g. custom MVC frameworks with
Ctx.__get()) have significant dynamic-dispatch holes. A typical codebase with 4,374 PHP files and 305 interfaces has thousands of dynamic property accesses through__get()that CodeGraph's tree-sitter extraction cannot resolve.The
@propertyPHPDoc standard annotation is widely used in these codebases to document the types returned by__get(). By parsing these annotations, we can recover the lost edges with high precision and zero false positives.Phase 2 motivation: Phase 1 only created class→class
referencesedges (static dependency), but did not create method→methodcallsedges (runtime invocation). Withoutcallsedges,callers(User_Factory::findByUid)andtrace(Controller::action, User_Factory::findByUid)would still return empty results because tree-sitter extracts$this->ctx->user_factory->findByUid()as the unresolvable reference namethis->ctx->user_factory.findByUid. Phase 2 fixes this by building apropName → targetTypemapping from all@propertyannotations and scanning method bodies for->propName->methodName(patterns, creating the missingcallsedges.Validation
Tested on a 4,374-file PHP codebase (custom MPF framework with heavy service-locator usage):
references(heuristic)calls(heuristic)Changes
src/resolution/callback-synthesizer.ts'php'toIFACE_OVERRIDE_LANGS; addphpPhpdocPropertyEdges()with Phase 1 (references) + Phase 2 (calls)src/resolution/frameworks/php-phpdoc.tssrc/resolution/frameworks/index.tsphpPhpdocResolver__tests__/php-phpdoc.test.tsTest plan
npm test) — 52 test files, 0 failuresnpm run build)phpPhpdocResolverdetect/resolve/extractphpPhpdocPropertyEdgesPhase 1 (references edges)phpPhpdocPropertyEdgesPhase 2 (calls edges):$this->ctx->user_factory->findByUid()→show→findByUidcalls edge$this->ctx->pay->firstcharge->showFirstCharge()→charge→showFirstChargecalls edge