Skip to main content

Opaque Type Aliases

Opaque type aliases are type aliases that hide their underlying type outside of the file in which they are defined.

1opaque type ID = string;

When to use this

Use opaque types over regular type aliases when you need to enforce abstraction boundaries across module boundaries — for example, preventing callers from treating an ID as a plain string. Use the optional supertype constraint when consumers need partial access (e.g. reading an ID as a string but not creating one from a string).

Opaque Type Alias Syntax

Opaque type aliases are created using the words opaque type followed by its name, an equals sign =, and a type definition.

opaque type Alias = Type;

You can optionally add a subtyping constraint to an opaque type alias by adding a colon : and a type after the name.

opaque type Alias: SuperType = Type;

Any type can appear as the super type or type of an opaque type alias.

1opaque type StringAlias = string;2opaque type ObjectAlias = {3  property: string,4  method(): number,5};6opaque type UnionAlias = 1 | 2 | 3;7opaque type AliasAlias: ObjectAlias = ObjectAlias;8opaque type VeryOpaque: AliasAlias = ObjectAlias;

Opaque Type Alias Type Checking

Within the Defining File

When in the same file the alias is defined, opaque type aliases behave exactly as regular type aliases do.

1opaque type NumberAlias = number;2
30 as NumberAlias;4
5function add(x: NumberAlias, y: NumberAlias): NumberAlias {6    return x + y;7}8function toNumberAlias(x: number): NumberAlias { return x; }9function toNumber(x: NumberAlias): number { return x; }

Outside the Defining File

When importing an opaque type alias, it behaves like a nominal type, hiding its underlying type.

exports.js

export opaque type NumberAlias = number;

imports.js

1// In imports.js, NumberAlias is opaque — its underlying type (number) is hidden2declare opaque type NumberAlias;3
40 as NumberAlias; // Error: 0 is not a NumberAlias!incompatible-typeCannot cast 0 to NumberAlias because number [1] is incompatible with NumberAlias [2].5
6function convert(x: NumberAlias): number {7  return x; // Error: x is not a number!incompatible-typeCannot return x because NumberAlias [1] is incompatible with number [2].8}

Subtyping Constraints

When you add a subtyping constraint to an opaque type alias, we allow the opaque type to be used as the super type when outside of the defining file.

exports.js

1export opaque type ID: string = string;

imports.js

1// In imports.js, ID is opaque with a supertype constraint of string2declare opaque type ID: string;3
4function formatID(x: ID): string {5    return "ID: " + x; // OK! IDs are strings.6}7
8function toID(x: string): ID {9    return x; // Error: strings are not IDs.incompatible-typeCannot return x because string [1] is incompatible with ID [2].10}

When you create an opaque type alias with a subtyping constraint, the type in the type position must be a subtype of the type in the super type position.

1opaque type Bad: string = number; // Error: number is not a subtype of stringincompatible-typenumber [1] is incompatible with string [2].2opaque type Good: {x: string, ...} = {x: string, y: number};

Generics

Opaque type aliases can also have their own generics, and they work exactly as generics do in regular type aliases

1opaque type MyObject<A, B, C>: {foo: A, bar: B, ...} = {2  foo: A,3  bar: B,4  baz: C,5};6
7const val: MyObject<number, boolean, string> = {8  foo: 1,9  bar: true,10  baz: 'three',11};

Library Definitions

You can also declare opaque type aliases in libdefs. There, you omit the underlying type, but may still optionally include a super type.

1declare opaque type Foo;2declare opaque type PositiveNumber: number;

See Also