← Back to AILP Home

Cross-module summaries

When a borrow signature has to be honoured at a call site in another module, the borrow checker needs to know the callee's parameter modes and — for return-borrows — which parameter the returned loan originates from. Inside a single compilation unit this is trivial: the checker has the full AST. Across module boundaries the answer comes from a .npksummary sidecar written next to each .npk source file.

When sidecars are written

After BorrowChecker::analyze() finishes for a module, the writer emits foo.npksummary next to foo.npk only if the module exports at least one function whose summary carries a return borrow (XMOD-DEC-006). Modules with no cross-module return-borrow exports produce no sidecar — keeping the source tree free of empty []-summary files.

Filter rules (XMOD-DEC-006/007):

When sidecars are loaded

During the next compilation, the loader runs in BorrowChecker::analyze()'s pre-pass — before the AST-based seedImportedSummaries walk (XMOD-DEC-004). Loaded summaries win on first write; the local AST seeder then fills in anything the sidecar did not cover. Missing sidecars silently fall back to AST seeding; stale sidecars (source .npk mtime > sidecar mtime, XMOD-DEC-008) trigger a stderr warning and fall back to AST seeding.

To see exactly which sidecars were consulted, set:

NPK_TRACE_XMOD_SUMMARY=1 npkc build foo.npk

Each attempted load prints one [xmod-sidecar] … line.

Sidecar schema (version 1)

{
  "version": 1,
  "module":  "<source path>",
  "summaries": [
    { "name": "<source name>",
      "mangled": "<post-mangling symbol>",
      "is_trait_method": <bool>,
      "borrow_params": [
        { "index": <u32>, "mode": "$$i"|"$$m",
          "name": "<param>" }
      ],
      "return_borrow":  { "kind": "self"|"named"|"first",
                          "from_param": "<name>"   // when kind == named
                        }  | null
    }, ...
  ]
}

return_borrow.kind is one of:

The schema version is a u32. If a sidecar's version field does not match the compiler's current value, the loader raises a hard error:

error[ARIA-057]: summary schema mismatch: sidecar version <N> is not
                 supported by this compiler (expected version 1)
hint: rebuild module 'foo' to refresh its .npksummary sidecar

There is no silent fallback on a schema mismatch (XMOD-DEC-005) — mixing stale and fresh summaries is the kind of footgun this chapter exists to prevent.

Worked example

Module pair.npk:

struct:Pair = { int32:a, int32:b };

pub func:get_a = $$i int32($$i Pair:p) {
    pass &p.a;
};

After npkc build pair.npk the writer produces pair.npksummary:

{
  "version": 1,
  "module":  "pair.npk",
  "summaries": [
    { "name": "get_a", "mangled": "pair__get_a",
      "is_trait_method": false,
      "borrow_params": [
        { "index": 0, "mode": "$$i", "name": "p" }
      ],
      "return_borrow":  { "kind": "first" }
    }
  ]
}

Module main.npk, in another build invocation:

use pair;

func:main = int32() {
    pair.Pair:pp = pair.Pair{a: 7, b: 9};
    $$i int32:r = pair.get_a(pp);   // sidecar lets the checker tie r → pp
    exit *r;                        // 7
};

Without the sidecar the loader would have nothing to tie r's host back to pp and the checker would emit ARIA-023 with the "callee 'pair.get_a' has no borrow summary available" message and the rebuild hint. With the sidecar in place, the borrow parameter index resolves to 0 and r is treated as a $$i loan on pp for the rest of main's body.

Diagnostics

K-semantics status

The K layer tracks the runtime value-flow lock for return-borrow calls in tests 162 ($$m return-borrow flow), 163 (named return-borrow second-param passthrough), and 164 (trait self return-borrow). A first-class <func-summaries> cell is deferred (XMOD-DEC-009) until the K grammar gains $$i T / $$m T return types; the current value-flow lock matches the v0.31.8.5 W-13 precedent (K tests 155–157).