Created
May 19, 2020 05:49
-
-
Save svieira/3574c151cd26c452657663f4247c0d63 to your computer and use it in GitHub Desktop.
The best nominal typing solution as of TS 3.9.x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export declare const nominalTag: unique symbol | |
type UniqueTypes = string | number | symbol | boolean | |
/** | |
* Needs no validation nominal types | |
* (allows the base type T to be uplifted without a cast) | |
*/ | |
export type Unique<T, M extends UniqueTypes> = T & { [nominalTag]?: M } | |
/** | |
* Validated nominal type | |
* (requires the base type T to be uplifted with a cast, ideally after validation) | |
*/ | |
export type Validated<T, M extends UniqueTypes> = T & { [nominalTag]: M } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Usage | |
// Declared enums used here, but any literal type would work (strings, numbers, unique symbols) | |
declare enum IdType { } | |
declare enum AccountBalanceType { } | |
type Id = Unique<number, IdType> | |
type AccountBalance = Unique<number, AccountBalanceType> | |
let id: Id = 123456789; // Raw values of type T are assignable to the Unique subtype | |
let balance: AccountBalance = 789; | |
id = balance; // Different types are not equivalent | |
// ERROR: Type 'Unique<number, AccountBalanceType>' is not assignable to type 'Unique<number, IdType>' | |
// Translations between Unique and Validated | |
type ValidatedId = Validated<number, IdType> | |
let validId: ValidatedId = 123 // Raw values of type T not assignable to validated | |
// ERROR: Type '123' is not assignable to type 'Validated<number, IdType>'. | |
validId = id; // Unique not assignable to Validated | |
// ERROR: Type 'Unique<number, IdType>' is not assignable to type 'Validated<number, IdType>' | |
validId = 12345 as ValidatedId // Casts "validate" as expected | |
id = validId // And validated types are exchangable with their "unique" subtypes as expected | |
// validId. // Nominal tag does not show up in the dotted auto-complete | |
// Use private fields for class nominality instead of Unique (doesn't work at all) | |
// or Validated(works but requires boilerplate) | |
class Point { | |
private [nominalTag]: never; | |
constructor(public x: number, public y: number){} | |
} | |
class Foo { | |
private [nominalTag]: never; | |
constructor(public x: number, public y: number){} | |
} | |
var x: Point = new Point(1, 2); | |
var y: Foo = new Foo(1, 2); | |
x = y; | |
// ERROR: Types have separate declarations of a private property '[nominalTag]' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment