Step 1 of 3 · Reading · ~3 min
Adding Type Annotations to the Language
Type System & Optimization
Parsing Types Before Checking Types
Every static type system starts the same way: extend the grammar to
accept type annotations, before writing a single line of type-checking
logic. This lesson is deliberately narrow — parse var x: number = 42;
and fun add(a: number, b: number): number { ... } and attach the
annotation to the AST, but don't validate anything yet. That separation
matters: parsing is about syntax (can this program even be read?),
checking is a semantic pass on top of an already-valid parse tree. Mixing
the two makes both harder to get right.
Extending the Grammar
The core addition is a single new token, :, appearing in two places:
varDecl -> "var" IDENT ( ":" IDENT )? "=" expression ";"
funDecl -> "fun" IDENT "(" ( param ( "," param )* )? ")" ( ":" IDENT )? block
param -> IDENT ( ":" IDENT )?
Both the variable's type and the return type are optional — this is a
gradually typed extension, not a redesign. Existing untyped code
(var x = 42;) must keep parsing exactly as before; the annotation is pure
addition.
Function parameters follow the same pattern, just repeated per parameter,
plus one more optional : type after the closing ) for the return type:
Why Store, Not Enforce, Right Now
Notice the parser never asks "is number a valid type name?" or "does 42
actually look like a number?" — it just records the name of whatever
identifier follows the colon. That's intentional: type names are just
syntax at this stage (they could even be misspelled — nubmer parses
fine). The next lesson's type checker is the pass that assigns meaning to
these names and rejects mismatches. Keeping parsing "dumb" here means the
grammar work is fully done and testable in isolation, before any semantic
rules exist to get wrong.
What to build
Extend your tokenizer to recognize : as its own token, then extend
var and fun parsing to optionally consume a type annotation, defaulting
to "any" when omitted. Output a readable dump of what was parsed (e.g.
decl x : number, fun add(a: number, b: number) : number) so you can
confirm the annotations round-trip correctly before the checker in the
next lesson ever runs against them. Make sure omitted annotations
(var y = 1;) still parse without error — that's the case most likely to
break if the colon-handling isn't fully optional.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…