fix(php): resolve method calls through $this-> properties on their declared type (#1220)#1251
Merged
Conversation
…clared type (#1220) A call whose receiver is a class property — $this->dep->method(), the dominant call shape in DI-style PHP (Symfony/Laravel constructor injection) — produced no call edge: the extractor records the receiver as raw text (this->dep), which no resolution strategy could type, so callers/impact silently missed every production consumer of a method while reporting its unit-test callers (local-new receivers). Three coordinated pieces, all riding existing machinery: - inferLocalReceiverType: strip the PHP this-> prefix and widen the scan to the whole file — the same treatment CFML component-scoped fields already get. The existing PHP typed-parameter pattern then recovers the type from a promoted constructor parameter, a typed property declaration, or a classic constructor parameter alike. - matchMethodCall: resolve the this->prop.method shape EXCLUSIVELY via declared-type inference + resolveMethodOnType validation; the name-similarity strategies never see it, so a property whose type can't be recovered stays unlinked rather than guessed. - defer + conformance retry: a ref whose method lives on the property type's supertype resolves only once implements/extends edges exist — push it to the existing deferred-chain retry (PHP_PROP_SHAPE) and dispatch it back through matchMethodCall in the conformance pass. Validation: 15-case corpus (promoted/classic/interface/inherited/ same-name-collision/untyped/deep-chain/local-shadowing) all resolve at 0.9 or stay deliberately unlinked; 9 new vitest tests; full suite passes. On six real PHP codebases (PHP 7.1-8.5, Symfony 3.2-8.0) a sampled repository method went 0/7 -> 7/7 production callers and 49/49 manually verified property-receiver edges were correct with no false positives. Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
…s only The property-receiver resolution (#1220/#1221) reused the generic local-variable patterns with a whole-file scan, CFML-style. In PHP a plain $prop local or parameter can never alias $this->prop, so a same-named variable in any method above the call could mistype the property and mint a wrong 0.9-confidence call edge — e.g. a local $greeter = new OtherGreeter() in an unrelated helper routed $this->greeter->greet() to OtherGreeter::greet despite the property being typed Greeter. A this->prop receiver now only consults property-shaped declarations: a modifier-prefixed typed declaration (typed property or promoted constructor parameter) and the $this->prop = new X() pseudoconstructor. Classic untyped properties are typed by following the $this->prop = $var assignment to $var's declaration within that function only — which also picks up multi-line constructor signatures and typed setter injection. Union-typed and otherwise unrecoverable properties stay unlinked, as before. Validated on Monolog: 53 new property-receiver call edges, sampled edges all route to the property's true declared type; parked this-> refs 430 -> 377. Co-Authored-By: Claude Fable 5 <noreply@anthropic.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.
Fixes #1220. Carries #1221 by @w0lan (thank you for the excellent report and fix!) plus a hardening commit — landing directly so the fix ships without another review round-trip; all credit for the mechanism and validation corpus to @w0lan.
What #1221 contributed (carried verbatim, rebased onto main)
PHP method calls through a class property —
$this->dep->method(), the dominant call shape in constructor-injection codebases — produced nocallsedge. #1221 routes thethis->prop.methodshape exclusively through declared-type inference +resolveMethodOnTypevalidation, and defers unresolved refs to the conformance pass so supertype-inherited methods resolve onceextends/implementsedges exist. 9 tests.What the hardening commit adds
The original scan reused the generic local-variable patterns whole-file (the CFML component-scope treatment). In PHP a plain
$proplocal or parameter can never alias$this->prop, so a same-named variable in any method above the call could mistype the property and mint a wrong 0.9-confidence edge — reproduced with a local$greeter = new OtherGreeter()in an unrelated helper routing$this->greeter->greet()toOtherGreeter::greetdespite the property being typedGreeter.Now a
this->propreceiver consults property-shaped declarations only:private ?Foo $prop;) and promoted constructor parameters (private readonly Foo $prop), and$this->prop = new Foo(), plus$this->prop = $varto$var's typed declaration bounded by the enclosingfunction— which also picks up multi-line constructor signatures and typed setter injection.Union-typed and otherwise unrecoverable properties stay unlinked — a wrong inference produces no edge rather than a wrong one, per #1221's own design principle.
Validation
__tests__/php-property-receiver-resolution.test.ts(the original 9 unchanged + 3 interference regressions, multi-line ctor, setter injection, scope-boundary negative); full suite green (135 files, 2,324 passed).this->refs 430 → 377); every sampled edge routes to the property's true declared type.Known limitations (unchanged from #1221): fluent second hops, nullsafe
?->calls (missing from the extractor's callTypes entirely), docblock-only types. Note for users: existing indexes need a re-index to gain the new edges — the parked refs predate the #1240 retry ledger.🤖 Generated with Claude Code