Type System
SuperJS extends JavaScript with a sound, gradual type system. Types are non-nullable by default, sum types replace discriminated unions, and match replaces switch for exhaustive dispatch.
What's banned
These TypeScript features do not exist in SJS:
| Feature | Reason |
|---|---|
any | Silent unsafety — use dynamic or unknown instead |
A & B intersection types | Unsound merging — compose object types with extends |
T extends U ? X : Y conditional types | Complexity without soundness gain |
Mapped types ({ [P in keyof T]: ... }) | Implicit metaprogramming — spell out the shape |
T! non-null assertion | Defeats null safety — narrow explicitly |
enum | Use type union literals instead |
Null safety
All types are non-nullable by default. Append ? to opt into nullability.
let name: string = "Alice" // cannot be null
let nickname: string? = null // OK
function greet(user: string?): string {
if (user === null) return "Hello, stranger"
return `Hello, ${user}`
}Nullable types must be narrowed before use. SJS emits SJS-E001 on unsafe access.
Primitive types
let s: string = "hello"
let n: number = 42
let b: boolean = true
let sym: symbol = Symbol("key")
let big: bigint = 123nObject types
Object types describe structural shapes. They use the brace form of type (no =). SJS uses structural typing — any value that satisfies the shape is compatible.
type User {
id: number
name: string
email: string
age?: number // optional property
readonly createdAt: Date
}
type AdminUser extends User {
role: string
permissions: string[]
}Index signatures use unknown, not any:
type Registry {
[key: string]: unknown
}Sum types
Sum types replace discriminated unions. Each variant is a constructor that carries typed payload.
type Shape =
| Circle(radius: number)
| Rect(width: number, height: number)
| Point
type Result<T, E> =
| Ok(value: T)
| Err(error: E)Constructing and consuming:
const s: Shape = Circle(5)
const area = match s {
Circle(r) => Math.PI * r * r,
Rect(w, h) => w * h,
Point => 0,
}Match expressions
match is exhaustive — SJS emits SJS-E008 if a variant arm is missing.
type ApiResult = Ok(data: string) | NotFound | ServerError(code: number)
function handle(r: ApiResult): string {
return match r {
Ok(data) => data,
NotFound => "404",
ServerError(code) => `Error ${code}`,
}
}Use _ for a catch-all arm:
return match r {
Ok(data) => data,
_ => "failed",
}Generics
Standard generic syntax. Constrain with extends:
function first<T>(arr: T[]): T? {
return arr.length > 0 ? arr[0] : null
}
type Repository<T> {
find(id: number): T?
save(item: T): void
findAll(): T[]
}
class Stack<T> {
private items: T[] = []
push(item: T): void { this.items.push(item) }
pop(): T? { return this.items.pop() ?? null }
get size(): number { return this.items.length }
}Generic constraints:
type Measurable {
length: number
}
function longest<T extends Measurable>(a: T, b: T): T {
return a.length >= b.length ? a : b
}The dynamic escape hatch
dynamic opts a value out of type checking. Use only at JS interop boundaries — never inside pure SJS code.
// wrapping an untyped third-party library
const raw: dynamic = require('some-legacy-lib').getData()
// narrow before use
if (typeof raw === 'string') {
console.log(raw.toUpperCase())
}dynamic propagates: operations on a dynamic value return dynamic. Narrow explicitly with typeof, instanceof, or a sum-type guard before using the value in typed code.
Type inference
SJS infers types for variable initializers, function return types, and array/object literals:
const message = "hello" // inferred: string
const items = [1, 2, 3] // inferred: number[]
const user = { name: "Alice" } // inferred: { name: string }
function double(n: number) {
return n * 2 // inferred return: number
}Structural typing
SJS is structurally typed. A value satisfies an object type if it has the required shape — no implements needed at call sites.
type Point { x: number; y: number }
function distance(a: Point, b: Point): number {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)
}
// Any object with x and y satisfies Point
distance({ x: 0, y: 0 }, { x: 3, y: 4 }) // OKNarrowing
Narrow nullable and unknown values with standard JS checks. SJS flow-analyzes these:
function process(val: string | number): string {
if (typeof val === "string") {
return val.toUpperCase() // val: string here
}
return val.toString() // val: number here
}
function display(name: string?): string {
if (name === null) return "anonymous"
return name // name: string here
}Error codes
| Code | Condition |
|---|---|
SJS-E001 | Nullable access without null check |
SJS-E002 | Type mismatch on assignment or return |
SJS-E003 | Missing required property |
SJS-E004 | Use of banned syntax (any, A & B, etc.) |
SJS-E008 | Non-exhaustive match (missing variant arm) |
SJS-W001 | Implicit dynamic propagation |