Description
TypeScript Version: 2.6.0-dev.20170824 (not in 2.4 or 2.5)
(Moving from a comment to a new issue)
Now that the fix for #15756 is in (#18042), I tried implementing the type function Transpose
, which reverses a mapping of string literals. Example:
// which kinds of ice cream does each person like
type IceCreamPreferences = {
'alice': 'vanilla' | 'chocolate' | 'strawberry';
'bob': 'chocolate';
'carol': 'strawberry' | 'rumRaisin';
'dave': 'rumRaisin' | 'chocolate';
'eve': 'tripleFudgeRipple';
}
// which people like each kind of ice cream
type TransposedIceCreamPreferences = {
'vanilla': 'alice';
'chocolate': 'alice' | 'bob' | 'dave';
'strawberry': 'alice' | 'carol';
'rumRaisin': 'carol' | 'dave';
'tripleFudgeRipple': 'eve';
}
Here is a version of Transpose
that still doesn't work:
// union of possible value types
type ValueOf<T> = T[keyof T];
// subtract unions of string literals
type Diff<T extends string, U extends string> = (
{[K in T]: K} &
{[K in U]: never} &
{ [K: string]: never }
)[T];
type Transpose<T extends Record<string, string>> = ValueOf<{
[P in keyof T]: Record<Diff<ValueOf<T>, T[P]>, never> & Record<T[P], P>
}> // broken!
type WhoLikes = Transpose<IceCreamPreferences>;
var chocolateLover: WhoLikes['chocolate'];
chocolateLover = 'alice'; // okay
chocolateLover = 'bob'; // okay
chocolateLover = 'carol'; // 🙁 should error, but doesn't!
chocolateLover = 'dave'; // okay
chocolateLover = 'eve'; // 🙁 should error, but doesn't!
Expected behavior:
WhoLikes['chocolate']
should be 'alice' | 'bob' | 'dave'
.
Actual behavior:
WhoLikes['chocolate']
is 'alice' | 'bob' | 'carol' | 'dave' | 'eve'
. Something is still doing an eager substitution where I don't expect it.
I'm not sure if this is the minimal example of this issue; seems only to show up with a certain amount or level of nesting of mapped type definitions and applications. I think this is still a bug but I'm not sure. Thoughts?
Note that the default-generic workaround does work here:
type Transpose<T extends Record<string, string>, X = {
[P in keyof T]: Record<Diff<ValueOf<T>, T[P]>, never> & Record<T[P], P>
}> = ValueOf<X> // works
type WhoLikes = Transpose<IceCreamPreferences>;
var chocolateLover: WhoLikes['chocolate'];
chocolateLover = 'alice'; // okay
chocolateLover = 'bob'; // okay
chocolateLover = 'carol'; // 🙂 error, carol doesn't like chocolate
chocolateLover = 'dave'; // okay
chocolateLover = 'eve'; // 🙂 error, eve doesn't like chocolate
so I do have a working Transpose
in 2.6.0-dev.20170824, but I'm not sure if the workaround is necessary.