Last active
February 5, 2021 17:55
-
-
Save asktree/8ab244583d4163f09a5a15ba5f60c7a3 to your computer and use it in GitHub Desktop.
TypeScript's type assignment feature destroys information (inconsistently)
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
// Typescript lets you put constraints on assignment -- cool | |
// - I don't know what this feature is officially called and can't find it. | |
// for example, telling the IDE that a constant should be "even" | |
type even = 2 | 4 | 6; | |
const a = 2; | |
const b: even = 2; | |
const c: even = 4; | |
const d: even = 3; // error: constraint violation | |
// you can assert types, this is different! | |
const d2 = 3 as even; // NO error: true assertion | |
// Unlike assertion, assignment *can* preserve information | |
// (but don't get excited) | |
type prime = 2 | 3 | 5; | |
const x: prime = a; | |
const y: prime = b; // does not error, assignment constraint did not destroy information | |
const z: prime = c; // does error, appropriate | |
const b2 = 2 as even; | |
const y2: prime = b2; // does error, assertions do destroy information | |
// ## Now let's try with readonly objects | |
const A = { n: 2 } as const; | |
const X: prime = A.n; // does not error, TS knows `A.n === 2` because it's readonly | |
const C: { n: even } = { n: 3 } as const; // errors correctly -- the constraint is not an assertion | |
const B: { n: even } = { n: 2 } as const; | |
const Y: prime = B.n; | |
// expected: no error, `B.n` is prime and we didn't assert. | |
// ❗️ ❗️ ❗️ Actual: does error, assignment constraint DID destroy information!!! | |
// # ❔ How can I tell the compiler that I want something to be of type `{n: even}` without destroying information? | |
const D = { n: 3 } as const; | |
// ## ✅ the lame way (contains vestigial runtime stuff) | |
const check = <T>(x: T) => {}; | |
check<{ n: even }>(A); // no error | |
check<{ n: even }>(D); // error, 3 is not even | |
// ## ✅ the lamer way (more vestigial runtime stuff) | |
const checkedA: { n: even } = A; // no error | |
const checkedC: { n: even } = D; // error | |
// ## ❌ this has no runtime crap but also doesn't work (`CheckC = never` but still compiles) | |
type CheckA = typeof A & { n: even }; | |
type CheckC = typeof D & { n: even }; | |
// ## ✅ this works and even looks like Type-Driven Development!... | |
declare let g: even; | |
g = 3; // error: woo is not 3 | |
// ❌ ... but doesn't work with const | |
// It seems like my approaches that involve runtime code are the only ways to do it. | |
// I really dislike that assignment constraints can destroy information. | |
// The difference between assignment constraints and assertions should be consistent across values. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Discussion in: microsoft/TypeScript#42656