Skip to main content

Type System

Script features a structural type system inspired by TypeScript. Types are checked at compile time and erased at runtime, providing safety without performance overhead.

Structural Typing

Script uses structural typing (duck typing), where type compatibility is determined by the structure of types rather than their names. If two types have compatible shapes, they are considered compatible.

interface Point {
x: number;
y: number;
}

interface Coordinate {
x: number;
y: number;
}

// These are compatible because they have the same structure
let p: Point = { x: 10, y: 20 };
let c: Coordinate = p; // OK - same structure

This is different from nominal typing (used in Java/C#) where types must be explicitly declared as related.

Type Annotations

Basic Types

let count: number = 42;
let name: string = "Script";
let active: boolean = true;
let nothing: null = null;
let missing: undefined = undefined;

Any and Unknown

// 'any' opts out of type checking - use sparingly
let flexible: any = 42;
flexible = "now a string"; // OK

// 'unknown' is safer - requires type checking before use
let data: unknown = fetchData();
// data.property; // Error: must narrow type first

Arrays

let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"];

Functions

function add(a: number, b: number): number {
return a + b;
}

// Arrow functions
let multiply: (x: number, y: number) => number = (x, y) => x * y;

// Optional parameters
function greet(name: string, greeting?: string): string {
return (greeting || "Hello") + ", " + name;
}

Interfaces

Interfaces define the shape of objects:

interface User {
id: number;
name: string;
email?: string; // Optional property
}

let user: User = {
id: 1,
name: "Alice"
// email is optional, can be omitted
};

Interface Extension

interface Animal {
name: string;
}

interface Dog extends Animal {
breed: string;
bark(): void;
}

let dog: Dog = {
name: "Buddy",
breed: "Golden Retriever",
bark: () => console.log("Woof!")
};

Type Aliases

Create custom type names:

type ID = number;
type Point = { x: number, y: number };
type StringOrNumber = string | number;

let userId: ID = 123;
let position: Point = { x: 10, y: 20 };

Union Types

A value can be one of several types:

let value: string | number = "hello";
value = 42; // OK

function format(input: string | number): string {
if (typeof input === "string") {
return input.toUpperCase();
}
return input.toString();
}

Type Inference

Script infers types when annotations are omitted:

let x = 42;           // Inferred as number
let s = "hello"; // Inferred as string
let arr = [1, 2, 3]; // Inferred as number[]

function double(n: number) {
return n * 2; // Return type inferred as number
}

Generic Types

Write reusable code that works with multiple types:

function identity<T>(value: T): T {
return value;
}

let num = identity<number>(42);
let str = identity<string>("hello");

// Type argument often inferred
let inferred = identity(42); // T inferred as number

Generic Interfaces

interface Container<T> {
value: T;
getValue(): T;
}

let numberContainer: Container<number> = {
value: 42,
getValue: function() { return this.value; }
};

Type Checking Errors

The compiler reports type mismatches at compile time:

let x: number = "hello";  
// Error: Type 'string' is not assignable to type 'number'

function greet(name: string): string {
return 42;
// Error: Type 'number' is not assignable to return type 'string'
}

let user: { name: string } = { age: 25 };
// Error: Property 'name' is missing

Self-Hosted Type Checker

The Script compiler is self-hosted, meaning the type checker itself is written in Script (compiler/passes/types.tscl). This provides several benefits:

  • Dogfooding: The type system is validated by being used to build the compiler itself
  • Single language: No context switching between Rust and Script
  • Fast iteration: Changes to type checking don't require Rust recompilation

The Rust VM is used during development for running the self-hosted compiler, while production builds target native code via the LLVM backend.

Comparison with TypeScript

FeatureScriptTypeScript
Structural typingYesYes
Type inferenceYesYes
GenericsYesYes
Union typesYesYes
Intersection typesPlannedYes
Conditional typesNoYes
Runtime type infoNo (erased)No (erased)
Compilation targetNative (LLVM)JavaScript

Script's type system is intentionally simpler than TypeScript's, focusing on the most commonly used features while enabling native code compilation.