Created
October 6, 2021 19:29
-
-
Save itsjohncs/d1dce0f5b31849ca71dbcd3be803b747 to your computer and use it in GitHub Desktop.
An attempt at simulating an Exact data type in TS using existing tools.
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
// If all of Candidate's properties also exist in Model, this will be | |
// equivalent to Model. However, if Candidate has extra properties, they will | |
// be retyped to never and then merged into the resulting type. | |
// | |
// >>> ExclusivelyModel<{}, {a: number}> | |
// ... {a: number} | |
// >>> ExclusivelyModel<{a: number, b: number}, {a: number}> | |
// ... {a: number, b: never} | |
// >>> ExclusivelyModel<{a: string, b: number}, {a: number}> | |
// ... {a: number, b: never} | |
// | |
type ExclusivelyModel<Candidate, Model> = { | |
[Property in keyof Required<Candidate> | keyof Model]: | |
Property extends keyof Model ? Model[Property] : never; | |
} | |
// Recursive version, barely tested, use at your peril | |
type ExclusivelyModelDeep<Candidate, Model> = { | |
[Property in keyof Required<Candidate> | keyof Model]: | |
Property extends keyof Model ? ( | |
Property extends keyof Required<Candidate> ? ( | |
Model[Property] extends object ? | |
ExclusivelyModelDeep<Required<Candidate>[Property], Model[Property]> | |
: Model[Property]) | |
: Model[Property]) | |
: never | |
} | |
interface Foo { | |
a: number; | |
} | |
function mustBeExactlyFoo<T>(foo: ExclusivelyModel<T, Foo>): Foo { | |
return foo; | |
} | |
// This works how you'd want | |
const notExactlyFoo = {a: 1, b: 2}; | |
// @ts-expect-error | |
console.log(mustBeExactlyFoo(notExactlyFoo)); | |
// Ultimately its very easy to fool though. You just need to lose some | |
// information about the type. And since "exact" semantics are only in | |
// play when your explicitly using ExclusivelyModel... You can just do: | |
const alsoNotExactlyFoo: Foo = notExactlyFoo; | |
console.log(mustBeExactlyFoo(alsoNotExactlyFoo)); // :( |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment