v6 Semantics: static types on a dynamic runtime

Lesson, slides, and applied problem sets.

View Slides

Lesson

v6 Semantics: type checking

This stage enforces static types before code generation.

This is the first module where the analyzer computes and propagates type information instead of only name tables.


1) Type model in this pack

Base types:

  • number
  • string
  • bool
  • nil

Structured types:

  • array<T>
  • map<K, V>
  • fn(P1, P2, ...) -> R

Plus one practical bucket:

  • unknown (used when inference lacks certainty or mixed constraints appear)

Assignability

  • exact structural match for most types
  • nil is assignable to any destination
  • unknown propagates as "compatible" to avoid false positives in early inference states

This makes analyzer behavior deterministic and still educational.


2) Declarations and inference

For let / var / const:

  • explicit annotation is validated against initializer
  • missing annotation:
    • initializer must provide clear inferable type
    • empty literals are ambiguous and should error unless contextual type exists

If inference fails:

  • report cannot infer type: <name>

const without initializer remains a structural rule:

  • const requires initializer: <name>

Function declarations:

  • parameter and return types are required
  • missing signature should be reported as a function signature error
  • function body is type-checked under declared return type

3) Expression typing in practice

Arithmetic:

  • + supports number+number and string+string
  • - * / require numbers
  • comparisons < <= > >= require numbers and return bool
  • == != require comparable types

Boolean logic:

  • && / || require booleans and return booleans
  • ! requires boolean

Flow checks:

  • if / while / for conditions must be bool-compatible
  • for-loops support cond and post typing in addition to init/body analysis

Calls:

  • callee must be a function type
  • arity must match
  • each argument must be assignable to declared param type

If not function:

  • call of non-function

If arity mismatch:

  • arity mismatch

4) Collections and indexing

array typing:

  • inferred from first element if no expected context
  • all subsequent elements must be assignable to the base element type
  • empty array literal needs contextual type (or explicit annotation)

map typing:

  • keys must share a stable key type and be valid map keys
  • values must share value type
  • empty map literal requires expected type context

Indexing:

  • array index requires number (or unknown)
  • map index key must match declared map key type
  • invalid index base returns type error (including non-collection indexing)

Map key validity check:

  • primitives and nil keys are valid
  • invalid key types produce key-type errors

5) Module + type integration

v6 combines module semantics with type rules:

  • module imports/exports still validated
  • exported declarations carry types into member access
  • member calls use the exported function signature/type

When analyzing util.add:

  • resolve module alias
  • resolve exported type for add
  • validate arguments via that function type

unknown export and unknown module continue to be front-end errors, often before deeper type mismatch checks at call sites.


6) Return and control-flow typing

Return checking now includes:

  • return outside function error remains unchanged
  • return expression must be assignable to function return type
  • return type mismatch: expected ... got ...

Break/continue rules remain from v4/v5 and still apply.


7) Practice checkpoints (almost solved)

1) Inference edge

Program:

let a = 1;
let xs = [];

Expected:

  • a infers number
  • xs reports cannot-infer without annotation

2) Binary typing

Program:

let n: number = "x" + 1;

Expected:

  • diagnostic for + operands not compatible

3) Boolean condition

Program:

if ("true") {}

Expected:

  • type mismatch: expected bool got string

4) Call + signature typing

Program:

fn add(a: number, b: number) -> number { return a + b; }
add(true, 1);

Expected:

  • type mismatch ... number got bool on first arg

5) Nil flexibility

Program:

let n: number = nil;

Expected:

  • valid (nil assignable to any type in this pack)

6) Array type enforcement

Program:

let xs = [1, "a"];

Expected:

  • first element infers number, second produces element mismatch

7) Map key restrictions

Program:

let bad = { [1,2]: 3 };

Expected:

  • invalid map key type if inferred key shape is non-primitive

8) Function signatures in modules

Program:

module util {
  export fn add(a: number, b: number) -> number { return a + b; }
}
module main {
  import util;
  util.add("x", 1);
}

Expected:

  • member resolves util.add to function type
  • arg type mismatch before runtime

Module Items

  • Semantic Analysis v6

    Validate v6 programs with static types and module rules.

    hard Upgrade to Pro to access hard problems
Join Discord