Skip to content

Expose resolved method calls as a CALLS edge#910

Open
paracycle wants to merge 1 commit into
mainfrom
uk_cypher_method_calls
Open

Expose resolved method calls as a CALLS edge#910
paracycle wants to merge 1 commit into
mainfrom
uk_cypher_method_calls

Conversation

@paracycle

@paracycle paracycle commented Jul 8, 2026

Copy link
Copy Markdown
Member

Goal

Expose method calls in the Cypher graph, resolved to the method declarations they target — so the graph can answer call-graph questions, not just definition/inheritance ones.

What it adds

A new relationship type:

CALLS: Document → Declaration (a Method)

It maps a file's method references to the method declaration each one resolves to, for calls whose receiver type is statically known:

  • a constant receiver (Foo.bar), resolved through the receiver's singleton-class ancestry (where class methods live), and
  • an implicit self call in a known scope (e.g. calling a sibling method from inside an instance method).
-- Everything a file calls (resolved)
MATCH (d:Document)-[:CALLS]->(m:Method) RETURN d.name, m.name

-- Callers of a specific method
MATCH (d:Document)-[:CALLS]->(m:Method {unqualified_name: 'bar()'}) RETURN d.name

How it resolves

The indexer already records each call with the receiver type's name (the singleton-class name for Foo.bar, the enclosing type for a self call). The schema:

  1. resolves that name to a declaration (the same way constant references resolve), then
  2. looks the method up along that declaration's ancestry, matching on the base name — method members are keyed name() while call sites record the bare name, so a direct id match doesn't work.

Scope / non-goals

Calls with a dynamic or unknown receiver (foo.bar on a local, a method-chain result, etc.) carry no receiver from the indexer and are intentionally not represented — resolving them would require type inference. This edge covers the statically-resolvable subset only; it's best-effort (first match in ancestry order) rather than a guaranteed runtime target.

The resolver currently lives in the cypher schema (consistent with how it already maps the graph); a future refactor could share a Graph-level method resolver with query.rs's completion path.

Verification

cargo test -p rubydex query::cypher (20 tests, incl. resolution + reverse "who calls this" + that dynamic-receiver calls are excluded); clippy + fmt clean; --schema lists CALLS; full gem suite green.

@paracycle paracycle requested a review from a team as a code owner July 8, 2026 17:48
@paracycle paracycle force-pushed the uk_add_cypher_query_engine branch from cc553f6 to 8714ea8 Compare July 8, 2026 18:16
@paracycle paracycle force-pushed the uk_cypher_method_calls branch 2 times, most recently from 7c184a1 to 6a12d7d Compare July 8, 2026 20:04
@paracycle paracycle force-pushed the uk_add_cypher_query_engine branch from 57c979c to f99fc2c Compare July 8, 2026 20:17
@paracycle paracycle force-pushed the uk_cypher_method_calls branch from 6a12d7d to 2eaa2d6 Compare July 8, 2026 20:18
@paracycle paracycle force-pushed the uk_add_cypher_query_engine branch from f99fc2c to fcbbf98 Compare July 9, 2026 20:09
@paracycle paracycle force-pushed the uk_cypher_method_calls branch from 2eaa2d6 to 557b811 Compare July 9, 2026 20:12
@paracycle paracycle force-pushed the uk_add_cypher_query_engine branch from fcbbf98 to 0206dbe Compare July 9, 2026 20:35
Base automatically changed from uk_add_cypher_query_engine to main July 9, 2026 20:43
Add a `CALLS` relationship (`Document` → `Method`) that maps method
references to the method declarations they resolve to, for calls whose
receiver type is statically known:

- a constant receiver (`Foo.bar`), resolved through the receiver's
  singleton-class ancestry, and
- an implicit `self` call in a known scope.

The indexer already records these calls with a receiver-type name; the
schema resolves that name to a declaration and looks the method up along
its ancestry (matching the base name, since method members are keyed
`name()` while call sites record `name`). Calls with a dynamic/unknown
receiver carry no receiver and are intentionally not represented — those
would require type inference.

This makes call-graph queries possible, e.g. callers of a method:
`MATCH (d:Document)-[:CALLS]->(m:Method {unqualified_name: 'bar()'}) RETURN d.name`.
@paracycle paracycle force-pushed the uk_cypher_method_calls branch from 557b811 to 5d58c11 Compare July 9, 2026 20:55
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.

1 participant