diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 30eb32a7ac48a..99ffe1c9eb23f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -603,6 +603,7 @@ import { isNewExpression, isNightly, isNodeDescendantOf, + isNonNullAccess, isNullishCoalesce, isNumericLiteral, isNumericLiteralName, @@ -26299,12 +26300,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (propName === undefined) { return type; } - const removeNullable = strictNullChecks && isOptionalChain(access) && maybeTypeOfKind(type, TypeFlags.Nullable); + const optionalChain = isOptionalChain(access); + const removeNullable = strictNullChecks && (optionalChain || isNonNullAccess(access)) && maybeTypeOfKind(type, TypeFlags.Nullable); let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName); if (!propType) { return type; } - propType = removeNullable ? getOptionalType(propType) : propType; + propType = removeNullable && optionalChain ? getOptionalType(propType) : propType; const narrowedPropType = narrowType(propType); return filterType(type, t => { const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 56b18ff90056d..0219352fe19b3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -295,6 +295,7 @@ import { isNamespaceExport, isNamespaceExportDeclaration, isNamespaceImport, + isNonNullExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, @@ -9482,3 +9483,10 @@ export function isOptionalDeclaration(declaration: Declaration): boolean { return false; } } + +/** @internal */ +export function isNonNullAccess(node: Node): node is AccessExpression { + const kind = node.kind; + return (kind === SyntaxKind.PropertyAccessExpression + || kind === SyntaxKind.ElementAccessExpression) && isNonNullExpression((node as AccessExpression).expression); +} diff --git a/tests/baselines/reference/narrowingUnionWithBang.js b/tests/baselines/reference/narrowingUnionWithBang.js new file mode 100644 index 0000000000000..09b1c234f12a5 --- /dev/null +++ b/tests/baselines/reference/narrowingUnionWithBang.js @@ -0,0 +1,85 @@ +//// [narrowingUnionWithBang.ts] +type WorkingType = { + thing?: + { name: 'Error1', message: string } | + { name: 'Error2', message: string } | + { name: 'Error3', message: string } | + { name: 'Error4', message: string } | + { name: 'Error5', message: string } | + { name: 'Error6', message: string } | + { name: 'Error7', message: string } | + { name: 'Error8', message: string } | + { name: 'Error9', message: string } | + { name: 'Correct', id: string } +}; +const working: WorkingType = null as unknown as WorkingType; +if (working.thing!.name !== "Correct") { + console.log(working.thing!.message) +} else { + console.log(working.thing!.id); +} + +type BorkedType = { + thing?: + { name: 'Error1', message: string } | + { name: 'Error2', message: string } | + { name: 'Error3', message: string } | + { name: 'Error4', message: string } | + { name: 'Error5', message: string } | + { name: 'Error6', message: string } | + { name: 'Error7', message: string } | + { name: 'Error8', message: string } | + { name: 'Correct', id: string } +}; +const borked: BorkedType = null as unknown as BorkedType; +if (borked.thing!.name !== "Correct") { + console.log(borked.thing!.message); +} else { + console.log(borked.thing!.id); +} + +export type FixedType = { + thing?: + { name: 'Error1', message: string } | + { name: 'Error2', message: string } | + { name: 'Error3', message: string } | + { name: 'Error4', message: string } | + { name: 'Error5', message: string } | + { name: 'Error6', message: string } | + { name: 'Error7', message: string } | + { name: 'Error8', message: string } | + { name: 'Correct', id: string } +}; +const fixed: FixedType = null as unknown as FixedType; + +if (fixed.thing?.name !== "Correct") { + console.log(fixed.thing!.message); +} else { + console.log(fixed.thing.id); +} + +//// [narrowingUnionWithBang.js] +"use strict"; +var _a; +Object.defineProperty(exports, "__esModule", { value: true }); +var working = null; +if (working.thing.name !== "Correct") { + console.log(working.thing.message); +} +else { + console.log(working.thing.id); +} +var borked = null; +if (borked.thing.name !== "Correct") { + console.log(borked.thing.message); +} +else { + console.log(borked.thing.id); +} +var fixed = null; +if (((_a = fixed.thing) === null || _a === void 0 ? void 0 : _a.name) !== "Correct") { + console.log(fixed.thing.message); +} +else { + console.log(fixed.thing.id); +} diff --git a/tests/baselines/reference/narrowingUnionWithBang.symbols b/tests/baselines/reference/narrowingUnionWithBang.symbols new file mode 100644 index 0000000000000..6bb48ad84f934 --- /dev/null +++ b/tests/baselines/reference/narrowingUnionWithBang.symbols @@ -0,0 +1,235 @@ +=== tests/cases/compiler/narrowingUnionWithBang.ts === +type WorkingType = { +>WorkingType : Symbol(WorkingType, Decl(narrowingUnionWithBang.ts, 0, 0)) + + thing?: +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 0, 20)) + + { name: 'Error1', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 2, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 2, 21)) + + { name: 'Error2', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 3, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 3, 21)) + + { name: 'Error3', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 4, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 4, 21)) + + { name: 'Error4', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 5, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 5, 21)) + + { name: 'Error5', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 6, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 6, 21)) + + { name: 'Error6', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 7, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 7, 21)) + + { name: 'Error7', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 8, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 8, 21)) + + { name: 'Error8', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 9, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 9, 21)) + + { name: 'Error9', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 10, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 10, 21)) + + { name: 'Correct', id: string } +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 11, 5)) +>id : Symbol(id, Decl(narrowingUnionWithBang.ts, 11, 22)) + +}; +const working: WorkingType = null as unknown as WorkingType; +>working : Symbol(working, Decl(narrowingUnionWithBang.ts, 13, 5)) +>WorkingType : Symbol(WorkingType, Decl(narrowingUnionWithBang.ts, 0, 0)) +>WorkingType : Symbol(WorkingType, Decl(narrowingUnionWithBang.ts, 0, 0)) + +if (working.thing!.name !== "Correct") { +>working.thing!.name : Symbol(name, Decl(narrowingUnionWithBang.ts, 2, 5), Decl(narrowingUnionWithBang.ts, 3, 5), Decl(narrowingUnionWithBang.ts, 4, 5), Decl(narrowingUnionWithBang.ts, 5, 5), Decl(narrowingUnionWithBang.ts, 6, 5) ... and 5 more) +>working.thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 0, 20)) +>working : Symbol(working, Decl(narrowingUnionWithBang.ts, 13, 5)) +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 0, 20)) +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 2, 5), Decl(narrowingUnionWithBang.ts, 3, 5), Decl(narrowingUnionWithBang.ts, 4, 5), Decl(narrowingUnionWithBang.ts, 5, 5), Decl(narrowingUnionWithBang.ts, 6, 5) ... and 5 more) + + console.log(working.thing!.message) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>working.thing!.message : Symbol(message, Decl(narrowingUnionWithBang.ts, 2, 21), Decl(narrowingUnionWithBang.ts, 3, 21), Decl(narrowingUnionWithBang.ts, 4, 21), Decl(narrowingUnionWithBang.ts, 5, 21), Decl(narrowingUnionWithBang.ts, 6, 21) ... and 4 more) +>working.thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 0, 20)) +>working : Symbol(working, Decl(narrowingUnionWithBang.ts, 13, 5)) +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 0, 20)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 2, 21), Decl(narrowingUnionWithBang.ts, 3, 21), Decl(narrowingUnionWithBang.ts, 4, 21), Decl(narrowingUnionWithBang.ts, 5, 21), Decl(narrowingUnionWithBang.ts, 6, 21) ... and 4 more) + +} else { + console.log(working.thing!.id); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>working.thing!.id : Symbol(id, Decl(narrowingUnionWithBang.ts, 11, 22)) +>working.thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 0, 20)) +>working : Symbol(working, Decl(narrowingUnionWithBang.ts, 13, 5)) +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 0, 20)) +>id : Symbol(id, Decl(narrowingUnionWithBang.ts, 11, 22)) +} + +type BorkedType = { +>BorkedType : Symbol(BorkedType, Decl(narrowingUnionWithBang.ts, 18, 1)) + + thing?: +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 20, 19)) + + { name: 'Error1', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 22, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 22, 21)) + + { name: 'Error2', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 23, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 23, 21)) + + { name: 'Error3', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 24, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 24, 21)) + + { name: 'Error4', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 25, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 25, 21)) + + { name: 'Error5', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 26, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 26, 21)) + + { name: 'Error6', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 27, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 27, 21)) + + { name: 'Error7', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 28, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 28, 21)) + + { name: 'Error8', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 29, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 29, 21)) + + { name: 'Correct', id: string } +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 30, 5)) +>id : Symbol(id, Decl(narrowingUnionWithBang.ts, 30, 22)) + +}; +const borked: BorkedType = null as unknown as BorkedType; +>borked : Symbol(borked, Decl(narrowingUnionWithBang.ts, 32, 5)) +>BorkedType : Symbol(BorkedType, Decl(narrowingUnionWithBang.ts, 18, 1)) +>BorkedType : Symbol(BorkedType, Decl(narrowingUnionWithBang.ts, 18, 1)) + +if (borked.thing!.name !== "Correct") { +>borked.thing!.name : Symbol(name, Decl(narrowingUnionWithBang.ts, 22, 5), Decl(narrowingUnionWithBang.ts, 23, 5), Decl(narrowingUnionWithBang.ts, 24, 5), Decl(narrowingUnionWithBang.ts, 25, 5), Decl(narrowingUnionWithBang.ts, 26, 5) ... and 4 more) +>borked.thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 20, 19)) +>borked : Symbol(borked, Decl(narrowingUnionWithBang.ts, 32, 5)) +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 20, 19)) +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 22, 5), Decl(narrowingUnionWithBang.ts, 23, 5), Decl(narrowingUnionWithBang.ts, 24, 5), Decl(narrowingUnionWithBang.ts, 25, 5), Decl(narrowingUnionWithBang.ts, 26, 5) ... and 4 more) + + console.log(borked.thing!.message); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>borked.thing!.message : Symbol(message, Decl(narrowingUnionWithBang.ts, 22, 21), Decl(narrowingUnionWithBang.ts, 23, 21), Decl(narrowingUnionWithBang.ts, 24, 21), Decl(narrowingUnionWithBang.ts, 25, 21), Decl(narrowingUnionWithBang.ts, 26, 21) ... and 3 more) +>borked.thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 20, 19)) +>borked : Symbol(borked, Decl(narrowingUnionWithBang.ts, 32, 5)) +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 20, 19)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 22, 21), Decl(narrowingUnionWithBang.ts, 23, 21), Decl(narrowingUnionWithBang.ts, 24, 21), Decl(narrowingUnionWithBang.ts, 25, 21), Decl(narrowingUnionWithBang.ts, 26, 21) ... and 3 more) + +} else { + console.log(borked.thing!.id); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>borked.thing!.id : Symbol(id, Decl(narrowingUnionWithBang.ts, 30, 22)) +>borked.thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 20, 19)) +>borked : Symbol(borked, Decl(narrowingUnionWithBang.ts, 32, 5)) +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 20, 19)) +>id : Symbol(id, Decl(narrowingUnionWithBang.ts, 30, 22)) +} + +export type FixedType = { +>FixedType : Symbol(FixedType, Decl(narrowingUnionWithBang.ts, 37, 1)) + + thing?: +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 39, 25)) + + { name: 'Error1', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 41, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 41, 21)) + + { name: 'Error2', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 42, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 42, 21)) + + { name: 'Error3', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 43, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 43, 21)) + + { name: 'Error4', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 44, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 44, 21)) + + { name: 'Error5', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 45, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 45, 21)) + + { name: 'Error6', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 46, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 46, 21)) + + { name: 'Error7', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 47, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 47, 21)) + + { name: 'Error8', message: string } | +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 48, 5)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 48, 21)) + + { name: 'Correct', id: string } +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 49, 5)) +>id : Symbol(id, Decl(narrowingUnionWithBang.ts, 49, 22)) + +}; +const fixed: FixedType = null as unknown as FixedType; +>fixed : Symbol(fixed, Decl(narrowingUnionWithBang.ts, 51, 5)) +>FixedType : Symbol(FixedType, Decl(narrowingUnionWithBang.ts, 37, 1)) +>FixedType : Symbol(FixedType, Decl(narrowingUnionWithBang.ts, 37, 1)) + +if (fixed.thing?.name !== "Correct") { +>fixed.thing?.name : Symbol(name, Decl(narrowingUnionWithBang.ts, 41, 5), Decl(narrowingUnionWithBang.ts, 42, 5), Decl(narrowingUnionWithBang.ts, 43, 5), Decl(narrowingUnionWithBang.ts, 44, 5), Decl(narrowingUnionWithBang.ts, 45, 5) ... and 4 more) +>fixed.thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 39, 25)) +>fixed : Symbol(fixed, Decl(narrowingUnionWithBang.ts, 51, 5)) +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 39, 25)) +>name : Symbol(name, Decl(narrowingUnionWithBang.ts, 41, 5), Decl(narrowingUnionWithBang.ts, 42, 5), Decl(narrowingUnionWithBang.ts, 43, 5), Decl(narrowingUnionWithBang.ts, 44, 5), Decl(narrowingUnionWithBang.ts, 45, 5) ... and 4 more) + + console.log(fixed.thing!.message); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>fixed.thing!.message : Symbol(message, Decl(narrowingUnionWithBang.ts, 41, 21), Decl(narrowingUnionWithBang.ts, 42, 21), Decl(narrowingUnionWithBang.ts, 43, 21), Decl(narrowingUnionWithBang.ts, 44, 21), Decl(narrowingUnionWithBang.ts, 45, 21) ... and 3 more) +>fixed.thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 39, 25)) +>fixed : Symbol(fixed, Decl(narrowingUnionWithBang.ts, 51, 5)) +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 39, 25)) +>message : Symbol(message, Decl(narrowingUnionWithBang.ts, 41, 21), Decl(narrowingUnionWithBang.ts, 42, 21), Decl(narrowingUnionWithBang.ts, 43, 21), Decl(narrowingUnionWithBang.ts, 44, 21), Decl(narrowingUnionWithBang.ts, 45, 21) ... and 3 more) + +} else { + console.log(fixed.thing.id); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>fixed.thing.id : Symbol(id, Decl(narrowingUnionWithBang.ts, 49, 22)) +>fixed.thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 39, 25)) +>fixed : Symbol(fixed, Decl(narrowingUnionWithBang.ts, 51, 5)) +>thing : Symbol(thing, Decl(narrowingUnionWithBang.ts, 39, 25)) +>id : Symbol(id, Decl(narrowingUnionWithBang.ts, 49, 22)) +} diff --git a/tests/baselines/reference/narrowingUnionWithBang.types b/tests/baselines/reference/narrowingUnionWithBang.types new file mode 100644 index 0000000000000..431fbb48c97e4 --- /dev/null +++ b/tests/baselines/reference/narrowingUnionWithBang.types @@ -0,0 +1,257 @@ +=== tests/cases/compiler/narrowingUnionWithBang.ts === +type WorkingType = { +>WorkingType : { thing?: { name: 'Error1'; message: string; } | { name: 'Error2'; message: string; } | { name: 'Error3'; message: string; } | { name: 'Error4'; message: string; } | { name: 'Error5'; message: string; } | { name: 'Error6'; message: string; } | { name: 'Error7'; message: string; } | { name: 'Error8'; message: string; } | { name: 'Error9'; message: string; } | { name: 'Correct'; id: string; } | undefined; } + + thing?: +>thing : { name: 'Error1'; message: string; } | { name: 'Error2'; message: string; } | { name: 'Error3'; message: string; } | { name: 'Error4'; message: string; } | { name: 'Error5'; message: string; } | { name: 'Error6'; message: string; } | { name: 'Error7'; message: string; } | { name: 'Error8'; message: string; } | { name: 'Error9'; message: string; } | { name: 'Correct'; id: string; } | undefined + + { name: 'Error1', message: string } | +>name : "Error1" +>message : string + + { name: 'Error2', message: string } | +>name : "Error2" +>message : string + + { name: 'Error3', message: string } | +>name : "Error3" +>message : string + + { name: 'Error4', message: string } | +>name : "Error4" +>message : string + + { name: 'Error5', message: string } | +>name : "Error5" +>message : string + + { name: 'Error6', message: string } | +>name : "Error6" +>message : string + + { name: 'Error7', message: string } | +>name : "Error7" +>message : string + + { name: 'Error8', message: string } | +>name : "Error8" +>message : string + + { name: 'Error9', message: string } | +>name : "Error9" +>message : string + + { name: 'Correct', id: string } +>name : "Correct" +>id : string + +}; +const working: WorkingType = null as unknown as WorkingType; +>working : WorkingType +>null as unknown as WorkingType : WorkingType +>null as unknown : unknown +>null : null + +if (working.thing!.name !== "Correct") { +>working.thing!.name !== "Correct" : boolean +>working.thing!.name : "Error1" | "Error2" | "Error3" | "Error4" | "Error5" | "Error6" | "Error7" | "Error8" | "Error9" | "Correct" +>working.thing! : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Error9"; message: string; } | { name: "Correct"; id: string; } +>working.thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Error9"; message: string; } | { name: "Correct"; id: string; } | undefined +>working : WorkingType +>thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Error9"; message: string; } | { name: "Correct"; id: string; } | undefined +>name : "Error1" | "Error2" | "Error3" | "Error4" | "Error5" | "Error6" | "Error7" | "Error8" | "Error9" | "Correct" +>"Correct" : "Correct" + + console.log(working.thing!.message) +>console.log(working.thing!.message) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>working.thing!.message : string +>working.thing! : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Error9"; message: string; } +>working.thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Error9"; message: string; } | undefined +>working : WorkingType +>thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Error9"; message: string; } | undefined +>message : string + +} else { + console.log(working.thing!.id); +>console.log(working.thing!.id) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>working.thing!.id : string +>working.thing! : { name: "Correct"; id: string; } +>working.thing : { name: "Correct"; id: string; } +>working : WorkingType +>thing : { name: "Correct"; id: string; } +>id : string +} + +type BorkedType = { +>BorkedType : { thing?: { name: 'Error1'; message: string; } | { name: 'Error2'; message: string; } | { name: 'Error3'; message: string; } | { name: 'Error4'; message: string; } | { name: 'Error5'; message: string; } | { name: 'Error6'; message: string; } | { name: 'Error7'; message: string; } | { name: 'Error8'; message: string; } | { name: 'Correct'; id: string; } | undefined; } + + thing?: +>thing : { name: 'Error1'; message: string; } | { name: 'Error2'; message: string; } | { name: 'Error3'; message: string; } | { name: 'Error4'; message: string; } | { name: 'Error5'; message: string; } | { name: 'Error6'; message: string; } | { name: 'Error7'; message: string; } | { name: 'Error8'; message: string; } | { name: 'Correct'; id: string; } | undefined + + { name: 'Error1', message: string } | +>name : "Error1" +>message : string + + { name: 'Error2', message: string } | +>name : "Error2" +>message : string + + { name: 'Error3', message: string } | +>name : "Error3" +>message : string + + { name: 'Error4', message: string } | +>name : "Error4" +>message : string + + { name: 'Error5', message: string } | +>name : "Error5" +>message : string + + { name: 'Error6', message: string } | +>name : "Error6" +>message : string + + { name: 'Error7', message: string } | +>name : "Error7" +>message : string + + { name: 'Error8', message: string } | +>name : "Error8" +>message : string + + { name: 'Correct', id: string } +>name : "Correct" +>id : string + +}; +const borked: BorkedType = null as unknown as BorkedType; +>borked : BorkedType +>null as unknown as BorkedType : BorkedType +>null as unknown : unknown +>null : null + +if (borked.thing!.name !== "Correct") { +>borked.thing!.name !== "Correct" : boolean +>borked.thing!.name : "Error1" | "Error2" | "Error3" | "Error4" | "Error5" | "Error6" | "Error7" | "Error8" | "Correct" +>borked.thing! : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Correct"; id: string; } +>borked.thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Correct"; id: string; } | undefined +>borked : BorkedType +>thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Correct"; id: string; } | undefined +>name : "Error1" | "Error2" | "Error3" | "Error4" | "Error5" | "Error6" | "Error7" | "Error8" | "Correct" +>"Correct" : "Correct" + + console.log(borked.thing!.message); +>console.log(borked.thing!.message) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>borked.thing!.message : string +>borked.thing! : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } +>borked.thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | undefined +>borked : BorkedType +>thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | undefined +>message : string + +} else { + console.log(borked.thing!.id); +>console.log(borked.thing!.id) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>borked.thing!.id : string +>borked.thing! : { name: "Correct"; id: string; } +>borked.thing : { name: "Correct"; id: string; } | undefined +>borked : BorkedType +>thing : { name: "Correct"; id: string; } | undefined +>id : string +} + +export type FixedType = { +>FixedType : { thing?: { name: 'Error1'; message: string; } | { name: 'Error2'; message: string; } | { name: 'Error3'; message: string; } | { name: 'Error4'; message: string; } | { name: 'Error5'; message: string; } | { name: 'Error6'; message: string; } | { name: 'Error7'; message: string; } | { name: 'Error8'; message: string; } | { name: 'Correct'; id: string; } | undefined; } + + thing?: +>thing : { name: 'Error1'; message: string; } | { name: 'Error2'; message: string; } | { name: 'Error3'; message: string; } | { name: 'Error4'; message: string; } | { name: 'Error5'; message: string; } | { name: 'Error6'; message: string; } | { name: 'Error7'; message: string; } | { name: 'Error8'; message: string; } | { name: 'Correct'; id: string; } | undefined + + { name: 'Error1', message: string } | +>name : "Error1" +>message : string + + { name: 'Error2', message: string } | +>name : "Error2" +>message : string + + { name: 'Error3', message: string } | +>name : "Error3" +>message : string + + { name: 'Error4', message: string } | +>name : "Error4" +>message : string + + { name: 'Error5', message: string } | +>name : "Error5" +>message : string + + { name: 'Error6', message: string } | +>name : "Error6" +>message : string + + { name: 'Error7', message: string } | +>name : "Error7" +>message : string + + { name: 'Error8', message: string } | +>name : "Error8" +>message : string + + { name: 'Correct', id: string } +>name : "Correct" +>id : string + +}; +const fixed: FixedType = null as unknown as FixedType; +>fixed : FixedType +>null as unknown as FixedType : FixedType +>null as unknown : unknown +>null : null + +if (fixed.thing?.name !== "Correct") { +>fixed.thing?.name !== "Correct" : boolean +>fixed.thing?.name : "Error1" | "Error2" | "Error3" | "Error4" | "Error5" | "Error6" | "Error7" | "Error8" | "Correct" | undefined +>fixed.thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Correct"; id: string; } | undefined +>fixed : FixedType +>thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | { name: "Correct"; id: string; } | undefined +>name : "Error1" | "Error2" | "Error3" | "Error4" | "Error5" | "Error6" | "Error7" | "Error8" | "Correct" | undefined +>"Correct" : "Correct" + + console.log(fixed.thing!.message); +>console.log(fixed.thing!.message) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>fixed.thing!.message : string +>fixed.thing! : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } +>fixed.thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | undefined +>fixed : FixedType +>thing : { name: "Error1"; message: string; } | { name: "Error2"; message: string; } | { name: "Error3"; message: string; } | { name: "Error4"; message: string; } | { name: "Error5"; message: string; } | { name: "Error6"; message: string; } | { name: "Error7"; message: string; } | { name: "Error8"; message: string; } | undefined +>message : string + +} else { + console.log(fixed.thing.id); +>console.log(fixed.thing.id) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>fixed.thing.id : string +>fixed.thing : { name: "Correct"; id: string; } +>fixed : FixedType +>thing : { name: "Correct"; id: string; } +>id : string +} diff --git a/tests/cases/compiler/narrowingUnionWithBang.ts b/tests/cases/compiler/narrowingUnionWithBang.ts new file mode 100644 index 0000000000000..21e0bc36695b6 --- /dev/null +++ b/tests/cases/compiler/narrowingUnionWithBang.ts @@ -0,0 +1,59 @@ +// @strict: true +type WorkingType = { + thing?: + { name: 'Error1', message: string } | + { name: 'Error2', message: string } | + { name: 'Error3', message: string } | + { name: 'Error4', message: string } | + { name: 'Error5', message: string } | + { name: 'Error6', message: string } | + { name: 'Error7', message: string } | + { name: 'Error8', message: string } | + { name: 'Error9', message: string } | + { name: 'Correct', id: string } +}; +const working: WorkingType = null as unknown as WorkingType; +if (working.thing!.name !== "Correct") { + console.log(working.thing!.message) +} else { + console.log(working.thing!.id); +} + +type BorkedType = { + thing?: + { name: 'Error1', message: string } | + { name: 'Error2', message: string } | + { name: 'Error3', message: string } | + { name: 'Error4', message: string } | + { name: 'Error5', message: string } | + { name: 'Error6', message: string } | + { name: 'Error7', message: string } | + { name: 'Error8', message: string } | + { name: 'Correct', id: string } +}; +const borked: BorkedType = null as unknown as BorkedType; +if (borked.thing!.name !== "Correct") { + console.log(borked.thing!.message); +} else { + console.log(borked.thing!.id); +} + +export type FixedType = { + thing?: + { name: 'Error1', message: string } | + { name: 'Error2', message: string } | + { name: 'Error3', message: string } | + { name: 'Error4', message: string } | + { name: 'Error5', message: string } | + { name: 'Error6', message: string } | + { name: 'Error7', message: string } | + { name: 'Error8', message: string } | + { name: 'Correct', id: string } +}; +const fixed: FixedType = null as unknown as FixedType; + +if (fixed.thing?.name !== "Correct") { + console.log(fixed.thing!.message); +} else { + console.log(fixed.thing.id); +} \ No newline at end of file