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.

Conditional types are incorrectly narrowed

See original GitHub issue

TypeScript Version: 3.1.6, 3.2.1, the current playground (3.3?), and next as of Feb 28

Search Terms: conditional types are incorrectly

Code

interface A { foo(); }
interface B { bar(); }

function test1<T extends A>(x: T, y: T extends B ? number : string) {
    if (typeof y == 'string') {
        y;
    } else {
        y; // never ?
    }
    const newY: string | number = y;
    newY;  // just string
}

function test2<T extends A>(x: T, y: T extends B ? string : number) {
    if (typeof y == 'string') {
        y; // never ?
    } else {
        y; 
    }
    const newY: string | number = y;
    newY;  // just number 
}

Expected behavior: T extends B ? string : number should either be left unchanged, or rounded up to string|number: I think the issue stems from incorrect inference that T extends B is false given T extends A (while they’re just unrelated interfaces that have a non-empty intersection). The test case below is as far as I’ve managed to reduce the problem.

Actual behavior: The T extends A constraint seems to make TS guess T extends B is always false, and so the a?b:c type behaves as c.

Playground Link: (playground)

Related Issues: https://github.com/Microsoft/TypeScript/issues/29939 looks slightly similar, but I don’t see the same constraints when playing around with my example, so I’m not sure it’s the same.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:6
  • Comments:17 (6 by maintainers)

github_iconTop GitHub Comments

2reactions
remojansencommented, Apr 11, 2019

We are having an issue at work that could be related:

type Primitive = number | string | boolean | null | undefined | Symbol | Function;

export interface ImmutableMap<T> {
    // ...
    toJS(): T;
}

export interface ImmutableList<T extends Array<any>> {
    // ...
    toJS(): T;
}

export type ImmutableFromJS<T> = T extends Primitive ? T
    : T extends Array<any> ? ImmutableList<T>
    : T extends object ? ImmutableMap<T>
    : never;

type Sometype<T> = ImmutableFromJS<{ [P in keyof T]: { [id: string]: T[P]; }; }>;

declare let a: Sometype<object>;
a.toJS(); // OK

declare let b: Sometype<number[]>;
a.toJS(); // OK

declare let c: Sometype<Primitive>;
c!.toJS(); // Error (Not expected)

function test<
    T1,
    T2 extends object,
    T3 extends number[],
    T4 extends Primitive
>(
    arg1: Sometype<T1>,
    arg2: Sometype<T2>,
    arg3: Sometype<T3>,
    arg4: Sometype<T4>
) {
    arg1.toJS(); // Error (Not expected)
    arg2.toJS(); // Error (Not expected)
    arg3.toJS(); // Error (Not expected)
    arg4.toJS(); // Error (Not expected)
}

It seems like when using generics the conditional type is not narrowed down as I would expect.

ImmutableFromJS<{ [P in keyof T]: { [id: string]: T[P]; }; }>

Should be mapped to ImmutableMap<T> always, independently of the type of T?

Please correct me If I’m not understanding this correctly.

1reaction
jack-williamscommented, Apr 11, 2019

That commentary exists within the code that instantiates mapped types, that is, replaces type variables with types. Your example:

declare const tst: { [P in keyof number]: { [id: string]: number[P]; }; };

is a closed type and therefore is not subject to instantiation.

That is to say, when written:

type Mapped<T> = { [P in keyof T]: { [id: string]: T[P]; }; };
declare const tst: Mapped<number>;

there is more than just a basic inlining of number for T going on.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Conditional type is not being narrowed by if/else branch
Narrowing works on single variables. Typescript does not have the concept of related variables/parameters that can be narrowed together.
Read more >
Documentation - Narrowing - TypeScript
If we'd assigned a boolean to x , we'd have seen an error since that wasn't part of the declared type. x =...
Read more >
Type-Safe TypeScript with Type Narrowing - Rainer Hahnekamp
Type Narrowing against unknown ... Everything it takes, is to apply an if condition with the type predicate and voilà, problem gone.
Read more >
The guide to conditional types in TypeScript - LogRocket Blog
Conditional types can be recursive; that is, one, or both, of the branches can themselves be a conditional type: type Recursive<T> = T...
Read more >
how "narrow" a conditional type? : r/typescript - Reddit
(maybe I'm wrong) ... I've always found issues in linking a conditional return type to the input of a function. It always ends...
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