Skip to content

fix: resolve scoped/static (Class::method) call targets for PHP and Rust#568

Open
NaeemHaque wants to merge 1 commit into
tirth8205:mainfrom
NaeemHaque:fix/scoped-static-call-resolution
Open

fix: resolve scoped/static (Class::method) call targets for PHP and Rust#568
NaeemHaque wants to merge 1 commit into
tirth8205:mainfrom
NaeemHaque:fix/scoped-static-call-resolution

Conversation

@NaeemHaque

@NaeemHaque NaeemHaque commented Jun 18, 2026

Copy link
Copy Markdown

Summary

Scoped/static calls (Mailer::send() in PHP, Mailer::send(x) / Self::method() in Rust) were stored as CALLS edges with the intermediate Class::method target, which matches neither the qualified-name lookup (<file>::Class.method) nor the bare-name lookup. The edges dangled, so callers_of, get_impact_radius, and tests_for silently missed these callers — especially impactful in Rust, where Type::method() / Self::method() is the dominant call form.

Fixes #567.

What this does

Adds a post-build resolver (code_review_graph/scoped_resolver.py) in the same style as the existing Spring/Temporal/ReScript resolvers. It rewrites resolvable Class::method CALLS targets to the canonical node qualified name, wired into both full build and incremental update (gated to .php/.rs).

It is deliberately conservative so it never fabricates an edge:

  • Only genuine two-part Class::method targets are resolved. Multi-segment Rust module paths (a::b::c) are left alone — resolving them by their last two segments would point at unrelated types.
  • PHP namespaces are stripped before lookup (App\MailerMailer).
  • self / Self resolve to the enclosing class.
  • Resolves on a single matching (class, method) node, or disambiguates via the caller file's IMPORTS_FROM edges. Ambiguous or unknown targets (external types like Vec::new) are left untouched.
  • Rewritten edges are tagged confidence_tier = INFERRED to distinguish them from directly-extracted ones.

Known limitation: same-name collisions across files with no import statement have nothing to disambiguate on and stay unresolved. Strictly better than today, not 100%.

Scope is PHP and Rust — the two languages that actually produce a dangling Class::method edge. C++/Ruby produce no CALLS edge for scoped calls (a separate extraction gap), and R's :: is external-package access; both are out of scope here.

Tests

tests/test_scoped_resolver.py (14 tests): PHP cross-file and namespaced calls, Rust cross-file and Self::, the multi-segment false-edge guard, external-target-left-untouched, import disambiguation (resolved + ambiguous), incremental re-resolution, idempotency, and INFERRED tagging.

  • uv run pytest — all pass (full suite green)
  • uv run ruff check code_review_graph/ — clean
  • mypy clean; coverage gate (65%) passes

Scoped/static calls — PHP `Mailer::send()`, Rust `Notifier::notify()`,
`Self::method()` — were stored as CALLS edges with the intermediate
`Class::method` target form, which matches neither the qualified-name
(`<file>::Class.method`) nor the bare-name lookup path. The edges dangled,
so callers_of / get_impact_radius / tests_for silently under-reported these
callers (notably every Rust associated-fn/constructor call).

Add a conservative post-build resolver (scoped_resolver.py) that rewrites
resolvable `Class::method` targets to the canonical node qualified name,
mirroring the existing Spring/Temporal/ReScript resolvers, wired into both
full build and incremental update. It only rewrites unambiguous (class,
method) matches or import-disambiguated ones; external/unknown targets like
`Vec::new` are left untouched so no false edge is created. self/static/Self/
this resolve to the enclosing class; parent to its base. Resolved edges are
tagged confidence_tier=INFERRED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Static/scoped calls (Class::method) aren't tracked as callers in PHP and Rust

1 participant