v6 Types: static guarantees for a dynamic VM
Lesson, slides, and applied problem sets.
View SlidesLesson
v6 Types: static guarantees for a dynamic VM
Type checking in v6 is a front-end guarantee, not an alternate runtime. Its job is to reject invalid programs early and make module contracts explicit.
1) Why a static type pass exists
Even with dynamic execution, types improve:
- faster failure feedback before compile/link
- stable API contracts for exported module symbols
- more deterministic diagnostics in tests and classrooms
In this pack, type checking is conservative and flow-sensitive enough for correctness teaching.
2) Type grammar
Type:
- number
- bool
- string
- nil
- array<Type>
- map<Type, Type>
- fn(Type, ... ) -> Type
- unknown (checker internal placeholder)
No full polymorphism is modeled. fn types are explicit and structural.
3) Assignability rules
- exact structural match for non-
nilvalues nilis assignable to any type in this pack- function assignment/call compatibility checks arity and each argument slot
unknownpropagates when information is incomplete and is treated as permissive in comparisons
unknown is important so one missing inference does not produce cascaded false positives.
4) Inference and ambiguity
Inference succeeds when shape is clear from:
- initializer expressions
- explicit annotations
- context expectation (assignment target or function parameter)
It fails when shape is ambiguous, for example:
- empty array literal without expected context
- empty map literal without expected context
Failure message: cannot infer type: <var>.
Annotations override ambiguity:
let xs: array<number> = [];is valid with explicit type context.
5) Declaration and assignment checks
For declarations:
- explicit annotation must be compatible with initializer (if present)
- unannotated declarations require inferable initializer
For assignment:
- right side must be assignable to left side declared/inferred type
Because of nil compatibility, let n: number = nil is valid while let n: string = 1 is not.
6) Expression typing rules
+supports number+number and string+string-,*,/require numbers- comparisons (
<,<=,>,>=) require numbers - equality supports compatible structured operands in this pack
&&,||,!require booleans
Type mismatches here produce precise operand-direction diagnostics.
7) Collections and indexing
array<T>indexing returnsTand requires numeric indexmap<K,V>indexing requires key compatible withK, returnsV- map keys must be key-type-compatible and not invalidly hash-incompatible
This catches many runtime indexing failures in analysis.
8) Function typing and return behavior
For fn:
- parameter declarations define call contract
- call sites are checked for arity and argument assignability
- function return must be assignable to declared return type
- missing returns are either flagged by explicit path checks or treated per pack return-policy
9) Module type contracts
Exports expose static types in module interface.
For imported member calls (alias.fn()): analyzer checks:
- alias exists and resolves module
- exported member exists and has function type
- call args match exported signature
This ensures module calls are validated before runtime.
10) Error quality and flow
Keep output deterministic:
- one user-facing primary mismatch per expression
- include expected/got forms where practical
- preserve stable first-error behavior from analyzer
Suggested classes:
type mismatch: expected ... got ...cannot infer typearity mismatchcall of non-functioninvalid operand
11) Practice checkpoints (almost solved)
1) Structural array mismatch + let x: array<number> = ["a"]
Expected: type mismatch at declaration.
2) Nil assignment + let x: number = nil
Expected: accepted (nil compatibility).
3) Map inference + let m: map<string, number> = {}
Expected: valid via annotation-driven expectation.
4) Function argument mismatch + fn add(a:number,b:number)->number { return a+b } then add(true, 1)
Expected: mismatch on first arg.
5) Exported signature + export fn id(x: number) -> number then import caller passes string
Expected: call-type mismatch before runtime.
6) Ambiguous empties + let xs = {} or let ys = []
Expected: inference error unless typed context exists.
7) Non-function call + let x = 1; x()
Expected: call of non-function.
8) Return coverage + fn f(x:number) -> number { if (x > 0) { return x; } }
Expected: return-path mismatch/error depending on explicit return-policy check.
Module Items
Program Lexer v6