Skip to content

Read a changing codebase

Compiler code changes quickly. A file can move. A helper can be split into three helpers. An internal data structure can be replaced.

The questions a compiler must answer usually change more slowly:

  • What source shape did the author write?
  • Which declaration does a name refer to?
  • Is the program well typed?
  • What checked meaning crosses into code generation?
  • What proves that the result is still correct?

Use those questions as landmarks. Treat filenames as directions for one known version, not as the architecture itself.

This guide separates information by purpose and by how quickly it is expected to change.

Layer What it explains When it should change
Guided path A successful setup, reading, testing, and review sequence When the contributor task or canonical commands change
Concept Compiler ideas and language ownership When the language or an architectural decision changes
Boundary A responsibility’s input, output, guarantee, and evidence When responsibility moves between parts of the compiler
Reading snapshot A few exact implementation decisions for one trace input When the trace identity or explained code path changes
Address snapshot Current files, symbols, and source links Whenever the mapped implementation moves
Executable evidence One small visible compiler path When its fixture, tool identity, or expected boundary changes

Foundations are concepts

Pages such as How a compiler works explain ideas without depending on one repository layout.

Change journeys follow boundaries

They follow responsibility from an input to a checked output and name the evidence expected at each handoff.

Code clinics are reading snapshots

They pin short excerpts to the exact source that produced one trace. Use them to learn how to cross code boundaries, not as permanent architecture.

Big maps are snapshots

They link to exact source revisions. Use them to find today’s starting point, not as a promise that a directory will keep its name.

Traces are executable evidence

They exercise small, user-visible paths in CI so that a prose explanation is not the only proof.

When you find an unfamiliar stage, do not begin by reading every type in it. Answer four smaller questions.

  1. What enters? Source text, tokens, a syntax node, checked types, an intermediate program, or target code?
  2. What must be true when it leaves? Names resolved, types known, control flow explicit, layout fixed, or diagnostics complete?
  3. Who consumes the result? The checker, a lowering step, every backend, one target toolchain, or an editor service?
  4. What proves the handoff? A diagnostic test, an IR assertion, generated source, program output, a fixed point, or compatibility evidence?

These answers describe a contract. Private helpers can change without changing that contract.

For a successful program, follow one piece of information forward:

source spelling
-> parsed shape
-> resolved declaration
-> checked type or operation
-> intermediate meaning
-> target behavior

For a rejected program, begin with the visible diagnostic and work backward:

diagnostic code and source span
<- rule that rejected the program
<- resolved names and inferred types used by the rule
<- syntax shape that reached the rule

The second route is often shorter. A diagnostic is a visible statement of which stage owns a rule.

Imagine a call with an argument of the wrong type. The exact structs and functions may change, but the responsibility chain remains recognizable:

parser preserves a call shape and its source span
resolver connects the callee name to one declaration
checker compares the argument with the declared parameter type
diagnostic reports the mismatch at the useful source location
backend is not reached for this invalid program

If a refactor renames the call node or moves diagnostic formatting, ask whether these responsibilities still hold. If they do, update the snapshot map; do not rewrite the conceptual explanation.

  1. Choose one observable question

    Begin with a command, diagnostic, generated fragment, or program output. “Where is everything parsed?” is too broad. “Where is this call rejected?” is a useful question.

  2. Collect search words from the boundary

    A diagnostic code, command name, source spelling, IR operation, manifest field, or emitted symbol is usually more durable than a helper name.

  3. Use the matching change journey

    The reference journey and Native journey show which responsibilities should be crossed and which should not.

  4. Open the pinned map only when you need an address

    First use the reference code clinic or Native code clinic to learn the reading method on one small input. The reference map and Native map then provide more exact source locations for their named versions.

  5. Run the smallest visible proof

    Use an existing focused test or one executable trace before reading farther. Evidence tells you whether your mental model matches the compiler.

Read for responsibility before structure. Keep concepts separate from current addresses. Ask for an input, a guarantee, a consumer, and evidence at every handoff.

That method is slower than opening the largest file for the first five minutes. It is much faster over the life of a changing compiler.