03 — Type Annotations in the AST and Parser
Type annotations let programmers express intent:
let x: Num = 42;
fn add(a: Num, b: Num): Num { return a + b; }
The parser must recognise the : Type syntax and store the annotation
in the AST.
AST changes
The LetStmt and function parameter nodes gain an optional type annotation:
struct LetStmt {
std::string name;
bool immutable;
ExprPtr init;
TypePtr annotation; // nullptr if absent
int line;
};
struct Param {
std::string name;
TypePtr annotation; // nullptr if absent
};
struct FnExpr {
std::vector<Param> params;
TypePtr retAnnotation; // nullptr if absent
StmtPtr body;
int line;
};
Parsing type annotations
A parseType helper handles the type grammar:
// Type ::= "Num" | "Bool" | "Str" | "Nil" | "Any"
// | "Fn" "(" TypeList ")" "->" Type
TypePtr Parser::parseType() {
Token t = advance();
if (t.lexeme == "Num") return mkNum();
if (t.lexeme == "Bool") return mkBool();
if (t.lexeme == "Str") return mkStr();
if (t.lexeme == "Nil") return mkNil();
if (t.lexeme == "Any") return mkAny();
if (t.lexeme == "Fn") {
expect(LParen);
std::vector<TypePtr> params;
while (peek().kind != RParen) {
params.push_back(parseType());
if (!match(Comma)) break;
}
expect(RParen);
expect(Arrow); // "->"
auto ret = parseType();
return mkFn(std::move(params), std::move(ret));
}
throw ParseError("[line " + std::to_string(t.line) +
"] Expected type annotation, got '" + t.lexeme + "'.");
}
Parsing let with annotation
StmtPtr Parser::parseLet() {
int line = advance().line; // consume 'let' / 'var'
bool immutable = (previous().kind == Let);
auto name = expect(Ident).lexeme;
TypePtr ann;
if (match(Colon)) ann = parseType(); // optional ": Type"
expect(Eq);
auto init = parseExpr(0);
expect(Semicolon);
return std::make_unique<LetStmt>(name, immutable, std::move(init), std::move(ann), line);
}
Parsing function parameters with annotations
std::vector<Param> Parser::parseParams() {
expect(LParen);
std::vector<Param> params;
while (peek().kind != RParen) {
auto name = expect(Ident).lexeme;
TypePtr ann;
if (match(Colon)) ann = parseType();
params.push_back({name, std::move(ann)});
if (!match(Comma)) break;
}
expect(RParen);
return params;
}
Token additions
The lexer needs two new token kinds:
Colonfor:separating name from type.Arrowfor->separating parameter types from return type.
-> is a two-character token; the lexer handles it in the - branch:
case '-':
return makeToken(match('>') ? Arrow : Minus);
Annotations are optional
All annotations are TypePtr defaulting to nullptr. Code without any
annotations is valid — it's treated as fully Any-typed (step 06). This
means cp-03/cp-04 programs are valid cp-05 programs without modification.