Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested mapped types still evaluated eagerly in some cases, causing surprising/incorrect behavior #18089

Closed
jcalz opened this issue Aug 28, 2017 · 2 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@jcalz
Copy link
Contributor

jcalz commented Aug 28, 2017

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.

@ahejlsberg
Copy link
Member

I'm seeing the expected errors with the current build in master:

// 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>
}>;

type WhoLikes = Transpose<IceCreamPreferences>;
var chocolateLover: WhoLikes['chocolate'];  // 'alice' | 'bob' | 'dave'
chocolateLover = 'alice';
chocolateLover = 'bob';
chocolateLover = 'carol'; // Error
chocolateLover = 'dave';
chocolateLover = 'eve'; // Error

Are you sure you're testing with the right build?

@ahejlsberg ahejlsberg added the Working as Intended The behavior described is the intended behavior; this is not a bug label Aug 28, 2017
@jcalz
Copy link
Contributor Author

jcalz commented Aug 29, 2017

Okay, I apparently tested only halfway through the relevant commits. This is fixed in 2.6.0-dev.20170826. Thanks for your attention. Closing.

@jcalz jcalz closed this as completed Aug 29, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

2 participants