Skip to content

5. Your first syntax change

New syntax is the most invasive type of change: it touches multiple stages and increases long-term maintenance cost.

This chapter shows how to do it safely and predictably.

RFC required for new language features

If you are proposing a new user-visible language feature (syntax or semantics), write an RFC first:

Bugfixes and chores do not require an RFC.

Before you start

Read:

Your mental checklist

Keep the pipeline aligned (to avoid language/tooling drift):

  • Syntax crate (loaves/kernel/incan_syntax/): lexer → parser → AST → diagnostics
  • Formatter (loaves/compiler/incan_format/): prints AST back (idempotent; never emits invalid syntax)
  • Semantic core (loaves/kernel/incan_lang/): canonical vocab / shared semantic helpers (avoid duplicating “meaning” in multiple layers)
  • Compiler (loaves/compiler/):
    • typechecker (incan_frontend/) validates and annotates
    • lowering (incan_ir/) turns AST into IR
    • emission (incan_emit/) generates correct Rust
  • Runtime/stdlib (loaves/stdlib/<component>/{src,rust}/): behavior that can live outside the compiler should live here

Rule of thumb: prefer pushing shared meaning “down” into incan_lang/incan_syntax/the stdlib facets, and keep the driver (incan_driver/) and the incan command line (loaves/toolchain/incan-cli/) focused on orchestration and pipeline wiring.

You should expect Rust exhaustiveness errors to guide your work when you add enum variants.

Pick a “first syntax change” that stays small

A good first syntax change:

  • is local (one new statement or expression form),
  • has a simple typing rule,
  • emits Rust in an obvious way,
  • can be covered by 1–2 regression tests.

Avoid starting with a feature that requires new runtime types, new module system rules, or complex ownership behavior.

Next

Next chapter: 06. Tooling loop: formatter + tests.