Skip to content
Permalink
Browse files
fix(50340): typeof ... === "undefined" check on discriminated union o…
…f undefined and object type doesn't narrow correctly (#50344)

* fix(50340): narrow type by discriminant in typeof

* add additional test cases
  • Loading branch information
a-tarasyuk committed Aug 31, 2022
1 parent 43f8ae6 commit a9797d218d34fefc61b823d0e1dd24f6eb5363c8
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 0 deletions.
@@ -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);
@@ -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
}
@@ -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))
}

@@ -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
}

@@ -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
}

0 comments on commit a9797d2

Please sign in to comment.