Skip to content

Language Specification

This document is the technical specification for SuperJS (SJS). It defines the type system semantics, syntax forms, compilation pipeline, and diagnostic codes. It is authoritative over the language reference for matters of precision.


1. Design Philosophy

SJS is designed around four convictions:

Sound type system. Every type error caught at compile time is a guarantee. SJS does not include escape hatches that silently undermine soundness (no any, no ! assertion). The dynamic type is the only opt-out, and it is explicit and runtime-checked.

Go-inspired simplicity. The type system has a fixed, small surface area. There are exactly 10 types. Object types are structural and satisfied implicitly. There are no mapped types, conditional types, or infer — features that make TypeScript's type system Turing-complete but also opaque.

Dart 2.12-style null safety. Non-nullable by default. T? is the only way to express nullability. The compiler tracks null flow through ?., ??, and narrowing. There is no non-null assertion operator.

Rust-inspired sum types. Variant types are a first-class construct, not a convention over discriminated union objects. match is an expression with compiler-enforced exhaustiveness.


2. The 10 Types

SJS has exactly 10 built-in types. The set is closed — new built-in types cannot be added by user code.

SJS TypeDescriptionRuntime representation
numberIEEE 754 double-precision floatJS number
stringUTF-16 stringJS string
booleantrue or falseJS boolean
bigintArbitrary-precision integerJS bigint
symbolUnique opaque symbolJS symbol
voidAbsence of a return valueJS undefined
nullExplicit nullJS null
neverUnreachable / bottom type— (no value reaches this)
dynamicRuntime-checked escape hatchJS value, checked at use sites
object THeap-allocated typed objectJS object

any does not exist in SJS. Using any in an .sjs file is a parse error.


3. Null Safety Semantics

3.1 Non-nullable by default

Every type T is non-nullable unless explicitly declared T?. This includes all 10 built-in types and all user-defined classes and object types.

const x: string = null        // SJS-E001
const y: string = undefined   // SJS-E001
const z: string? = null       // OK

3.2 Nullable types

T? desugars to T | null | undefined in the type algebra, but SJS surfaces it only as T?. The distinction matters: T | null is not valid SJS syntax — write T? instead.

3.3 Null-safe operators

?. (optional chaining) and ?? (nullish coalescing) are both type-checked. The operand on the left of ?. must be T?; the result is U? where U is the property type.

const len: number? = user?.length   // user must be string?
const name: string = user ?? "Anon" // result is string (non-nullable)

3.4 Narrowing

The compiler tracks null flow through if/else and typeof guards. After a null check, the type is narrowed to the non-nullable variant.

const user: string? = findUser(id)

if (user !== null) {
  console.log(user.toUpperCase())  // user: string here
}

3.5 No non-null assertion

! is not a postfix type operator in SJS. There is no way to tell the compiler "trust me, this is not null" without an actual runtime check. This is intentional — ! is a common source of null pointer exceptions in TypeScript codebases.


4. Sum Type Syntax and Runtime Representation

4.1 Declaration syntax

type Result<T, E> = Ok(T) | Err(E)
type Shape = Circle({ radius: number }) | Rect({ w: number, h: number })
type Option<T> = Some(T) | None

Each variant is either:

  • A unit variant: None (no payload)
  • A tuple variant: Ok(T) (single positional payload)
  • A record variant: Circle({ radius: number }) (named payload fields)

4.2 Constructor functions

Each variant name is a constructor function at runtime:

const r: Result<number, string> = Ok(42)
const e: Result<number, string> = Err("bad input")
const s: Shape = Circle({ radius: 5 })
const n: Option<string> = None

4.3 Runtime representation

The compiler emits discriminated union objects. The _tag field holds the variant name as a string literal. The payload is placed in _0 (tuple variants) or spread into the object (record variants):

SJS expressionEmitted JS object
Ok(42){ _tag: "Ok", _0: 42 }
Err("bad"){ _tag: "Err", _0: "bad" }
Circle({ radius: 5 }){ _tag: "Circle", radius: 5 }
None{ _tag: "None" }

SJS code never references _tag or _0 directly. These are internal to the compilation target. Use match to destructure.


5. Match Expression Semantics and Exhaustiveness

5.1 Syntax

match is an expression, not a statement. It always produces a value.

const result = match expr {
  Variant1(x)        => expression1,
  Variant2({ a, b }) => expression2,
  default            => expressionDefault,
}

5.2 Compilation target

Match expressions compile to IIFE switch statements on ._tag:

// SJS:
const msg = match r { Ok(val) => `Got ${val}`, Err(e) => `Failed: ${e}` }

// Compiled JS:
const msg = (() => {
  switch (r._tag) {
    case "Ok":  { const val = r._0; return `Got ${val}`; }
    case "Err": { const e = r._0;   return `Failed: ${e}`; }
  }
})()

5.3 Exhaustiveness

When the matched expression has a sum type, the compiler verifies that every variant is covered. If any variant is missing and there is no default branch, SJS-E007 is emitted at compile time.

type Color = Red | Green | Blue

const label = match color {
  Red   => "red",
  Green => "green",
  // SJS-E007: match is not exhaustive — missing variant: Blue
}

Adding default suppresses the check:

const label = match color {
  Red     => "red",
  default => "other",
}

5.4 Destructuring in arms

Tuple payload: Ok(val) binds val to r._0. Record payload: Circle({ radius }) destructures the record fields from the variant object. Unit variants: None matches when _tag === "None" with no binding.


6. Structural Object Types

6.1 Definition

Object types use the brace form of type (no =):

type Printable {
  toString(): string
}

6.2 Implicit satisfaction

A value of type C satisfies object type I if and only if C exposes every member declared in I with a compatible type. No implements declaration is required or supported.

class Celsius {
  constructor(public value: number) {}
  toString(): string { return `${this.value}°C` }
}

function print(p: Printable): void {
  console.log(p.toString())
}

print(new Celsius(100))  // OK — Celsius satisfies Printable structurally

6.3 Object type extension

Object types may extend one or more other object types. The extending type inherits all member requirements.

type Serializable extends Printable {
  serialize(): string
}

6.4 No intersection types

A & B is not valid SJS syntax. Use object type extension to compose contracts:

// Wrong (banned):
type Named = HasName & HasAge

// Correct:
type Named extends HasName, HasAge {}

7. Generics

7.1 Syntax

Generic type parameters use angle brackets on functions, classes, and object types:

function identity<T>(x: T): T { return x }

class Stack<T> {
  private items: T[] = []
  push(item: T): void { this.items.push(item) }
  pop(): T? { return this.items.pop() ?? null }
}

type Container<T> {
  get(): T?
  set(value: T): void
}

7.2 Constraints

Use : TypeName to constrain a type parameter:

function max<T: Comparable>(a: T, b: T): T {
  return a.compareTo(b) > 0 ? a : b
}

The constraint is checked structurally — T must satisfy the Comparable object type.

7.3 Monomorphization

SJS generics are monomorphized at compile time, not type-erased. Each instantiation of a generic at a distinct type produces a distinct specialization. This means:

  • Generic code has no runtime type-erasure cost.
  • Type parameters are not available at runtime (no T.name, no instanceof T).
  • The compiled output is larger than a type-erased equivalent for many distinct instantiations.

7.4 Banned generic features

The following TypeScript generic features are not in SJS:

  • Conditional types: T extends U ? A : B
  • infer keyword
  • Mapped types: { [K in keyof T]: ... }
  • Template literal types: `prefix_${T}`

8. The dynamic Type

8.1 Purpose

dynamic is the opt-out from the static type system. It exists for interoperability with untyped external data: JSON responses, third-party libraries without type definitions, and runtime-constructed objects.

8.2 Semantics

  • A dynamic value may hold any JavaScript value at runtime.
  • Accessing a property or calling a method on dynamic succeeds at compile time but is checked at runtime.
  • dynamic does not propagate silently. Assigning a dynamic to a statically typed variable requires a runtime narrowing check.
  • In strict mode, positions that would implicitly receive dynamic emit SJS-W001.
function parseJSON(raw: string): dynamic {
  return JSON.parse(raw)
}

const data: dynamic = parseJSON('{"count": 3}')
const count = data.count   // dynamic — runtime-checked

// To use as a typed value, narrow explicitly:
if (typeof count === "number") {
  const n: number = count  // OK
}

8.3 Difference from any

any in TypeScript is unsound — it silently opts out of type checking for all downstream expressions. dynamic in SJS is explicitly runtime-checked: the compiler inserts guards at use sites and the type does not widen surrounding expressions.


9. Compilation Pipeline

SJS compiles .sjs.js through a hand-written pipeline — no Babel and no TypeScript at runtime — in five ordered phases:

Phase 1: Lex

The source file is read as UTF-8 and tokenized by a hand-written lexer (numbers in all bases, templates with nested interpolation, regex-vs-division disambiguation, BiDi-control rejection).

Phase 2: Parse

A recursive-descent parser with a Pratt expression layer produces an AST that is a superset of the JavaScript AST, including SJS-specific nodes (sum type declarations, match expressions, type annotations). The parser recovers from errors so a single mistake does not abort the whole file.

Phase 3: Type check

A bidirectional type checker (synth/check) runs over the AST and emits diagnostics:

  • SJS-E001 / SJS-E003 — null/undefined assigned to a non-nullable type; access on a possibly-null value
  • SJS-E002 — type mismatch on assignment or return
  • SJS-E007 — non-exhaustive match on a sum type
  • SJS-W001 — implicit dynamic (strict mode only)

Type errors do not block emission by default. Set compilerOptions.noEmitOnError to halt compilation when any error is present.

Phase 4: Lower to SJS-IR

The typed AST is lowered to SJS-IR (an ESTree-subset JavaScript AST). All type syntax is erased and SJS constructs are desugared:

  • Sum type constructors → tagged objects ({ _tag, _0 })
  • Match expressions → an invoked arrow (IIFE) that switches on _tag and binds payloads
  • JSXReact.createElement (or the configured runtime); class parameter propertiesthis.x = x

Phase 5: Codegen & emit

A precedence-aware printer renders the IR to JavaScript at the configured ES target, generating a Source Map v3 alongside. The compiler writes one .js (and .js.map, when source maps are enabled) per input file, preserving directory structure under outDir.


10. Diagnostic Code Reference

All SJS diagnostics have stable, permanent codes. Codes are never reused after retirement.

Error codes (SJS-E)

CodeNameDescription
SJS-E001Null safety violationA value of type null, undefined, or T? was assigned to a non-nullable binding.
SJS-E002Type mismatchThe type of an expression is not compatible with the declared type at an assignment, return site, or call argument position.
SJS-E007Non-exhaustive matchA match expression on a sum type is missing one or more variants and has no default arm.

SJS-E001 example:

const name: string = null
// error[SJS-E001]: cannot assign null to non-nullable type 'string'
//  --> app.sjs:1:22
// hint: use 'string?' to allow null, or assign a non-null value

SJS-E002 example:

function double(n: number): number {
  return "oops"
}
// error[SJS-E002]: expected return type 'number', found 'string'
//  --> app.sjs:2:10

SJS-E007 example:

type Color = Red | Green | Blue
const label = match color {
  Red   => "red",
  Green => "green",
}
// error[SJS-E007]: match is not exhaustive — missing variant: Blue
//  --> app.sjs:2:15
// hint: add an arm for 'Blue', or add a 'default' arm

Warning codes (SJS-W)

CodeNameActivated byDescription
SJS-W001Implicit dynamicstrict modeA variable or parameter has no type annotation and would implicitly receive type dynamic.

SJS-W001 example (with compilerOptions.strict: true):

function add(a, b) { return a + b }
// warning[SJS-W001]: parameter 'a' has implicit type 'dynamic'
// warning[SJS-W001]: parameter 'b' has implicit type 'dynamic'

Diagnostic output format

Default (human-readable):

error[SJS-E001]: cannot assign null to non-nullable type 'string'
 --> src/app.sjs:3:22

JSON mode (--json flag, one object per line):

{"code":"SJS-E001","severity":"error","message":"cannot assign null to non-nullable type 'string'","file":"src/app.sjs","line":3,"column":22}

11. Permanently Banned Features

The following features are not part of SJS and will not be added. They are excluded by design, not omission.

FeatureReason for exclusion
anyUnsound. Use dynamic — it is explicit and runtime-checked.
T extends U ? A : B (conditional types)Makes the type system Turing-complete; produces inscrutable error messages. Use sum types and match instead.
{ [K in keyof T]: ... } (mapped types)Produces types that are correct by construction but hard to read and diagnose. Use explicit object types.
Template literal typesExpressive but adds significant type checker complexity for marginal practical benefit.
inferTied to conditional types; removed along with them.
namespaceSuperseded by ES modules.
TypeScript enumEnums have confusing runtime semantics. Use sum types — they are explicit, exhaustively matchable, and compile cleanly.
A & B (intersection types)Intersection of two object types is rarely what the author intends and is unsound in several positions. Use object type extension.
! (non-null assertion)Allows bypassing null safety without a runtime check. Use narrowing — it is both safe and readable.

12. File Format

  • Extension: .sjs
  • Encoding: UTF-8
  • Line endings: LF preferred
  • Comments: Standard JavaScript // and /* */
  • Shebang: Supported (#!/usr/bin/env superjs)
  • JSX: Enabled in all .sjs files — no opt-in required