question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Type narrowing of generic types in control flow analysis

See original GitHub issue

Bug Report

Hello,

🔎 Search Terms

type narrowing, generic types, control flow analysis

🕗 Version & Regression Information

(see Playground) When trying to narrow generic types (unions), the control flow analysis is not aware of the narrowing. I also see that this PR: https://github.com/microsoft/TypeScript/pull/43183 was supposed to address this.

Please keep and fill in the line that best applies:

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

⏯ Playground Link

Playground link with relevant code

💻 Code

const INITIAL_STATE = {
  1: 'test1',
  2: 'test2',
};
type State = typeof INITIAL_STATE;

const stateReducer = <Type extends keyof State>(
  state: State,
  action: { type: Type; value: string } | { type: 'clear'; value: number },
) => {
  if (action.type === 'clear') {
    // action.value is "string | number", but should be "number"
    return action.value;
  }

  return {
    ...state,
    [action.type]: action.value,
  };
};

stateReducer(INITIAL_STATE, { type: 'clear', value: 0 });

🙁 Actual behavior

action.value is "string | number"

🙂 Expected behavior

but should be “number”

Thanks in advance!

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

3reactions
whzx5bybcommented, Sep 6, 2022

{ type: Type; value: string } | { type: 'clear'; value: number } is not a discriminated union so it may not be narrowed as the way you expected. To make it work, you have to distribute over the generic type Type.

const stateReducer = <Type extends keyof State>(
  state: State,
-  action: { type: Type; value: string } | { type: 'clear'; value: number },
+  action: Type extends unknown ? { type: Type; value: string } | { type: 'clear'; value: number } : never,
) => {
1reaction
fatcerberuscommented, Sep 7, 2022

It’s not that every constituent in the union needs a unit type

It’s funny you say this because this was also my understanding for a long time. It’s only due to past comments by @RyanCavanaugh that I found out the rule is the discriminant can either be a unit type or a union of unit types (thus why keyof State works as a discriminant). So I certainly wouldn’t blame anyone who didn’t realize that.

It’s interesting that that conditional type trick works. I would expect it to just defer since it’s distributive over a type parameter.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Narrowing - TypeScript
This analysis of code based on reachability is called control flow analysis, and TypeScript uses this flow analysis to narrow types as it...
Read more >
TypeScript - narrowing type from generic union type
Currently the compiler does not use control flow analysis to narrow generic type parameters or values of types dependent on generic type ...
Read more >
TypeScript Generics and Type Guards - Explained by Example
Type guards are powerful for narrowing types, satisfying the TypeScript compiler's control flow process and guaranteeing runtime type-safety ...
Read more >
توییتر \ Nick Nisi در توییتر: «TypeScript question: Is it possible to ...
testA using union type properly narrows the ternary. ... Improve narrowing of generic types in control flow analysis by ahejlsberg · Pull Request...
Read more >
Generic Types | Flow
In Flow, most of the time when you pass one type into another you lose the original type. So that when you pass...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found