JavaScript Type System Syntax Comparison

December 11, 2023 · View on GitHub

The table included below compares the syntax used for various type system constructs and other applications of types such as type annotation sites.

FeatureTypeScript syntaxFlow syntaxHegel syntaxClosure compiler (in comments)Notes
ArraysT[]Array<T>Flow also supports T[] but wants to deprecate it
Tuple type[S, T, ...]Same as TS
Annotating function parameterfunction f(x: S) {}Same as TSSame as TS@param {S} x
Annotating return valuefunction f() : S {}Same as TSSame as TS@return {S}
Annotating rest parametersfunction f(...x : S[])function f(...x : Array<S>)Same as Flow@param {...S} xFlow also accepts the TS annotation.
Variable annotationlet x:S = foo;Same as TSSame as TS@type {S}
Function type(x: S, ...) => T(S, ...) => TSame as Flowfunction(S, T, ...): UIn TS, parameter names are required and types can be omitted (defaults to any). In Flow, they are optional.
Function type with rest parameter(...x : S[]) => T(...x : Array<S>) => TSame as Flowfunction(S, ...T): U
Annotating a function's this parameterfunction f(this: {key: T})Same as TS@this {S}
Generic functionfunction f<T>(x : type) {}Same as TSSame as TS@template X, Y, ...
Generic function type<X>(x: S, ...) => TSame as TSSame as TS
Generic type applicationf<S>(arg)Same as TSSame as TS@type {!F<S>}This syntax can’t be easily supported in JS parsers, an alternative is funName::<type>().
Generic type application without callf<S>N/AN/AAdded in TS 4.7. Generic functions or other values can be instantiated without a call-site. As above, this can’t be supported as-is in JS.
Bounded polymorphism<S extends T><S: T>Same as FlowN/A
Dotted/variadic polymorphismtype F<T extends any[]> = (x: [S, ...T]) => [...T];Same as TSN/AN/AAdded in TS 4.0.
Union typeS | ... | TSame as TSSame as TSSame as TS
Maybe / option typeN/A?TSame as Flow?T or T=In Flow, ?T allows null and undefined. Hegel only allows undefined. Closure compiler defines nullable types (?) and optional types (=).
Intersection typetype & typeSame as TSN/AN/A
Top typeunknownmixedSame as TS*
Unsafe / dyn typeanySame as TSN/A?
Function top typeFunction(...$ReadOnlyArray<empty>) => mixedSame as TS
Bottom typeneveremptyN/AN/AAdded in TS 2.0, previously null/undefined acted like bottom
Undefined typeundefinedvoidSame as TSSame as TS
Null typenullSame as TSSame as TSSame as TS
Inexact object type{ x: S; y: T; }{x: S, y: T, ...}{x:S, ...}Same as FlowFlow prefers , over ; but both are accepted as field separators. Hegel has “hard” object types by default, so literal ellipses are needed for “soft” semantics. Closure compiler allows bare keys, which have any type.
Exact object typeN/A{x: S, y: T}{x: S, y: T}TS doesn’t have exact object types, but you can encode them https://fettblog.eu/typescript-match-the-exact-object-shape/ Flow plans to deprecate the {||} syntax at some point in the future (~1 year), as exact object types are the default now
Object type spreadtype T = {...S}
Object top typeobjectinterface {}ObjectObject
Interfacesinterface I { x: S; }Same as TSN/A (type aliases used instead)@interfaceIn Hegel, type aliases can be used in an implements clause
Interface getter/setterinterface I { get p(): number; set p(x : S); }Same as TS (setter return type required by Flow)N/A
Index signature{ [key: S] : T }Flow does not require key name, {[S]: T} is valid as wellN/AN/AThis syntax is supported in Hegel but appears to be buggy. Index signatures are also allowed in TS inside of class bodies, optionally with a static specifier.
Class interface declarationclass Foo implements Bar { ... }Same as TSSame as TS@implements {ClassName}Hegel implements from type aliases
Override declarationclass C extends D { override m() { … } }N/AN/A@overrideAdded in TS 4.3.
Abstract classabstract class C { abstract m() : S; }N/AN/A@abstract
Optional property{ key?: T; }Same as TS{ key : ?T }N/A
Call signature{ (x: S, ...): T; ... }{ (S ...): T, ... }N/AHegel parses the Flow syntax but it appears to be buggy.
Constructor signature{ new (x: S): T; } or new (x: S) => TN/AN/AFlow will understand the first TS example type, but interpret it as a property “new” with a function type.
Cast<T>expr or expr as TSame as TSN/A/** @type {!T} */ (expr)For Flow see v0.219.0 release notes
Assertion (cast without change of type)expr satisfies TN/AN/AAdded in TS 4.9. Flow parses for error reporting purposes.
Non-null assertionexpr!N/AN/A
Literal value type assertion“literal” as constN/AN/AAdded in TS 3.4. Flow parses for error reporting purposes.
Interface declarationinterface extends Parent, ... {}Same as TSN/A
Inline interface?type T = interface { }
Type aliastype A = TSame as TSSame as TS@typedef { ... }
Type alias genericstype F<X> = SSame as TSSame as TSN/A
Generic parameter variancetype F<in out X> = Stype F<+T> = ...; or type F<-T> = ...;N/AN/AAdded in TS 4.7. Flow parses for error reporting purposes.
Type alias with default generic parametertype F<X = S> = TSame as TSSame as TSN/AType application differs between TS and Flow. TS would use F while Flow would use F<> for the default argument
Module type exportexport type Foo = type0Same as TSSame as TSN/ATS allows type exports in ordinary exports, but as of 3.8 allows Flow’s type export syntax.
Module type importimport type { Foo } from "./m.js"Same as TSSame as TSN/ASee note above.
Inline type importimport { type Foo } from "./m.js"Same as TSN/AN/A
Type reflectiontype T = typeof exprSame as TStype T = $TypeOf<expr>;{typeof expr}
Typeof importN/Aimport typeof foo from "./m.js"N/AN/A
Key type operatorkeyof type$Keys<type>Same as FlowN/A
Value type operatorN/A$Values<type>Same as FlowN/A
Readonly property{readonly x: T}{+key: T}N/AN/AIn Flow, + is considered a variance annotation.
Readonly class propertyclass A {readonly foo: string}class A {+foo: string}
Writeonly propertyN/A{-key: T}
Readonly arrayreadonly T[]$ReadOnlyArray<T>
Utility types in generalReadonly<type>$ReadOnly<type>$Soft<T>N/AThese are just examples of a large category of utility type constructors. Flow is moving away from prefixing non-expiremental utility types with $
Indexed access typetype T = S[“key”]Same as TS$PropertyType<S, “key”>N/A
Optional indexed access typeN/Atype T = S?.["key"]
Mapped typestype T<S> = { [Property in keyof S]: U; };Same as TSN/AN/A
Conditional typestype S = T extends U ? V : W;Same as TSN/AN/A
Opaque typesN/Aopaque type T = ...
Type predicatesfunction p(x): x is T {...}Same as TS
Function overloadsfunction a(x: S): S; function a(x: T): T;Like TS but requires declare before type only definitions