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

Generic lookup on mapped types cannot resolve type #29528

Closed
lingz opened this issue Jan 22, 2019 · 4 comments
Closed

Generic lookup on mapped types cannot resolve type #29528

lingz opened this issue Jan 22, 2019 · 4 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@lingz
Copy link

lingz commented Jan 22, 2019

TypeScript Version: 3.2.0

Search Terms: Mapped Types, Generics

Code

type letters = 'a' | 'b'

const identityMapping: {
  [letter in letters]: letter
} = {
  'a': 'a',
  'b': 'b'
};

function getIdentity<T extends letters>(t: T): T {
  return identityMapping[t];
}

Expected behavior:
Compiles without type errors

Actual behavior:

[ts]
Type '{ a: "a"; b: "b"; }[T]' is not assignable to type 'T'.
  Type 'letters' is not assignable to type 'T'.
    Type '"a"' is not assignable to type 'T'. [2322]

Playground Link:
http://www.typescriptlang.org/play/#src=type%20letters%20%3D%20'a'%20%7C%20'b'%0D%0A%0D%0Aconst%20identityMapping%3A%20%7B%0D%0A%20%20%5Bletter%20in%20letters%5D%3A%20letter%0D%0A%7D%20%3D%20%7B%0D%0A%20%20'a'%3A%20'a'%2C%0D%0A%20%20'b'%3A%20'b'%0D%0A%7D%3B%0D%0A%0D%0Afunction%20getIdentity%3CT%20extends%20letters%3E(t%3A%20T)%3A%20T%20%7B%0D%0A%20%20return%20identityMapping%5Bt%5D%3B%0D%0A%7D%0D%0A

Related Issues:

@jack-williams
Copy link
Collaborator

jack-williams commented Jan 22, 2019

I'd be interested in your use-case here. It can also be written the following way, without errors:

function getIdentity<T extends letters>(t: T): typeof identityMapping[T] {
  return identityMapping[t];
}

And also another instance improved by #29049.

@lingz
Copy link
Author

lingz commented Jan 23, 2019

Hi @jack-williams

This is a common pattern we use, for instance to assign a render function to every member of an enum set:

type letters = 'a' | 'b'

const renderMapping: {
    [l in letters]: renderFunction<l>;
} = {
    'a': (a: 'a') => 'alpha',
    'b': (b: 'b') => 'bravo',
};
type renderFunction<l extends letters> = (letter: l) => string;

function renderLetter<l extends letters>(letter: l): renderFunction<l> {
  return renderMapping[letter];
}

I'm trying to understand if this is a bug, or a limitation of typescript by design. If it is a limitation, what is that limitation and what is the workaround?

You can see the type error generated in this playground:

http://www.typescriptlang.org/play/#src=type%20letters%20%3D%20'a'%20%7C%20'b'%0D%0A%0D%0Aconst%20renderMapping%3A%20%7B%0D%0A%20%20%20%20%5Bl%20in%20letters%5D%3A%20renderFunction%3Cl%3E%3B%0D%0A%7D%20%3D%20%7B%0D%0A%20%20%20%20'a'%3A%20(a%3A%20'a')%20%3D%3E%20'alpha'%2C%0D%0A%20%20%20%20'b'%3A%20(b%3A%20'b')%20%3D%3E%20'bravo'%2C%0D%0A%7D%3B%0D%0Atype%20renderFunction%3Cl%20extends%20letters%3E%20%3D%20(letter%3A%20l)%20%3D%3E%20string%3B%0D%0A%0D%0Afunction%20renderLetter%3Cl%20extends%20letters%3E(letter%3A%20l)%3A%20renderFunction%3Cl%3E%20%7B%0D%0A%20%20return%20renderMapping%5Bletter%5D%3B%0D%0A%7D%0D%0A

@jack-williams
Copy link
Collaborator

I'm trying to understand if this is a bug, or a limitation of typescript by design. If it is a limitation, what is that limitation and what is the workaround?

I would say that this is by design. Your are indexing into something concrete (with no generic parameters) and expecting to get back something generic; specifically, accessing into renderMapping (concrete) and trying to return something of type renderFunction<l> (generic). In general, you can only get generic things from other generic things (because type variables represent unknowns). I would say one workaround is to write the function like:

type letters = 'a' | 'b'

type RenderMap = { [l in letters]: RenderFunction<l>;}

const renderMapping: RenderMap = {
    'a': (a: 'a') => 'alpha',
    'b': (b: 'b') => 'bravo',
};

type RenderFunction<l extends letters> = (letter: l) => string;

function renderLetter<l extends letters>(letter: l): RenderMap[l] {
  return renderMapping[letter];
}

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Jan 23, 2019
@RyanCavanaugh
Copy link
Member

As usual I agree with @jack-williams. Basically TypeScript would need to be able to do second-order reasoning about generics (i.e. figuring out that the correlation within the mapped type was equivalent to the correlation in the function signature) in order to understand that the return expression actually did match the return type, but we're really only capable of first-order reasoning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants