v6 Types: static guarantees for a dynamic VM

Lesson, slides, and applied problem sets.

View Slides

Lesson

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-nil values
  • nil is assignable to any type in this pack
  • function assignment/call compatibility checks arity and each argument slot
  • unknown propagates 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 returns T and requires numeric index
  • map<K,V> indexing requires key compatible with K, returns V
  • 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 type
  • arity mismatch
  • call of non-function
  • invalid 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

    Lex the v6 language with type annotations and arrow tokens.

    medium Sign in to access medium and hard problems
Join Discord