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

switch doesn't narrow object property for passing as function parameter #49153

Closed
awinograd opened this issue May 17, 2022 · 3 comments
Closed

Comments

@awinograd
Copy link

Bug Report

🔎 Search Terms

narrow switch, discriminate switch, refine switch

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about type discrimination & narrowing in switches.

crashes on 3.5.x and above. Before this I couldn't run the playground because as const wasn't available. However, similar behavior happens when using enums (will include 2nd playground)

⏯ Playground Link

Playground link with relevant code using as const
Playground link with relevant code using enum

💻 Code

export const Kind = {
  FOO: 'FOO',
  BAR: 'BAR',
} as const;
export type Kind = typeof Kind[keyof typeof Kind];

type Foo = { kind: typeof Kind.FOO };
type Bar = { kind: typeof Kind.BAR };

function extractFoo(obj: Foo): string {
    return 'foo';
}

function extractBar(obj: Bar): string {
    return 'bar';
}

function main() {
   const arr: Array<{ kind: Kind }> = [];

   const values = arr.map(item => {
       switch (item.kind) {
           case Kind.FOO: {
               const kind: typeof Kind['FOO'] = item.kind; // no error
               return extractFoo(item); // error
           }
           case Kind.BAR: {
               return extractBar(item); // error
           }
       }
   })
}

🙁 Actual behavior

Error on extractFoo & extractBar

Types of property 'kind' are incompatible.
    Type 'Kind' is not assignable to type '"FOO"'.
      Type '"BAR"' is not assignable to type '"FOO"'

🙂 Expected behavior

item should be a valid parameter to extractFoo/extractBar because the kind property should be narrowed/refined by the switch statement.

@jcalz
Copy link
Contributor

jcalz commented May 18, 2022

This is working as intended: item is not of a discriminated union type (it's not of a union type at all), so checking item.kind only narrows the type of item.kind itself; it does not narrow the type of item. See #31755. There is also an open feature request at #42384 asking for this sort of behavior.

@whzx5byb
Copy link

As @jcalz says, you must make your array item type a discriminated union: { kind: 'Foo' } | { kind: 'Bar' }, instead of { kind: 'Foo' | 'Bar' }.

A workaround could be:

export const Kind = {
  FOO: 'FOO',
  BAR: 'BAR',
} as const;
export type Kind = typeof Kind[keyof typeof Kind];

type Foo = { kind: typeof Kind.FOO };
type Bar = { kind: typeof Kind.BAR };

function extractFoo(obj: Foo): string {
    return 'foo';
}

function extractBar(obj: Bar): string {
    return 'bar';
}

function main() {
-   const arr: Array<{ kind: Kind }> = [];
+   const arr: Array<Kind extends infer U ? U extends unknown ? { kind: U } : never : never> = [];


   const values = arr.map(item => {
       switch (item.kind) {
           case Kind.FOO: {
               const kind: typeof Kind['FOO'] = item.kind; // no error
               return extractFoo(item); // no error!
           }
           case Kind.BAR: {
               return extractBar(item); // no error!
           }
       }
   })
}

@awinograd
Copy link
Author

awinograd commented May 18, 2022

Thanks to you both for pointing me in the right direction. I guess I need to work on my "GH issue"-foo. 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants