Pre-AlphaMetaScript is in early design phase. The compiler is not yet available.Join Discord for updates
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 user = User.parse(response) // Returns Result<User, ParseError>

Primitive Types

MetaScript has familiar primitives with precise semantics:

TypeDescriptionExample
number64-bit float (IEEE 754)42, 3.14
intPlatform-native integer42
i32, i64Sized integers42i32
u8, u32, u64Unsigned integers255u8
stringUTF-8 string"hello"
booleanTrue or falsetrue
voidNo value-
neverImpossible type-

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
}

Product Types (Records)

Classes with automatic structural equality:

@derive(Eq, Hash)
class Point {
  x: number
  y: number
}

const p1 = new Point(1, 2)
const p2 = new Point(1, 2)
console.log(p1.equals(p2)) // true

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}!`
}

Traits (Interfaces)

Define behavior contracts:

trait Serializable {
  toJSON(): string
  static fromJSON(json: string): Self
}

@derive(Serializable)
class User {
  name: string
  email: string
}

// Works with any Serializable
function save<T: Serializable>(item: T): void {
  localStorage.setItem("data", item.toJSON())
}

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