Type system
TypeScript's type surface reinterpreted for whole-program AOT compilation, so the divergences are about what a static native target can and can't model. Advanced type-level machinery — mapped/utility/conditional types, generics, intersections, typeof queries — is evaluated at compile time via monomorphization, then erased, and assertions (`as T`, `as const`, `satisfies`) and modifiers (`readonly`) are likewise erased rather than enforcing. The primitives are JS-faithful (`number` is an IEEE-754 double; `bigint` is arbitrary-precision), with the extended integer/float JSDoc types as the escape hatch for real fixed-width integer semantics.
number → double
Works, with caveatsA JS-faithful IEEE-754 double: 0.1 + 0.2 is 0.30000000000000004, 10 / 3 is 3.3333333333333335, 1 / 0 is Infinity. The explicit JSDoc integer types are the opt-in escape hatch when real integer semantics matter.
console.log(0.1 + 0.2) // 0.30000000000000004
console.log(10 / 3) // 3.3333333333333335
console.log(2 ** -1) // 0.5- A plain
numberpast 2^53 loses precision exactly as JS does (9007199254740993is…992) — inherent to the double model, not a defect (useint64/uint64for exact 64-bit integers) - Mixing an explicit integer type with a bare literal promotes to double (
int32 / 2is3.5); use two integer-typed operands for integer division
string → ptr
ExactThe string type, lowered to a pointer at the IR level.
boolean → i1
ExactThe boolean type, lowered to a single-bit i1.
void
ExactThe absence of a return value on a function.
null / undefined
ExactThe null and undefined values, represented as a sentinel null pointer.
JSDoc extended integers
ExactA JSDoc @type override that gives a binding real fixed-width integer semantics: int8..int64 and uint8..uint64, with integer division, exact values past 2^53, and bit-width storage.
JSDoc extended floats
ExactA JSDoc @type override selecting float32 or float64. Float-to-string is JS-faithful: the shortest decimal that round-trips, per ECMAScript Number::toString, so 1.1 + 2.2 prints 3.3000000000000003 and integer-valued floats drop the point. A float32 prints against its widened double.
any
Works, with caveatsA runtime-tagged value supporting declare/assign/reassign/print/typeof/=== plus bare any params and returns on every function shape. Objects and arrays box by reference — === is reference identity, typeof is "object".
let x: any = 5
x = "hello"
x = true
console.log(typeof x) // boolean- Arithmetic on an
anyis a clean compile error - Nested positions
any[]/{ x: any }are clean compile errors - A boxed array keeps only its data pointer — length/elements aren't recoverable, so it stringifies to the
[object Array]tag, not its contents
unknown
Works, with caveatsThe type-safe counterpart to any, with the same Staged V1 scope: declare/assign/reassign/print/typeof/=== plus bare params and returns.
- Same as
any(see above)
never
ExactThe bottom type. A (): never function that always throws works correctly.
symbol
Works, with caveatsOpaque unique values — Symbol()/Symbol("desc") (no new, Symbol is not a constructor), ===/!==, typeof, .description, .toString(), and the Symbol.for/Symbol.keyFor registry (keyFor gives undefined for a symbol not in it). Uniqueness comes from the value's own heap-pointer identity, so two Symbol() calls with the same description are never equal.
const a = Symbol("id")
const b = Symbol("id")
console.log(a === b) // false
console.log(Symbol.keyFor(Symbol.for("app"))) // app- Of the well-known symbols the runtime honors
Symbol.iterator,Symbol.asyncIteratorandSymbol.toPrimitive(own properties, class and object-literal members);toStringTag,hasInstance,speciesand the rest are values only.
bigint
Works, with caveatsArbitrary-precision integers over a backend-agnostic ABI, selectable via -bigint=libtommath|gmp (default libtommath, public domain). Literals, the full arithmetic/bitwise operator set, same-type and cross-type-with-integer comparisons, typeof, ++/--, .toString([radix]), BigInt.asIntN/asUintN, Number(bigint), params/returns/fields/arrays, and BigInt(int|string) all work. Division/modulo by zero throws a catchable Error.
const a = 10n
console.log(a + 20n) // 30n
console.log(BigInt.asIntN(8, 255n)) // -1n
console.log(Number(a)) // 10- No
.toLocaleString()(anIntl-shaped gap)
T | null (nullable)
ExactA single-branch T | null uses a presence-flagged nullable-scalar slot ({ i1, T }), so a genuine null is distinguishable from a legitimately-present 0 everywhere — ??, === null, printing, JSON — across locals, params, returns, fields, and Maps. A multi-branch A | B | null is handled by the union box instead.
let present: number | null = 0
let absent: number | null = null
console.log(present ?? 42) // 0
console.log(absent ?? 42) // 42Object types (interfaces / inline {})
ExactInterfaces, inline {} shapes, and type aliases. A literal's fields are coerced against the declared type wherever one is known — variable annotation, function parameter/return/default, array element type, or nested field — not just the literal's own self-inferred type.
interface Point {
x: number;
y: number;
}
type Rect = { width: number; height: number }Array types T[]
ExactHomogeneous arrays, stored as a { ptr, i64 } aggregate.
Promise<T>
ExactThe Promise<T> type for async values.
Function types (a: T) => R
ExactFunction types, represented as a closure struct { funcPtr, envPtr }.
Map<K,V>
ExactThe Map<K,V> collection, with separate helpers per key/value combination (<string,number>, <string,string>, etc.).
Set<T>
ExactThe Set<T> collection.
Union types beyond T | null
Works, with caveatsThe same runtime box as any/unknown with a checked member set on top, statically restricted to the declared members at every assignment/call/return boundary. A narrowed local unboxes to the concrete member; an object member to its typed pointer. Usable for a variable, parameter, return value, object field and array element ((number | string)[], and (number | undefined)[] whose absent elements are real undefineds); every write is checked against the member set.
let x: string | number = "hello"
console.log(typeof x) // string
x = 42
console.log(typeof x) // number- Scalar members (
number/string/boolean+null/undefined), object members (classes, plain object types, one headerless host object such asURL, or a discriminated union), and aReadableStreammember; two array members of one kind, number-literal tags, and a non-first-position tag aren't supported - Flow narrowing is for a union **local** via
typeof/truthiness/==null/instanceof/in/a tag (if/else branches + early return); a field/element,switch (typeof x), andascasts aren't narrowed; before narrowing an object union reads only a member all its object members declare
Intersection types
Works, with caveatsAn object-type intersection collapses at resolveType into one merged ObjectType — member fields concatenated, compatible duplicates deduped — with no runtime tag, unlike a union. & binds tighter than |. Works as a named interface, inline {}, or type alias member, and in variable/param/return/field/array-element positions.
interface HasName { name: string }
interface HasAge { age: number }
const p: HasName & HasAge = { name: "Kyriakos", age: 34 }- Object-type members only — a non-object member (scalar/function/array/tuple/union, all
neverin TS), or a generic-with-type-arguments member, is a clean compile error - A field declared with conflicting **non-object** types across members is rejected under
-compat=strict(default); the-compat=jsTS-faithfulnever-field is not yet implemented, so a conflict is currently rejected in both modes (same-named **object** fields are recursively intersected instead —{ x: A } & { x: B }⇒x: A & B) - Inherits object types' missing-field leniency: a partial object literal for an
A & B-typed target is not rejected (a pre-existing object-literal behavior, not intersection-specific)
Tuple types
Works, with caveatsA fixed-arity, heterogeneous, positional value stored as a fixed-shape struct. Supports labelled elements ([min: number, max: number]), the tuple literal, constant-index read and assignment, .length (compile-time constant), destructuring (including nested and for-of), and array-shaped rendering. Map/Array/Object.entries() return real [K, V] tuples.
const point: [number, number] = [3, 4]
console.log(point[0]) // 3
const [name, value] = point- No rest/optional elements
- Constant index only — no non-constant index; compound assignment to an element (
t[0] += x) is a clean rejection - No array methods (
.map/.filter/…) on a tuple (.lengthworks) - Not supported nested in
anyor a union
Mapped & utility types
Works, with caveatsPartial/Required/Readonly/NonNullable/Pick/Omit/Record, the general mapped form { [K in keyof T]: V }, keyof, indexed access T[K], and string-literal types — all evaluated at compile time to concrete object types via the monomorphization seam, no runtime representation.
type Clone = { [K in keyof User]: User[K] }Partial/Required/Readonlyare structural no-ops — the object model already zero-fills omitted fields and does no mutation checking; the visibly-effective utilities arePick/Omit/Record- Mapped value body handles
T[K](homomorphic), a bareK, a concrete type (uniform), orT[K]wrapped one level inArray/Promise/Set; aT[K]nested deeper isn't substituted - No key remapping (
as) or modifier-removal (-?/-readonly);?/readonlyare accepted but near-no-ops - String-literal types resolve to
string— the literal value is not narrowed/enforced - An unrecognized
Name<Args>(or a malformedPick/Omit) is not yet a clean rejection
Conditional types (T extends U ? X : Y)
Works, with caveatsEvaluated at compile time off the monomorphized (concrete) check type — generic type aliases, infer capture, and a structural assignability predicate. Nested conditionals are right-associative.
type ElementOf<T> = T extends Array<infer E> ? E : T
const one: ElementOf<number[]> = 7
const raw: ElementOf<string> = "hi"infersupported forArray<infer E>/Promise<infer V>and a bareinfer R; function-signature inference ((...) => infer R) and deeperinfernesting are deferredassignableis structural width subtyping (objects), element-wise (arrays), IR-equality with numeric leniency (scalars) — not full variance
Generics on user functions/interfaces/classes
Works, with caveatsV1 (default) is monomorphization — one specialized implementation per distinct combination of concrete types actually used. Covers function identity<T>, interface Box<T>, class Box<T>, and multiple type parameters <K, V>. <T extends X> constraints are parsed and enforced (structural width subtyping). V2 /** @erased */ compiles a generic function's body once, with no name mangling.
function identity<T>(x: T): T {
return x;
}
console.log(identity(42)); // 42
console.log(identity("hello")); // hello- A generic function needs an inferable
T/T[]-typed parameter for each of its type parameters - Map/Set/Promise/closure/dynamic type arguments are a clean rejection (scalars, arrays, and object/interface/class types are supported)
- V2
@erasedcovers functions only (not interfaces/classes) and bareTpositions only —T[]orTnested in an object field is a clean compile error - Arithmetic on an erased
Thits the same "operator on any/unknown" rejection as plainany
Index signatures ({ [k: string]: T })
Works, with caveatsA string index signature types a dictionary of arbitrary string keys, backed by a map.
interface Scores {
[player: string]: number;
}
const scores: Scores = { alice: 10, bob: 7 };
scores["carol"] = 12;- String or number key; the dictionary is the object itself, so a static object or an any value converted to it is the same object and writes show on both —
d[key]read/write,in,delete, an object-literal initializer, andObject.keys/values/entries+for…ofall work; a missing key reads undefined - Named properties beside the signature (
{ id: number; [k: string]: number }) keep their declared types JSON.stringifyof an index-signature dict is compact output only (thespaceargument is not applied)- The class-body form (
class C { [n: number]: T }) is dropped
typeof type queries (type T = typeof x)
ExactDerives a type from an existing value without restating its shape, usable as a type alias, an inline annotation, or a parameter/return type.
const config = { host: "localhost", port: 8080 };
type Config = typeof config;
const staging: Config = { host: "staging", port: 9090 };- Resolves the referenced value's type in the query's own scope, as tsc does (a
constgives its literal type; an unknown name is TS2304), walking atypeof a.bmember path;typeof Cof a class is the class itself, usable as a value
Type assertions (x as T, as const, satisfies T)
Works, with caveatsParsed as a left-associative postfix on any expression; as const and satisfies T are likewise identity.
- **Erased, not enforcing** — the assertion is accepted and dropped: the value is unchanged and keeps its *own* inferred type, so
as Tdoes not re-type the expression (a sound cross-representation reinterpret isn't modeled). This matches TS's runtime erasure but not its static narrowing/widening; a cast the code *relies on* for typing won't take effect. One carve-out:as Tdirectly onJSON.parse(...)/res.json()supplies the projection target under-compat=strict, exactly as a declaration annotation would.
Template literal types ( a-${T} )
Works, with caveatsTemplate literal types parse in a type position (no-substitution, multi-substitution, and []-suffixed forms) and resolve to `string` — the literal pattern isn't narrowed or enforced, the same simplification string-literal types use.
- Erased to
string— the literal pattern is not narrowed/enforced.
readonly T[] array-type modifier
Works, with caveatsThe readonly array-type modifier parses in variable/parameter/return/field positions.
- **Erased, not enforcing** —
readonly number[]andreadonly [T, U]parse (in variable/parameter/return/field positions), but the modifier is dropped and mutation is not prevented, the same stance asReadonly<T>and the mapped-typereadonlymodifier. TheReadonlyArray<T>alias form is not covered
Ambient declarations (declare const/function/class/module/namespace)
Works, with caveatsdeclare ambient declarations describe types the environment provides; carrying no implementation, they are parsed and erased.
declare const BUILD_ID: string;
declare function nativeHook(x: number): number;
declare module "vendor-lib" {
export function doThing(): void;
}- Parsed and erased — the no-body/no-init forms and the brace-bodied
global/module/namespace/classblocks all compile away. Ambient declarations describe externally-provided values; under whole-program AOT they have no target to bind to, so a runtime *use* of a declared binding this compiler doesn't itself provide is an ordinaryundefined variableerror
TypeScript type errors
Works, with caveatsThe default strict lane rejects a program TypeScript rejects for these type errors, reporting tsc's code, message and position. -compat=js compiles the same program as JavaScript.
let n: number = "x"
// klainmain: type error: main.ts: 1:1: type 'string' is not assignable to type 'number'
// hint: TypeScript rejects this program; -compat=js compiles it as JavaScript- Reported so far: assignability, arguments and argument counts, arithmetic operands, comparisons with no overlap, missing and excess properties, calls no overload matches, possibly-null, possibly-undefined and unknown operands, and stores to constants, functions, classes and enums; a missing name and a read of a partly assigned variable are not
- Calls to generic functions and to builtins are not checked yet
- A function type in a message names its parameters p0, p1, … instead of their declared names