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

Narrowing a union from generic fails when union is not the first generic #23017

Closed
luke-john opened this issue Mar 30, 2018 · 2 comments
Closed

Comments

@luke-john
Copy link

luke-john commented Mar 30, 2018

TypeScript Version:

  • 2.9.0-dev.20180330
  • 2.8.1
  • 2.7.2

Search Terms:

  • generic order
  • map index generic

Code

type Tags  = { foo: ''; bar: ''; baz: ''; }
type Tag = keyof Tags 

type GetTag<UsedTag extends keyof Tags> = keyof { [tag in UsedTag]: any } 

type DoThingVersionOne =
  <UsedTag extends Tag = Tag, Props = {}>(tag: UsedTag) => GetTag<UsedTag>

const doThingVersionOne: DoThingVersionOne = {} as any

const fooOne = doThingVersionOne('foo')                       // expected | type = "foo"
const fooOneITag = doThingVersionOne<'foo'>('foo')            // expected | type = "foo"
const fooBarITagIProps = doThingVersionOne<'foo', {}>('foo')  // expected | type = "foo"

type DoThingVersionTwo =
    <Props = {}, UsedTag extends Tag = Tag>(tag: UsedTag) => GetTag<UsedTag>

const doThingVersionTwo: DoThingVersionTwo = {} as any

const fooTwo = doThingVersionTwo('foo')                       // expected | type = "foo"
const fooTwoITag = doThingVersionTwo<{}>('foo')               // unexpected | type "foo" | "bar" | "baz" 
const fooTwoITagIProps = doThingVersionTwo<{}, 'foo'>('foo')  // expected | type "foo" 

Expected behavior:

The order of a generic does not effect it's behaviour when narrowing from a union.

Actual behavior:

The order of a generic does effect it's behaviour when narrowing from a union.

As described below when no generics are passed typescript infers all of them, when any number of generics are passed typescript partially instantiates all of them using the defaults provided.

Playground Link:

Playground Reproduction

Related Issues:
#17713
#21631
#12424

@luke-john luke-john changed the title Narrowing a union from generic fails based on when union is not the first generic Narrowing a union from generic fails when union is not the first generic Mar 30, 2018
@jack-williams
Copy link
Collaborator

jack-williams commented Mar 30, 2018

The order matters because of the default values you have and in the unexpected case you're partially instantiating the generics. The following two are equivalent and give the same result.

const fooTwoITagWithDefault = doThingVersionTwo<{}>('foo') //  "foo" | "bar" | "baz" 
const fooTwoITag = doThingVersionTwo<{}, Tag>('foo') //  "foo" | "bar" | "baz" 

I believe partial application of generics turns off inference for those that are omitted (they fall back to their default value). I guess the issue you're saying is that it should try and infer missing default type parameters?

@luke-john
Copy link
Author

Ahh, right. I was thinking when providing generics, only the ones passed would be instantiated.

Happy to close this, thanks for your quick response and explanation.

@microsoft microsoft locked and limited conversation to collaborators Jul 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants