diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 67a5f4afae458..9f4a9f62bfe40 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25328,11 +25328,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..16e2d3f37851b --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofUndefined.js @@ -0,0 +1,31 @@ +//// [narrowingTypeofUndefined.ts] +declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } } + +if (typeof a.error === 'undefined') { + 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.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 new file mode 100644 index 0000000000000..33a81259b6909 --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofUndefined.symbols @@ -0,0 +1,52 @@ +=== tests/cases/compiler/narrowingTypeofUndefined.ts === +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)) +>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, 67)) +>a : Symbol(a, Decl(narrowingTypeofUndefined.ts, 0, 13)) +>error : Symbol(error, Decl(narrowingTypeofUndefined.ts, 0, 18), Decl(narrowingTypeofUndefined.ts, 0, 67)) + + 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)) +} +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 new file mode 100644 index 0000000000000..3a84e875be650 --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofUndefined.types @@ -0,0 +1,58 @@ +=== tests/cases/compiler/narrowingTypeofUndefined.ts === +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 : { 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 : { prop: string; } +>a : { error: { prop: string; }; result: undefined; } | { error: undefined; result: { prop: number; }; } +>error : { prop: string; } +>'undefined' : "undefined" + + 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 new file mode 100644 index 0000000000000..af83df804edd4 --- /dev/null +++ b/tests/cases/compiler/narrowingTypeofUndefined.ts @@ -0,0 +1,15 @@ +declare const a: { error: { prop: string }, result: undefined } | { error: undefined, result: { prop: number } } + +if (typeof a.error === 'undefined') { + a.result.prop; // number +} +else { + a.error.prop; // string +} + +if (typeof a.error !== 'undefined') { + a.error.prop; // string +} +else { + a.result.prop; // number +}