Skip to content

Conditional types are evaluated too eagerly #34810

Closed

Description

TypeScript Version: 3.7.x-dev.201xxxxx

Search Terms: conditional type eager, conditional type narrowing

Code

type FooHelper<X> = (arg1: X, arg2: X extends 0 ? number : string) => void;
type Foo = FooHelper<0 | 1>;

let x: number;
const foo: Foo = (t, d) => {
    if(t === 0) { x = d; }
}

Expected behavior:
No errors, t has type 0 | 1 in the function body which gets narrowed to just 0 in the body of the if statement. In the if statement, d should have type number, because we know that t has to have type 0.

Actual behavior:
Type 'string | number' is not assignable to type 'string'.

Playground Link: https://www.typescriptlang.org/play/?ts=3.7-Beta#code/C4TwDgpgBAYg9nAEhANpATgHgBoD4oC8UAFAIboDmAjAFxTYA0U5FATHdlBAB7AQB2AEwDOUAAxQA-FGHB0AS34UodfgFcAtgCMI6AJSF8ANzjzBAbgBQoSLASE7SVBkwSAPlCq4rllBGBQ3HSyCkpWAMZw-LJQAGYIdPBwDsTATIIGBPgA3pZQ+VDysamEBERiBtmBDhZQAL6WdUA

Related Issues: not really

Activity

  1. jvanbruegge commented on Oct 29, 2019

    @jvanbruegge
    Author

    I also tried to define Foo differently:

    type Foo = ((arg1: 0, arg2: number) => void) & ((arg1: 1, arg2: string) => void);

    But that infers t and d both as any

  2. RyanCavanaugh commented on Oct 29, 2019

    @RyanCavanaugh
    Member

    Foo isn't a generic type, so the conditional has already been evaluated. Conditional types only stay unresolved when they're in a generic type.

  3. jvanbruegge commented on Oct 29, 2019

    @jvanbruegge
    Author

    So is it somehow possible to get the wanted behavior?

  4. RyanCavanaugh commented on Oct 29, 2019

    @RyanCavanaugh
    Member

    Not really, no. You'd need #33014 at an absolute minimum - there's currently no mechanism that would allow for the kind of narrowing implied here.

  5. jvanbruegge commented on Oct 29, 2019

    @jvanbruegge
    Author

    Sad. Ok, thank you

  6. rubenpieters commented on Nov 2, 2019

    @rubenpieters

    If I understand correctly, your intention is kind of like the following: the parameter arg2 is dependent on the type of arg1. Actually, it is dependent on the value of arg1, but that is inexpressible.

    let x: number;
    
    type F = { 0: number, 1: string }
    
    function foo<X extends 0 | 1>(arg1: X, arg2: F[X]): void {
      if (arg1 === 0) {
        x = arg2;
      }
    }

    Unfortunately, just #33014 won't solve your problem (it is similar to the rejected case a here). Since the following can happen:

    foo(0 as 0 | 1, "oops");

    Meaning: the value of arg1 can be 0, while the type variable X can be instantiated with 0 | 1, which means that arg2 can actually hold a string. As a result the logic of narrowing X to 0 based on the check arg1 === 0 is unsound.

    We would additionally require something like #30284 to solve this.

  7. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions