Closed PreviewCompiler source opens July 1, 2026. Playground and binary available today.Join Discord →
MetaScript

Sound Types

MetaScript has a sound type system that catches errors at compile-time, not runtime. Unlike TypeScript's "gradual" types, MetaScript types are guaranteed.

Type Soundness

In TypeScript, types can lie:

// TypeScript - this compiles but crashes at runtime
const user: User = JSON.parse(response) // No guarantee it's a User!

In MetaScript, types are verified:

// MetaScript type-safe parsing
const { ok, value: user } = JSON.parse<User>(response); // return Result<User, ParseError>

// Or shorthand for TypeScript-compatible, yet still safe parsing with try
const user = try JSON.parse<User>(response);

// Or flexible/un-typed parse that behaves very much like JavaScript
const data = JSON.parse(response); // return flexible JsonValue
console.log(data.name);
console.log(data.nested.level.name); // return null if nested fields don't exist, no exception

const name: string = data.nest.level.name;
const inferredStringType = data.nest.level.name.asString("default value"); // or .asString() mean default ""

Primitive Types

MetaScript has familiar primitives with precise semantics:

TypeDescriptionC Mapping
numberIEEE 754 64-bit floatdouble
int8|16|32|64Sized signed integersint8_t, etc.
uint8|16|32|64Sized unsigned integersuint8_t, etc.
float32|64Sized floatsfloat, double
stringMutable UTF-8 with COWmsString
uint8[]Byte array, zero-copy bridge with stringmsUint8Array
char8-bit characterchar
booleanTrue or falsebool
cstringC-compatible string pointerconst char*
voidNo valuevoid
neverUnreachable (bottom type)-
unknownType-safe anyvoid*

Algebraic Data Types

Sum Types (Unions)

Tagged unions that the compiler tracks:

type Result<T, E> =
  | { ok: true, value: T }
  | { ok: false, error: E }

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return { ok: false, error: "Division by zero" }
  }
  return { ok: true, value: a / b }
}

// Compiler enforces handling both cases
const result = divide(10, 2)
if (result.ok) {
  console.log(result.value) // Compiler knows this is number
} else {
  console.error(result.error) // Compiler knows this is string
}

Discriminated Unions (TypeScript superset)

Use match in type position for precise variant matching with enum discriminants:

enum NodeKind { NumLit, StrLit, BinExpr }

type NodeData = match (kind: NodeKind) {
  NodeKind.NumLit  => { value: number },
  NodeKind.StrLit  => { value: string },
  NodeKind.BinExpr => { op: string, left: Node, right: Node },
};

// Construction is validated — wrong fields for a variant = compile error
const node: NodeData = { kind: NodeKind.NumLit, value: 42 };

// Field access after narrowing
match (node.kind) {
  NodeKind.NumLit  => console.log(node.value),   // number
  NodeKind.StrLit  => console.log(node.value),   // string
  NodeKind.BinExpr => console.log(node.op),      // string
}

Structs (Value Types)

Stack-allocated value types — no heap, no refcounting:

struct Vec2 { x: float64; y: float64; }

const p: Vec2 = { x: 1.0, y: 2.0 };  // stack-allocated

// Compiler auto-selects optimal C ABI per parameter
function length(v: Vec2): float64 {
  return Math.sqrt(v.x * v.x + v.y * v.y);
}

Generics

Full parametric polymorphism:

class Stack<T> {
  private items: T[] = []

  push(item: T): void {
    this.items.push(item)
  }

  pop(): T | undefined {
    return this.items.pop()
  }
}

const numbers = new Stack<number>()
numbers.push(42)
numbers.push("oops") // Compile error!

Type Inference

MetaScript infers types where possible:

// Type inferred as number[]
const numbers = [1, 2, 3]

// Type inferred as (x: number) => number
const double = (x) => x * 2

// Return type inferred as string
function greet(name: string) {
  return `Hello, ${name}!`
}

Type Guards

Runtime checks that refine types:

function process(value: string | number) {
  if (typeof value === "string") {
    // Compiler knows value is string here
    console.log(value.toUpperCase())
  } else {
    // Compiler knows value is number here
    console.log(value.toFixed(2))
  }
}

Strict Null Safety

No more undefined is not a function:

function findUser(id: string): User | null {
  return db.users.find(u => u.id === id)
}

const user = findUser("123")
// user.name // Compile error! Might be null

if (user) {
  user.name // OK - compiler knows it's User
}

// Or use optional chaining
user?.name // Returns string | undefined

Next Steps