Closed
Closed
Copy link
Labels
DuplicateAn existing issue was already createdAn existing issue was already created
Description
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.
Metadata
Metadata
Assignees
Labels
DuplicateAn existing issue was already createdAn existing issue was already created
Activity
jack-williams commentedon Jun 1, 2020
This is the same as #31156; the problem is that the left constituent of the union
X<{ [k: string]: unknown }>, which isA<{ [k: string]: unknown }, keyof ({ [k: string]: unknown })>, is not a subtype ofX<Some>. As a result, theApart of the type insome2is removed, leavingBwhich is related toX<Some>.InExtremaRes commentedon Jun 1, 2020
Thank you Jack Williams (@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:
The first lines show that
A<any, any>is assignable toA<Some, keyof Some>(and therefore a subtype, isn't?), howeverA<any, any> | Bstill narrows toBalone.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 commentedon Jun 1, 2020
Assignability and subtyping are different relations, so while
anyassignable 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 commentedon Jun 10, 2020
Oh, I see. Thank you for your explanation. Feel free to close this as a duplicate then :)
typescript-bot commentedon Jun 12, 2020
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.