You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Type narrowing/guard together with nullish coalescing/optional chaining #38136
switch (menuTypeFilter) {
case MenuType.EatIn: console.log(1); break
case MenuType.TakeAway: console.log(2); break
case null:
case undefined: console.log(3); break
default: unreachable(menuTypeFilter)
}
Narrowing works with nullish coalescing.
What isn't possible is to narrow one value and expect it to have an effect on another variable.
Your code is equivalent to:
declare const x: 'a' | null | undefined;
const y = x ?? null; // this is correctly inferred as 'a' | null
switch (y) {
case 'a': break;
case null: break;
default: unreachable(x); // y was indeed narrowed down to never, but not x
}
We can see that, in order to reach the default branch, y must've been something other than 'a' | null | undefined, which means x was too. But the compiler doesn't keep track of all these possible connections between different values.
enumMenuType{EatIn,TakeAway}declarefunctionunreachable(value: never): neverdeclareletfilter: {menuType: MenuType|null|undefined}|null|undefined// Works :)if(filter?.menuType!=null){switch(filter.menuType){caseMenuType.EatIn: console.log(1);breakcaseMenuType.TakeAway: console.log(2);breakdefault: unreachable(filter.menuType)}}// Doesn't work :(switch(filter?.menuType){caseMenuType.EatIn: console.log(1);breakcaseMenuType.TakeAway: console.log(2);breakcasenull: console.log(3);breakcaseundefined: console.log(3);breakdefault: unreachable(filter.menuType)}
Gonçalo Fernandes (@ilogico) I believe that this demonstrates a case where I have not essentially created a new variable? Especially since it works when doing if instead of switch
changed the title [-]Type narrowing/guard together with nullish coalescing[/-][+]Type narrowing/guard together with nullish coalescing/optional chaining[/+]on Apr 24, 2020
You're not creating a new variable, but you are creating a new value.
Your if statement works in narrowing filter to not null, but your inner switch works because you're using filter.menuType everywhere, but in the second case, you're using filter?.menuType in one place and filter.menuType in the other. Even though we can see the values are related, CFA doesn't track that connection. It would be great if it did, but it doesn't.
In any case, this has nothing to do with nullish coalescing or optional chaining. If replace one with menuTypeFilter || null and the other with filter && filter.menuType, you will get the same result.
Your if statement works in narrowing filter to not null, but your inner switch works because you're using filter.menuType everywhere, but in the second case, you're using filter?.menuType in one place and filter.menuType in the other.
Activity
MartinJohns commentedon Apr 23, 2020
A simple workaround is:
LinusU commentedon Apr 23, 2020
Hehe, I can't believe I didn't see that, thanks! 😄
Would still be great if type narrowing could work with nullish coalescing though☺️
ilogico commentedon Apr 23, 2020
Narrowing works with nullish coalescing.
What isn't possible is to narrow one value and expect it to have an effect on another variable.
Your code is equivalent to:
We can see that, in order to reach the default branch,
ymust've been something other than'a' | null | undefined, which meansxwas too. But the compiler doesn't keep track of all these possible connections between different values.LinusU commentedon Apr 24, 2020
It also doesn't work with optional chaining:
Gonçalo Fernandes (@ilogico) I believe that this demonstrates a case where I have not essentially created a new variable? Especially since it works when doing
ifinstead ofswitch[-]Type narrowing/guard together with nullish coalescing[/-][+]Type narrowing/guard together with nullish coalescing/optional chaining[/+]ilogico commentedon Apr 24, 2020
You're not creating a new variable, but you are creating a new value.
Your
ifstatement works in narrowingfilterto not null, but your innerswitchworks because you're usingfilter.menuTypeeverywhere, but in the second case, you're usingfilter?.menuTypein one place andfilter.menuTypein the other. Even though we can see the values are related, CFA doesn't track that connection. It would be great if it did, but it doesn't.In any case, this has nothing to do with nullish coalescing or optional chaining. If replace one with
menuTypeFilter || nulland the other withfilter && filter.menuType, you will get the same result.LinusU commentedon Apr 25, 2020
Ahhhhh, I see now, thanks for the explanation!