Skip to content

fix narrowTypeByDiscriminant for non null expression access #52136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ import {
isNewExpression,
isNightly,
isNodeDescendantOf,
isNonNullAccess,
isNullishCoalesce,
isNumericLiteral,
isNumericLiteralName,
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we remove null or undefined from the type, but then a few lines below we add back only an undefined. That reflects what happens in an optional chain, but the ! operator never changes one to the other. It may not matter, but doesn't quite seem right.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I had missed that. So I guess we shouldn't call getOptionalType below if it's a ! operator, since ! actually gets rid of undefined and null, unlike an optional chain.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahejlsberg I pushed the above change, let me know if it's not right or if there's something else missing.

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);
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ import {
isNamespaceExport,
isNamespaceExportDeclaration,
isNamespaceImport,
isNonNullExpression,
isNoSubstitutionTemplateLiteral,
isNumericLiteral,
isObjectLiteralExpression,
Expand Down Expand Up @@ -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);
}
85 changes: 85 additions & 0 deletions tests/baselines/reference/narrowingUnionWithBang.js
Original file line number Diff line number Diff line change
@@ -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);
}
235 changes: 235 additions & 0 deletions tests/baselines/reference/narrowingUnionWithBang.symbols
Original file line number Diff line number Diff line change
@@ -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))
}
Loading