Advanced

These are the places where TypeScript's type system says the most and the runtime knows the least. The examples in this section show what the converter (fromTS, which turns TypeScript into TJS) keeps from each feature, and what is checked at run time afterwards.

One thing decides most of the answers. Converted code keeps JavaScript semantics, so by default it checks nothing: it runs as it would after tsc. A file that starts with /* @tjs TjsStrict */ opts in, and then the types the converter kept are checked when a function is called. A failed check does not throw: the call returns an error value (a MonadicError, which is an Error).

The four examples, in order: Generic Functions shows which type parameters are kept and which become any. Async Functions shows what an async function's parameters and resolved value are checked against, and why a failed check resolves instead of rejecting. Classes runs one class with and without TjsStrict: constructor and method parameters, private, and calling a class without new. The Full Picture is a small order handler that puts it all together.

What is checked after conversion

TypeScript As converted With TjsStrict To get the check
<T extends { id: number }> (constraint) not checked checked add TjsStrict
<T> (unconstrained) not checked not checked (any) nothing to check: TJS checks values, not relations
async function parameters not checked checked add TjsStrict
async function resolved value not checked not checked in native TJS, change :! to :?
class constructor and method parameters not checked checked add TjsStrict
parameter typed with an enum not checked not checked (any) in native TJS, write (s: Status)
discriminated union not checked not checked in native TJS, one Type per variant

"Native TJS" means a .tjs file you write or keep yourself, rather than code converted from TypeScript on the fly.

Generics

A generic type parameter describes a relationship between types that no single value can show, so the converter keeps what a value can show. A constraint becomes the example: byId<T extends { id: number }>(x: T) converts to byId(x: { id: 0.0 }). Under TjsStrict, byId({ id: 'no' }) then returns a type error instead of running. An unconstrained <T> becomes any, meaning nothing is checked at run time, and the converter writes a comment above each function where that happened, such as /* TODO: TS types degraded — value: T, return: T */. Search converted code for TS types degraded to find them all.

Async functions

Promise<T> describes a value that does not exist yet. The converter writes the resolved type as a type-only return annotation: async function load(n: number): Promise<string> becomes async function load(n: number):! string. :! states the type without checking it, so the resolved value is documented, not validated, with or without TjsStrict. The parameters are checked under TjsStrict, as any function's are.

Classes

Constructor and method parameters keep their types, and under TjsStrict they are checked when the constructor or method is called, as a function's are. TjsStrict changes two more things: TypeScript's private becomes a real #private field, and the class is called without new. Classes runs the same class both ways.

Known gaps after conversion

Two TypeScript features convert, but conversion does not yet produce a check for them, even under TjsStrict. The fix is to write the type in native TJS, not to turn on a mode.

Enums. Take two TypeScript enums, one string-valued and one numeric:

enum Status {
  Active = 'active',
  Off = 'off',
}

enum Dir {
  Up,
  Down,
}

function describe(s: Status): string {
  return 'got ' + s
}

Each enum converts to a TJS Enum, a runtime object that holds the members and can check whether a value is one of them (Status.check(x)). Members read as they do in TypeScript: Status.Active is 'active', and the numeric enum keeps its reverse lookup (Dir[1] is 'Down').

The parameter typed Status is not checked. The converter cannot yet use an enum as a parameter type, so it drops the type and warns Unknown type 'Status' converted to 'any' - may need manual review. The warning is printed by tjs convert and returned in fromTS(...).warnings, and the converted code carries a TODO: TS types degraded — s: Status comment above the function. describe('bogus') returns 'got bogus', with TjsStrict or without.

To get the check, either call Status.check(s) yourself in the TypeScript, or take the file to native TJS and write the parameter as function describe(s: Status). There a value that is not one of Status's values returns a type error. See Enums for the native form.

Discriminated unions. A type such as type Shape = { kind: 'circle'; r: number } | { kind: 'square'; side: number } converts to a Type (TJS's runtime type declaration) whose body is only the TypeScript, kept as a // TS: comment. It has no example and no predicate, so it accepts any value, and area({ kind: 'tri' }) reaches the function body. The native TJS form declares one Type per variant, with the discriminant made exact by Exactly('circle'), and a same-name function for each; a value that matches no variant then returns an error. See Discriminated unions.

For T | null and unions of string literals, see Union Types.