From 89c173fddc38f1a2eeb80879d1ad4127f1a4741a Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 25 Jan 2021 09:29:51 -0800 Subject: [PATCH] Narrow via discriminant property through optional chain (#42450) * Naive attempt at narrowing via discriminant property through optional chain * Clean up test --- src/compiler/checker.ts | 5 ++- .../controlFlowOptionalChain.symbols | 8 ++-- .../reference/controlFlowOptionalChain.types | 6 +-- .../reference/controlFlowOptionalChain2.js | 30 +++++++++++++ .../controlFlowOptionalChain2.symbols | 44 +++++++++++++++++++ .../reference/controlFlowOptionalChain2.types | 44 +++++++++++++++++++ .../controlFlow/controlFlowOptionalChain2.ts | 20 +++++++++ 7 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/controlFlowOptionalChain2.js create mode 100644 tests/baselines/reference/controlFlowOptionalChain2.symbols create mode 100644 tests/baselines/reference/controlFlowOptionalChain2.types create mode 100644 tests/cases/conformance/controlFlow/controlFlowOptionalChain2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 49c45384e6d..5966cdc0e54 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22599,10 +22599,13 @@ namespace ts { if (propName === undefined) { return type; } - const propType = getTypeOfPropertyOfType(type, propName); + const includesUndefined = strictNullChecks && maybeTypeOfKind(type, TypeFlags.Undefined); + const removeOptional = includesUndefined && isOptionalChain(access); + let propType = getTypeOfPropertyOfType(removeOptional ? getTypeWithFacts(type, TypeFacts.NEUndefined) : type, propName); if (!propType) { return type; } + propType = removeOptional ? getOptionalType(propType) : propType; const narrowedPropType = narrowType(propType); return filterType(type, t => { const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); diff --git a/tests/baselines/reference/controlFlowOptionalChain.symbols b/tests/baselines/reference/controlFlowOptionalChain.symbols index 5354a1ef95b..be03f36c67d 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.symbols +++ b/tests/baselines/reference/controlFlowOptionalChain.symbols @@ -200,14 +200,14 @@ else { >o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) o3?.x; ->o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) +>o3?.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 41)) >o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 41)) o3.x; ->o3.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) +>o3.x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 41)) >o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) ->x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 19), Decl(controlFlowOptionalChain.ts, 58, 41)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 58, 41)) } o3; >o3 : Symbol(o3, Decl(controlFlowOptionalChain.ts, 58, 13)) diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index 9b22d5149d9..4e335fca6d2 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -223,16 +223,16 @@ if (o3?.x === 1) { } else { o3; ->o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>o3 : { x: 2; y: number; } | undefined o3?.x; >o3?.x : 2 | undefined ->o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>o3 : { x: 2; y: number; } | undefined >x : 2 | undefined o3.x; >o3.x : 2 ->o3 : { x: 1; y: string; } | { x: 2; y: number; } | undefined +>o3 : { x: 2; y: number; } | undefined >x : 2 } o3; diff --git a/tests/baselines/reference/controlFlowOptionalChain2.js b/tests/baselines/reference/controlFlowOptionalChain2.js new file mode 100644 index 00000000000..0f8478b90fa --- /dev/null +++ b/tests/baselines/reference/controlFlowOptionalChain2.js @@ -0,0 +1,30 @@ +//// [controlFlowOptionalChain2.ts] +type A = { + type: 'A'; + name: string; +} + +type B = { + type: 'B'; +} + +function funcTwo(arg: A | B | undefined) { + if (arg?.type === 'B') { + arg; // `B` + return; + } + + arg; + arg?.name; +} + + +//// [controlFlowOptionalChain2.js] +function funcTwo(arg) { + if ((arg === null || arg === void 0 ? void 0 : arg.type) === 'B') { + arg; // `B` + return; + } + arg; + arg === null || arg === void 0 ? void 0 : arg.name; +} diff --git a/tests/baselines/reference/controlFlowOptionalChain2.symbols b/tests/baselines/reference/controlFlowOptionalChain2.symbols new file mode 100644 index 00000000000..f30a8673e45 --- /dev/null +++ b/tests/baselines/reference/controlFlowOptionalChain2.symbols @@ -0,0 +1,44 @@ +=== tests/cases/conformance/controlFlow/controlFlowOptionalChain2.ts === +type A = { +>A : Symbol(A, Decl(controlFlowOptionalChain2.ts, 0, 0)) + + type: 'A'; +>type : Symbol(type, Decl(controlFlowOptionalChain2.ts, 0, 10)) + + name: string; +>name : Symbol(name, Decl(controlFlowOptionalChain2.ts, 1, 12)) +} + +type B = { +>B : Symbol(B, Decl(controlFlowOptionalChain2.ts, 3, 1)) + + type: 'B'; +>type : Symbol(type, Decl(controlFlowOptionalChain2.ts, 5, 10)) +} + +function funcTwo(arg: A | B | undefined) { +>funcTwo : Symbol(funcTwo, Decl(controlFlowOptionalChain2.ts, 7, 1)) +>arg : Symbol(arg, Decl(controlFlowOptionalChain2.ts, 9, 17)) +>A : Symbol(A, Decl(controlFlowOptionalChain2.ts, 0, 0)) +>B : Symbol(B, Decl(controlFlowOptionalChain2.ts, 3, 1)) + + if (arg?.type === 'B') { +>arg?.type : Symbol(type, Decl(controlFlowOptionalChain2.ts, 0, 10), Decl(controlFlowOptionalChain2.ts, 5, 10)) +>arg : Symbol(arg, Decl(controlFlowOptionalChain2.ts, 9, 17)) +>type : Symbol(type, Decl(controlFlowOptionalChain2.ts, 0, 10), Decl(controlFlowOptionalChain2.ts, 5, 10)) + + arg; // `B` +>arg : Symbol(arg, Decl(controlFlowOptionalChain2.ts, 9, 17)) + + return; + } + + arg; +>arg : Symbol(arg, Decl(controlFlowOptionalChain2.ts, 9, 17)) + + arg?.name; +>arg?.name : Symbol(name, Decl(controlFlowOptionalChain2.ts, 1, 12)) +>arg : Symbol(arg, Decl(controlFlowOptionalChain2.ts, 9, 17)) +>name : Symbol(name, Decl(controlFlowOptionalChain2.ts, 1, 12)) +} + diff --git a/tests/baselines/reference/controlFlowOptionalChain2.types b/tests/baselines/reference/controlFlowOptionalChain2.types new file mode 100644 index 00000000000..6e41060218e --- /dev/null +++ b/tests/baselines/reference/controlFlowOptionalChain2.types @@ -0,0 +1,44 @@ +=== tests/cases/conformance/controlFlow/controlFlowOptionalChain2.ts === +type A = { +>A : A + + type: 'A'; +>type : "A" + + name: string; +>name : string +} + +type B = { +>B : B + + type: 'B'; +>type : "B" +} + +function funcTwo(arg: A | B | undefined) { +>funcTwo : (arg: A | B | undefined) => void +>arg : A | B | undefined + + if (arg?.type === 'B') { +>arg?.type === 'B' : boolean +>arg?.type : "A" | "B" | undefined +>arg : A | B | undefined +>type : "A" | "B" | undefined +>'B' : "B" + + arg; // `B` +>arg : B + + return; + } + + arg; +>arg : A | undefined + + arg?.name; +>arg?.name : string | undefined +>arg : A | undefined +>name : string | undefined +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowOptionalChain2.ts b/tests/cases/conformance/controlFlow/controlFlowOptionalChain2.ts new file mode 100644 index 00000000000..a48d627ee32 --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowOptionalChain2.ts @@ -0,0 +1,20 @@ +// @strictNullChecks: true + +type A = { + type: 'A'; + name: string; +} + +type B = { + type: 'B'; +} + +function funcTwo(arg: A | B | undefined) { + if (arg?.type === 'B') { + arg; // `B` + return; + } + + arg; + arg?.name; +}