Skip to content

Commit 069f73d

Browse files
committed
Change type narrowing for optional properties
1 parent a5d6be1 commit 069f73d

8 files changed

+517
-360
lines changed

src/compiler/checker.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -12722,10 +12722,30 @@ namespace ts {
1272212722
return type;
1272312723
}
1272412724

12725+
function isTypePresencePossible(type: Type, propName: string, shouldHaveProperty: boolean) {
12726+
const prop = getPropertyOfType(type, propName);
12727+
if (!prop) {
12728+
// if there is NO property:
12729+
// - but we assume type SHOULD have it then presence of object of following type IS NOT possible
12730+
// - and we assume type SHOULD NOT have it then presence of object of following type IS possible
12731+
return !shouldHaveProperty;
12732+
} else if (prop.flags & SymbolFlags.Optional) {
12733+
// if there is an optional property:
12734+
// - and we assume type SHOULD have it then presence of object of following type IS possible
12735+
// - but assume type SHOULD NOT have it then presence of object of following type IS still possible
12736+
return true;
12737+
} else /* if (prop.flags & SymbolFlags.Required) */ {
12738+
// if there is a required property:
12739+
// - and we assume type SHOULD have it then presence of object of following type IS possible
12740+
// - but we assume type SHOULD NOT have it then presence of object of following type IS NOT possible
12741+
return shouldHaveProperty;
12742+
}
12743+
}
12744+
1272512745
function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) {
1272612746
if ((type.flags & (TypeFlags.Union | TypeFlags.Object)) || (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType)) {
1272712747
const propName = literal.text;
12728-
return filterType(type, t => !!getPropertyOfType(t, propName) === assumeTrue);
12748+
return filterType(type, t => isTypePresencePossible(t, propName, /* shouldHaveProperty */ assumeTrue));
1272912749
}
1273012750
return type;
1273112751
}

tests/baselines/reference/inKeywordTypeguard.errors.txt

+17-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(6,11): error TS2339: Property 'b' doe
22
tests/cases/compiler/inKeywordTypeguard.ts(8,11): error TS2339: Property 'a' does not exist on type 'B'.
33
tests/cases/compiler/inKeywordTypeguard.ts(14,11): error TS2339: Property 'b' does not exist on type 'A'.
44
tests/cases/compiler/inKeywordTypeguard.ts(16,11): error TS2339: Property 'a' does not exist on type 'B'.
5+
tests/cases/compiler/inKeywordTypeguard.ts(27,11): error TS2339: Property 'b' does not exist on type 'AWithOptionalProp | BWithOptionalProp'.
6+
Property 'b' does not exist on type 'AWithOptionalProp'.
57
tests/cases/compiler/inKeywordTypeguard.ts(42,11): error TS2339: Property 'b' does not exist on type 'AWithMethod'.
68
tests/cases/compiler/inKeywordTypeguard.ts(49,11): error TS2339: Property 'a' does not exist on type 'never'.
79
tests/cases/compiler/inKeywordTypeguard.ts(50,11): error TS2339: Property 'b' does not exist on type 'never'.
@@ -19,7 +21,7 @@ tests/cases/compiler/inKeywordTypeguard.ts(84,39): error TS2339: Property 'a' do
1921
tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' does not exist on type 'never'.
2022

2123

22-
==== tests/cases/compiler/inKeywordTypeguard.ts (16 errors) ====
24+
==== tests/cases/compiler/inKeywordTypeguard.ts (17 errors) ====
2325
class A { a: string; }
2426
class B { b: string; }
2527

@@ -47,23 +49,26 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do
4749
}
4850
}
4951

50-
class AOpt { a?: string }
51-
class BOpn { b?: string }
52+
class AWithOptionalProp { a?: string; }
53+
class BWithOptionalProp { b?: string; }
5254

53-
function positiveTestClassesWithOptionalProperties(x: AOpt | BOpn) {
55+
function positiveTestClassesWithOptionalProperties(x: AWithOptionalProp | BWithOptionalProp) {
5456
if ("a" in x) {
5557
x.a = "1";
5658
} else {
5759
x.b = "1";
60+
~
61+
!!! error TS2339: Property 'b' does not exist on type 'AWithOptionalProp | BWithOptionalProp'.
62+
!!! error TS2339: Property 'b' does not exist on type 'AWithOptionalProp'.
5863
}
5964
}
6065

6166
class AWithMethod {
62-
a(): string { return "" }
67+
a(): string { return ""; }
6368
}
6469

6570
class BWithMethod {
66-
b(): string { return "" }
71+
b(): string { return ""; }
6772
}
6873

6974
function negativeTestClassesWithMembers(x: AWithMethod | BWithMethod) {
@@ -96,8 +101,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do
96101
}
97102
}
98103

99-
class C { a: string }
100-
class D { a: string }
104+
class C { a: string; }
105+
class D { a: string; }
101106

102107
function negativeMultipleClassesTest(x: A | B | C | D) {
103108
if ("a" in x) {
@@ -112,9 +117,9 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do
112117
}
113118
}
114119

115-
class ClassWithProp { prop: A | B }
120+
class ClassWithUnionProp { prop: A | B }
116121

117-
function negativePropTest(x: ClassWithProp) {
122+
function negativePropTest(x: ClassWithUnionProp) {
118123
if ("a" in x.prop) {
119124
let y: string = x.prop.b;
120125
~
@@ -129,7 +134,7 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do
129134
class NegativeClassTest {
130135
protected prop: A | B;
131136
inThis() {
132-
if ('a' in this.prop) {
137+
if ("a" in this.prop) {
133138
let z: number = this.prop.b;
134139
~
135140
!!! error TS2339: Property 'b' does not exist on type 'A'.
@@ -144,7 +149,7 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do
144149
class UnreachableCodeDetection {
145150
a: string;
146151
inThis() {
147-
if ('a' in this) {
152+
if ("a" in this) {
148153
} else {
149154
let y = this.a;
150155
~

tests/baselines/reference/inKeywordTypeguard.js

+22-22
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ function positiveClassesTest(x: A | B) {
1818
}
1919
}
2020

21-
class AOpt { a?: string }
22-
class BOpn { b?: string }
21+
class AWithOptionalProp { a?: string; }
22+
class BWithOptionalProp { b?: string; }
2323

24-
function positiveTestClassesWithOptionalProperties(x: AOpt | BOpn) {
24+
function positiveTestClassesWithOptionalProperties(x: AWithOptionalProp | BWithOptionalProp) {
2525
if ("a" in x) {
2626
x.a = "1";
2727
} else {
@@ -30,11 +30,11 @@ function positiveTestClassesWithOptionalProperties(x: AOpt | BOpn) {
3030
}
3131

3232
class AWithMethod {
33-
a(): string { return "" }
33+
a(): string { return ""; }
3434
}
3535

3636
class BWithMethod {
37-
b(): string { return "" }
37+
b(): string { return ""; }
3838
}
3939

4040
function negativeTestClassesWithMembers(x: AWithMethod | BWithMethod) {
@@ -55,8 +55,8 @@ function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWit
5555
}
5656
}
5757

58-
class C { a: string }
59-
class D { a: string }
58+
class C { a: string; }
59+
class D { a: string; }
6060

6161
function negativeMultipleClassesTest(x: A | B | C | D) {
6262
if ("a" in x) {
@@ -66,9 +66,9 @@ function negativeMultipleClassesTest(x: A | B | C | D) {
6666
}
6767
}
6868

69-
class ClassWithProp { prop: A | B }
69+
class ClassWithUnionProp { prop: A | B }
7070

71-
function negativePropTest(x: ClassWithProp) {
71+
function negativePropTest(x: ClassWithUnionProp) {
7272
if ("a" in x.prop) {
7373
let y: string = x.prop.b;
7474
} else {
@@ -79,7 +79,7 @@ function negativePropTest(x: ClassWithProp) {
7979
class NegativeClassTest {
8080
protected prop: A | B;
8181
inThis() {
82-
if ('a' in this.prop) {
82+
if ("a" in this.prop) {
8383
let z: number = this.prop.b;
8484
} else {
8585
let y: string = this.prop.a;
@@ -90,7 +90,7 @@ class NegativeClassTest {
9090
class UnreachableCodeDetection {
9191
a: string;
9292
inThis() {
93-
if ('a' in this) {
93+
if ("a" in this) {
9494
} else {
9595
let y = this.a;
9696
}
@@ -124,15 +124,15 @@ function positiveClassesTest(x) {
124124
x.a = "1";
125125
}
126126
}
127-
var AOpt = (function () {
128-
function AOpt() {
127+
var AWithOptionalProp = (function () {
128+
function AWithOptionalProp() {
129129
}
130-
return AOpt;
130+
return AWithOptionalProp;
131131
}());
132-
var BOpn = (function () {
133-
function BOpn() {
132+
var BWithOptionalProp = (function () {
133+
function BWithOptionalProp() {
134134
}
135-
return BOpn;
135+
return BWithOptionalProp;
136136
}());
137137
function positiveTestClassesWithOptionalProperties(x) {
138138
if ("a" in x) {
@@ -190,10 +190,10 @@ function negativeMultipleClassesTest(x) {
190190
x.a = "1";
191191
}
192192
}
193-
var ClassWithProp = (function () {
194-
function ClassWithProp() {
193+
var ClassWithUnionProp = (function () {
194+
function ClassWithUnionProp() {
195195
}
196-
return ClassWithProp;
196+
return ClassWithUnionProp;
197197
}());
198198
function negativePropTest(x) {
199199
if ("a" in x.prop) {
@@ -207,7 +207,7 @@ var NegativeClassTest = (function () {
207207
function NegativeClassTest() {
208208
}
209209
NegativeClassTest.prototype.inThis = function () {
210-
if ('a' in this.prop) {
210+
if ("a" in this.prop) {
211211
var z = this.prop.b;
212212
}
213213
else {
@@ -220,7 +220,7 @@ var UnreachableCodeDetection = (function () {
220220
function UnreachableCodeDetection() {
221221
}
222222
UnreachableCodeDetection.prototype.inThis = function () {
223-
if ('a' in this) {
223+
if ("a" in this) {
224224
}
225225
else {
226226
var y = this.a;

0 commit comments

Comments
 (0)