From b4e02abf06b7f014855d5914aa1f6dd501a45a9d Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Thu, 18 Aug 2022 00:33:31 +0300 Subject: [PATCH 1/2] fix(50340): narrow type by discriminant in typeof --- src/compiler/checker.ts | 8 ++++++ .../reference/narrowingTypeofUndefined.js | 12 +++++++++ .../narrowingTypeofUndefined.symbols | 23 ++++++++++++++++ .../reference/narrowingTypeofUndefined.types | 26 +++++++++++++++++++ .../compiler/narrowingTypeofUndefined.ts | 5 ++++ 5 files changed, 74 insertions(+) create mode 100644 tests/baselines/reference/narrowingTypeofUndefined.js create mode 100644 tests/baselines/reference/narrowingTypeofUndefined.symbols create mode 100644 tests/baselines/reference/narrowingTypeofUndefined.types create mode 100644 tests/cases/compiler/narrowingTypeofUndefined.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8d53b7d1c4eb5..2dc502799d725 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25260,11 +25260,19 @@ namespace ts { } const target = getReferenceCandidate(typeOfExpr.expression); if (!isMatchingReference(reference, target)) { + const propertyAccess = getDiscriminantPropertyAccess(typeOfExpr.expression, type); + if (propertyAccess) { + return narrowTypeByDiscriminant(type, propertyAccess, t => narrowTypeByLiteralExpression(t, literal, assumeTrue)); + } if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { return getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } return type; } + return narrowTypeByLiteralExpression(type, literal, assumeTrue); + } + + function narrowTypeByLiteralExpression(type: Type, literal: LiteralExpression, assumeTrue: boolean) { return assumeTrue ? narrowTypeByTypeName(type, literal.text) : getTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject); diff --git a/tests/baselines/reference/narrowingTypeofUndefined.js b/tests/baselines/reference/narrowingTypeofUndefined.js new file mode 100644 index 0000000000000..8d6dc02328cce --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofUndefined.js @@ -0,0 +1,12 @@ +//// [narrowingTypeofUndefined.ts] +declare const a: { error: { a: string }, result: undefined } | { error: undefined, result: { b: number } } + +if (typeof a.error === 'undefined') { + a.result.b; // ok +} + + +//// [narrowingTypeofUndefined.js] +if (typeof a.error === 'undefined') { + a.result.b; // ok +} diff --git a/tests/baselines/reference/narrowingTypeofUndefined.symbols b/tests/baselines/reference/narrowingTypeofUndefined.symbols new file mode 100644 index 0000000000000..f9e9d3a1f072c --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofUndefined.symbols @@ -0,0 +1,23 @@ +=== tests/cases/compiler/narrowingTypeofUndefined.ts === +declare const a: { error: { a: string }, result: undefined } | { error: undefined, result: { b: number } } +>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) +>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18)) +>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 27)) +>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 40)) +>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 64)) +>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 82)) +>b : Symbol(b, Decl(narrowingTypeofUndefined.ts, 0, 92)) + +if (typeof a.error === 'undefined') { +>a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 64)) +>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) +>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 64)) + + a.result.b; // ok +>a.result.b : Symbol(b, Decl(narrowingTypeofUndefined.ts, 0, 92)) +>a.result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 40), Decl(narrowingTypeofUndefined.ts, 0, 82)) +>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) +>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 40), Decl(narrowingTypeofUndefined.ts, 0, 82)) +>b : Symbol(b, Decl(narrowingTypeofUndefined.ts, 0, 92)) +} + diff --git a/tests/baselines/reference/narrowingTypeofUndefined.types b/tests/baselines/reference/narrowingTypeofUndefined.types new file mode 100644 index 0000000000000..c909af326cc02 --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofUndefined.types @@ -0,0 +1,26 @@ +=== tests/cases/compiler/narrowingTypeofUndefined.ts === +declare const a: { error: { a: string }, result: undefined } | { error: undefined, result: { b: number } } +>a : { error: { a: string;}; result: undefined; } | { error: undefined; result: { b: number;}; } +>error : { a: string; } +>a : string +>result : undefined +>error : undefined +>result : { b: number; } +>b : number + +if (typeof a.error === 'undefined') { +>typeof a.error === 'undefined' : boolean +>typeof a.error : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>a.error : { a: string; } +>a : { error: { a: string; }; result: undefined; } | { error: undefined; result: { b: number; }; } +>error : { a: string; } +>'undefined' : "undefined" + + a.result.b; // ok +>a.result.b : number +>a.result : { b: number; } +>a : { error: { a: string; }; result: undefined; } | { error: undefined; result: { b: number; }; } +>result : { b: number; } +>b : number +} + diff --git a/tests/cases/compiler/narrowingTypeofUndefined.ts b/tests/cases/compiler/narrowingTypeofUndefined.ts new file mode 100644 index 0000000000000..ab83357c90ffd --- /dev/null +++ b/tests/cases/compiler/narrowingTypeofUndefined.ts @@ -0,0 +1,5 @@ +declare const a: { error: { a: string }, result: undefined } | { error: undefined, result: { b: number } } + +if (typeof a.error === 'undefined') { + a.result.b; // ok +} From 5ffac1f3dc0e299449f3799bf08c279f3f048ad5 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 27 Aug 2022 09:56:10 +0300 Subject: [PATCH 2/2] add additional test cases --- .../reference/narrowingTypeofUndefined.js | 25 +++++++- .../narrowingTypeofUndefined.symbols | 55 ++++++++++++---- .../reference/narrowingTypeofUndefined.types | 62 ++++++++++++++----- .../compiler/narrowingTypeofUndefined.ts | 14 ++++- 4 files changed, 123 insertions(+), 33 deletions(-) diff --git a/tests/baselines/reference/narrowingTypeofUndefined.js b/tests/baselines/reference/narrowingTypeofUndefined.js index 8d6dc02328cce..16e2d3f37851b 100644 --- a/tests/baselines/reference/narrowingTypeofUndefined.js +++ b/tests/baselines/reference/narrowingTypeofUndefined.js @@ -1,12 +1,31 @@ //// [narrowingTypeofUndefined.ts] -declare const a: { error: { a: string }, result: undefined } | { error: undefined, result: { b: number } } +declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } } if (typeof a.error === 'undefined') { - a.result.b; // ok + a.result.prop; // number +} +else { + a.error.prop; // string +} + +if (typeof a.error !== 'undefined') { + a.error.prop; // string +} +else { + a.result.prop; // number } //// [narrowingTypeofUndefined.js] if (typeof a.error === 'undefined') { - a.result.b; // ok + a.result.prop; // number +} +else { + a.error.prop; // string +} +if (typeof a.error !== 'undefined') { + a.error.prop; // string +} +else { + a.result.prop; // number } diff --git a/tests/baselines/reference/narrowingTypeofUndefined.symbols b/tests/baselines/reference/narrowingTypeofUndefined.symbols index f9e9d3a1f072c..33a81259b6909 100644 --- a/tests/baselines/reference/narrowingTypeofUndefined.symbols +++ b/tests/baselines/reference/narrowingTypeofUndefined.symbols @@ -1,23 +1,52 @@ === tests/cases/compiler/narrowingTypeofUndefined.ts === -declare const a: { error: { a: string }, result: undefined } | { error: undefined, result: { b: number } } +declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } } >a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) >error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18)) ->a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 27)) ->result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 40)) ->error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 64)) ->result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 82)) ->b : Symbol(b, Decl(narrowingTypeofUndefined.ts, 0, 92)) +>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27)) +>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43)) +>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 67)) +>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 85)) +>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95)) if (typeof a.error === 'undefined') { ->a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 64)) +>a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67)) >a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) ->error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 64)) +>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67)) - a.result.b; // ok ->a.result.b : Symbol(b, Decl(narrowingTypeofUndefined.ts, 0, 92)) ->a.result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 40), Decl(narrowingTypeofUndefined.ts, 0, 82)) + a.result.prop; // number +>a.result.prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95)) +>a.result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43), Decl(narrowingTypeofUndefined.ts, 0, 85)) >a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) ->result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 40), Decl(narrowingTypeofUndefined.ts, 0, 82)) ->b : Symbol(b, Decl(narrowingTypeofUndefined.ts, 0, 92)) +>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43), Decl(narrowingTypeofUndefined.ts, 0, 85)) +>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95)) +} +else { + a.error.prop; // string +>a.error.prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27)) +>a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67)) +>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) +>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67)) +>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27)) +} + +if (typeof a.error !== 'undefined') { +>a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67)) +>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) +>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67)) + + a.error.prop; // string +>a.error.prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27)) +>a.error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67)) +>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) +>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67)) +>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 27)) +} +else { + a.result.prop; // number +>a.result.prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95)) +>a.result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43), Decl(narrowingTypeofUndefined.ts, 0, 85)) +>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) +>result : Symbol(result, Decl(narrowingTypeofUndefined.ts, 0, 43), Decl(narrowingTypeofUndefined.ts, 0, 85)) +>prop : Symbol(prop, Decl(narrowingTypeofUndefined.ts, 0, 95)) } diff --git a/tests/baselines/reference/narrowingTypeofUndefined.types b/tests/baselines/reference/narrowingTypeofUndefined.types index c909af326cc02..3a84e875be650 100644 --- a/tests/baselines/reference/narrowingTypeofUndefined.types +++ b/tests/baselines/reference/narrowingTypeofUndefined.types @@ -1,26 +1,58 @@ === tests/cases/compiler/narrowingTypeofUndefined.ts === -declare const a: { error: { a: string }, result: undefined } | { error: undefined, result: { b: number } } ->a : { error: { a: string;}; result: undefined; } | { error: undefined; result: { b: number;}; } ->error : { a: string; } ->a : string +declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } } +>a : { error: { prop: string;}; result: undefined; } | { error: undefined; result: { prop: number;}; } +>error : { prop: string; } +>prop : string >result : undefined >error : undefined ->result : { b: number; } ->b : number +>result : { prop: number; } +>prop : number if (typeof a.error === 'undefined') { >typeof a.error === 'undefined' : boolean >typeof a.error : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" ->a.error : { a: string; } ->a : { error: { a: string; }; result: undefined; } | { error: undefined; result: { b: number; }; } ->error : { a: string; } +>a.error : { prop: string; } +>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; } +>error : { prop: string; } >'undefined' : "undefined" - a.result.b; // ok ->a.result.b : number ->a.result : { b: number; } ->a : { error: { a: string; }; result: undefined; } | { error: undefined; result: { b: number; }; } ->result : { b: number; } ->b : number + a.result.prop; // number +>a.result.prop : number +>a.result : { prop: number; } +>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; } +>result : { prop: number; } +>prop : number +} +else { + a.error.prop; // string +>a.error.prop : string +>a.error : { prop: string; } +>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; } +>error : { prop: string; } +>prop : string +} + +if (typeof a.error !== 'undefined') { +>typeof a.error !== 'undefined' : boolean +>typeof a.error : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>a.error : { prop: string; } +>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; } +>error : { prop: string; } +>'undefined' : "undefined" + + a.error.prop; // string +>a.error.prop : string +>a.error : { prop: string; } +>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; } +>error : { prop: string; } +>prop : string +} +else { + a.result.prop; // number +>a.result.prop : number +>a.result : { prop: number; } +>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; } +>result : { prop: number; } +>prop : number } diff --git a/tests/cases/compiler/narrowingTypeofUndefined.ts b/tests/cases/compiler/narrowingTypeofUndefined.ts index ab83357c90ffd..af83df804edd4 100644 --- a/tests/cases/compiler/narrowingTypeofUndefined.ts +++ b/tests/cases/compiler/narrowingTypeofUndefined.ts @@ -1,5 +1,15 @@ -declare const a: { error: { a: string }, result: undefined } | { error: undefined, result: { b: number } } +declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } } if (typeof a.error === 'undefined') { - a.result.b; // ok + a.result.prop; // number +} +else { + a.error.prop; // string +} + +if (typeof a.error !== 'undefined') { + a.error.prop; // string +} +else { + a.result.prop; // number }