Last active
August 3, 2018 18:29
-
-
Save donabrams/74075e89d10db446005abe7b1e7d9481 to your computer and use it in GitHub Desktop.
Nominal typing in TypeScript
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
interface FooId extends String { | |
_fooIdBrand: string; | |
}; | |
export interface Foo { | |
id: FooId; | |
color: 'yellow'; | |
isAwesome: true; | |
} | |
interface BarId extends String { | |
_barIdBrand: string; | |
}; | |
export interface Bar { | |
id: BarId; | |
color: 'blue'; | |
isAwesome: false; | |
} | |
type BeepId = FooId | BarId; | |
type Beep = Foo | Bar; | |
type GetId<T> = T extends { id: infer U } ? U : never; | |
function lookupById<T extends Beep>(ex: Beep[], id: GetId<T>): T | null { | |
return ex.find(({ id }) => id === id) as T | null; | |
} | |
interface PositiveTestResult { | |
foo: Foo | null; | |
bar: Bar | null; | |
beep: Beep | null; | |
} | |
function positiveTest(ex: Beep[]): PositiveTestResult { | |
const idFoo: FooId = "1" as any; | |
const idBar: BarId = "2" as any; | |
const idBeep: BeepId = "3" as any; | |
return { | |
foo: lookupById(ex, idFoo), | |
bar: lookupById(ex, idBar), | |
beep: lookupById(ex, idBeep), | |
}; | |
} | |
interface NegativeTestResult { | |
foo: Foo | null; | |
bar: Bar | null; | |
foo2: Foo | null; | |
} | |
function negativeTest1(ex: Beep[]): NegativeTestResult { | |
const idFoo: FooId = "1" as any; | |
const idBar: BarId = "2" as any; | |
const idBeep: BeepId = "3" as any; | |
return { | |
foo: lookupById(ex, idFoo), | |
bar: lookupById(ex, idBar), | |
// This correctly fails to typecheck | |
foo2: lookupById(ex, idBeep), | |
}; | |
} | |
function negativeTest2(ex: Beep[]): NegativeTestResult { | |
const idFoo: FooId = "1" as any; | |
const idBar: BarId = "2" as any; | |
const idBeep: BeepId = "3" as any; | |
const foo2 = lookupById(ex, idBeep); | |
return { | |
foo: lookupById(ex, idFoo), | |
bar: lookupById(ex, idBar), | |
// This correctly fails to typecheck | |
foo2, | |
}; | |
} | |
function negativeTest3(ex: Beep[]): NegativeTestResult { | |
const idFoo: FooId = "1" as any; | |
const idBar: BarId = "2" as any; | |
const idBeep: BeepId = "3" as any; | |
// This correctly fails to typecheck | |
const foo2: Foo | null = lookupById(ex, idBeep); | |
return { | |
foo: lookupById(ex, idFoo), | |
bar: lookupById(ex, idBar), | |
foo2, | |
}; | |
} | |
function negativeTest4(ex: Beep[]): NegativeTestResult { | |
const idFoo: FooId = "1" as any; | |
const idBar: BarId = "2" as any; | |
const idBeep: BeepId = "3" as any; | |
return { | |
foo: <Foo | null>lookupById(ex, idFoo), | |
bar: <Bar | null>lookupById(ex, idBar), | |
// This correctly fails to typecheck | |
foo2: <Beep | null>lookupById(ex, idBeep), | |
}; | |
} | |
const testId: BeepId = "3" as any; | |
const test = lookupById([] as Beep[], testId); | |
function test2(a: Foo | null): string { | |
return a ? a.color : "clear"; | |
} | |
// This correctly fails to typecheck | |
test2(test); | |
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
// How to force nominal types in Typescript (as opposed to duck types) | |
// From: https://github.com/Microsoft/TypeScript/issues/202#issuecomment-302402671 | |
declare class NominalType<S extends string> { | |
private DO_NOT_DEFINE_ME_HUMAN: S; | |
} | |
type Email = string & NominalType<'Email'>; | |
type CustomerId = number & NominalType<'CustomerId'>; | |
const email = "yay"; | |
function sendEmail(email: Email) { | |
console.log(email); | |
} | |
function cleanEmail(userInput: string): Email { | |
// TODO: clean up email | |
return userInput as Email; | |
} | |
sendEmail(email); | |
sendEmail(cleanEmail(email)); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment