Skip to content

06 — Name resolution & the Tier-1 call graph #15

Description

@rahlk

Part of #9. Phase 1 — level 1. The hardest thinking of level 1 — budget accordingly.

Learning goals

Name resolution — the classic compiler problem, hand-built: scope chains, import maps, receiver
type inference. Plus mutation discipline in Rust: backfilling callee_signature in place while
iterating the symbol table teaches you why the borrow checker exists (collect-then-apply, split
borrows, or id-indexed updates — pick one and understand the trade).

Task

Resolve each recorded call site to a declaration and build the identity-only call graph:

  1. Build a resolution context per module: the use-map (alias → full path), the enclosing
    module path, cargo metadata's crate roots.
  2. Resolve, in order of confidence: fully-qualified paths (crate::a::b::f(...)) → exact;
    bare names through the scope chain + use-map → exact; Type::assoc_fn(...) incl. ::new
    exact; method calls recv.method(...) → infer the receiver type from locals/params you
    already recorded, look up inherent methods first, then trait impls.
  3. Dispatch policy decision (record it): for a dyn Trait receiver, expand to the trait
    method of every KNOWN implementing type (your CHA/RTA analog — declared-types-only vs
    instantiated-types-only changes precision; the skill says surface this choice, not bury it).
  4. Backfill callee_signature in place; emit coalesced edges (source, target, weight) with
    provenance: ["canrs"]. Unresolved sites (closures, fn pointers, macro-generated) stay
    recorded with None — an explicit fallback, never a guess, never a crash.
  5. Gate the whole thing behind -a 2; -a 1 stays symbol-table-only with call_graph: [].

Teacher's notes

  • Do NOT chase completeness: exact on monomorphic/static calls + explicit unresolved fallback
    beats clever-but-wrong. The upgrade path (issue backlog: adopt ra_ap_hir as the oracle) is
    planned; your hand-rolled resolver stays as the fast path and the thing you learned on.
  • signature_of() on BOTH sides is what makes edges byte-match — if a gate fails with a
    near-miss string, the canonicalizer has two callers disagreeing.

Gate (call-graph gate)

  • Zero dangling endpoints: every edge's source and target exists in the symbol table.
  • Named expected edges asserted exactly: a cross-module call, a Type::new call, an inherent
    method call, a trait-method call under your dispatch policy.
  • Every edge has non-empty provenance; callee_signature backfilled on resolved sites; output
    still validates.

Metadata

Metadata

Assignees

Labels

learning-ladderThe escalating-complexity curriculum issueslevel-1Symbol table + resolver call graph

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions