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 (crates/incan_syntax/): lexer → parser → AST → diagnostics
  • Formatter (src/format/): prints AST back (idempotent; never emits invalid syntax)
  • Semantic core (crates/incan_core/): canonical vocab / shared semantic helpers (avoid duplicating “meaning” in multiple layers)
  • Compiler (src/frontend/, src/backend/):
    • typechecker validates and annotates
    • lowering turns AST into IR
    • emission generates correct Rust
  • Runtime/stdlib (crates/incan_stdlib/, stdlib/): behavior that can live outside the compiler should live here

Rule of thumb: prefer pushing shared meaning “down” into incan_core/incan_syntax/incan_stdlib, and keep the incan (root) crate 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.