Skip to content

Previous generic union does not narrow to an union again with type predicate #38869

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

Closed
InExtremaRes opened this issue Jun 1, 2020 · 5 comments · Fixed by #49625
Closed

Previous generic union does not narrow to an union again with type predicate #38869

InExtremaRes opened this issue Jun 1, 2020 · 5 comments · Fixed by #49625
Labels
Duplicate An existing issue was already created

Comments

@InExtremaRes
Copy link

InExtremaRes commented Jun 1, 2020

TypeScript Version: 4.0.0-dev.20200531

Search Terms: type narrow discriminant generic

Code

interface A<T, K extends keyof T> {
    t: true;
    v: T[K];
}
interface B {
    t: false;
    e: Error;
}
type X<T> = A<T, keyof T> | B;

type Some = { y: number; z: string };
declare function assertIsX(x: unknown): asserts x is X<Some>;

// CASE 1: narrowing unknown (works)
declare const some: unknown;
assertIsX(some);
// Ok, `r1` is `string | number | Error` as expected
const r1 = some.t ? some.v : some.e;

// CASE 2: before narrowing (works)
declare const some2: X<{ [k: string]: unknown }>;
// Ok, `r2` is `unknown` as expected
const r2 = some2.t ? some2.v : some2.e;

// CASE 3: narrowing again (does not work)
assertIsX(some2);
// ERROR: `some2` narrowed to `B`, not `X<Some>`,
// so `v` isn't accesible
some2.t ? some2.v : some2.e;

// CASE 4: narrowing `A<any, any> | B` (does not work)
declare function onlyASomeHere(x: A<Some, keyof Some>): void;
declare const a: A<any, any>;
// `A<any, any>` is assignable to `A<Some, keyof Some>`
onlyASomeHere(a);

declare const some3: A<any, any> | B;
assertIsX(some3);
// ERROR: `some3` narrowed to `B`, not `X<Some>`
some3.t ? some3.v : some3.e;

I'm using a type assertion, but a type predicate makes no difference. The same happens starting with X<any> or X<unknown>.

Expected behavior: some2 is narrowed to the union A<Some> | B so .v can be accessed when t === true.

Actual behavior: some2 narrows only to B, so when t === true some2 is never.

Playground Link: playground

Related Issues: Maybe #30557 but that case works on last nightly and this does not.

@jack-williams
Copy link
Collaborator

This is the same as #31156; the problem is that the left constituent of the union X<{ [k: string]: unknown }>, which is A<{ [k: string]: unknown }, keyof ({ [k: string]: unknown })>, is not a subtype of X<Some>. As a result, the A part of the type in some2 is removed, leaving B which is related to X<Some>.

@InExtremaRes
Copy link
Author

InExtremaRes commented Jun 1, 2020

Thank you @jack-williams, I didn't find that issue, reading it now. However the PR that should close that issue is closed with the comment "[...] there’s not currently a solution that’s worth the disruption it will bring". Something similar in #32158.

Also there is something I don't understand completely here. I updated the description just in case. Consider this snippet:

// CASE 4: narrowing `A<any, any> | B` (does not work)
declare function onlyASomeHere(x: A<Some, keyof Some>): void;
declare const a: A<any, any>;
// `A<any, any>` is assignable to `A<Some, keyof Some>`
onlyASomeHere(a);

declare const some3: A<any, any> | B;
assertIsX(some3);
// ERROR: `some3` narrowed to `B`, not `X<Some>`
some3.t ? some3.v : some3.e;

The first lines show that A<any, any> is assignable to A<Some, keyof Some> (and therefore a subtype, isn't?), however A<any, any> | B still narrows to B alone.

If this is still related to the issue you have linked, feel free to close this as a duplicate and I'll subscribe to that instead.

@jack-williams
Copy link
Collaborator

Assignability and subtyping are different relations, so while any assignable to everything, it is not a subtype of everything.

Function calls use assignability, which is why there is no error.
Type predicates filter using subtyping, which is why the any case gets removed.

@InExtremaRes
Copy link
Author

Oh, I see. Thank you for your explanation. Feel free to close this as a duplicate then :)

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jun 10, 2020
@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
4 participants