Skip to content

Commit 86e00f6

Browse files
author
Josh Goldberg
committed
Experiment: narrow falsy strings to '' and falsy numbers to 0
1 parent b36511a commit 86e00f6

8 files changed

+71
-31
lines changed

src/compiler/checker.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22560,6 +22560,28 @@ namespace ts {
2256022560
return filterType(type, t => (getTypeFacts(t) & include) !== 0);
2256122561
}
2256222562

22563+
function truthyTypeFilter(type: Type, truthiness: boolean): Type | undefined {
22564+
if (!truthiness) {
22565+
if (type.flags & TypeFlags.BigInt) {
22566+
return zeroBigIntType;
22567+
}
22568+
else if (type.flags & TypeFlags.Boolean) {
22569+
return falseType;
22570+
}
22571+
else if (type.flags & TypeFlags.String) {
22572+
return emptyStringType;
22573+
}
22574+
else if (type.flags & TypeFlags.Number) {
22575+
return zeroType;
22576+
}
22577+
}
22578+
22579+
const facts = getTypeFacts(type);
22580+
const include = truthiness ? TypeFacts.Truthy : TypeFacts.Falsy;
22581+
22582+
return ((facts & include) === 0) ? undefined : type;
22583+
}
22584+
2256322585
function getTypeWithDefault(type: Type, defaultExpression: Expression) {
2256422586
return defaultExpression ?
2256522587
getUnionType([getNonUndefinedType(type), getTypeOfExpression(defaultExpression)]) :
@@ -22790,10 +22812,19 @@ namespace ts {
2279022812
return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type);
2279122813
}
2279222814

22793-
function filterType(type: Type, f: (t: Type) => boolean): Type {
22815+
// TODO: make this actually performant
22816+
function compactMap<T>(array: T[], f: (item: T) => T | undefined) {
22817+
const result = compact(map(array, f));
22818+
22819+
return result.length === array.length && every(array, (item, i) => item === result[i])
22820+
? array
22821+
: result;
22822+
}
22823+
22824+
function filterAndNarrowType(type: Type, narrow: (t: Type) => Type | undefined) {
2279422825
if (type.flags & TypeFlags.Union) {
2279522826
const types = (type as UnionType).types;
22796-
const filtered = filter(types, f);
22827+
const filtered = compactMap(types, narrow);
2279722828
if (filtered === types) {
2279822829
return type;
2279922830
}
@@ -22806,7 +22837,7 @@ namespace ts {
2280622837
// Otherwise, if we have exactly one type left in the origin set, return that as the filtered type.
2280722838
// Otherwise, construct a new filtered origin type.
2280822839
const originTypes = (origin as UnionType).types;
22809-
const originFiltered = filter(originTypes, t => !!(t.flags & TypeFlags.Union) || f(t));
22840+
const originFiltered = compactMap(originTypes, t=> !!(t.flags & TypeFlags.Union) ? t : narrow(t));
2281022841
if (originTypes.length - originFiltered.length === types.length - filtered.length) {
2281122842
if (originFiltered.length === 1) {
2281222843
return originFiltered[0];
@@ -22816,7 +22847,16 @@ namespace ts {
2281622847
}
2281722848
return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin);
2281822849
}
22819-
return type.flags & TypeFlags.Never || f(type) ? type : neverType;
22850+
22851+
if (type.flags & TypeFlags.Never) {
22852+
return type;
22853+
}
22854+
22855+
return narrow(type) || neverType;
22856+
}
22857+
22858+
function filterType(type: Type, f: (t: Type) => boolean): Type {
22859+
return filterAndNarrowType(type, (t) => f(t) ? t : undefined);
2282022860
}
2282122861

2282222862
function removeType(type: Type, targetType: Type) {
@@ -23756,7 +23796,7 @@ namespace ts {
2375623796
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
2375723797
if (isMatchingReference(reference, expr)) {
2375823798
return type.flags & TypeFlags.Unknown && assumeTrue ? nonNullUnknownType :
23759-
getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
23799+
filterAndNarrowType(type, t => truthyTypeFilter(t, assumeTrue));
2376023800
}
2376123801
if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
2376223802
type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);

tests/baselines/reference/classStaticBlock8.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ function foo (v: number) {
111111
>4 : 4
112112
}
113113
switch (v) {
114-
>v : number
114+
>v : 0 | 1 | 3
115115

116116
default: break; // valid
117117
}

tests/baselines/reference/controlFlowBinaryAndExpression.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let cond: boolean;
1717
>0 : 0
1818

1919
x; // string | number
20-
>x : string | number
20+
>x : number | ""
2121

2222
x = "";
2323
>x = "" : ""

tests/baselines/reference/controlFlowDoWhileStatement.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function d() {
102102
>length : number
103103

104104
x; // number
105-
>x : number
105+
>x : 0
106106
}
107107
function e() {
108108
>e : () => void

tests/baselines/reference/controlFlowTruthiness.types

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function f1() {
1818
}
1919
else {
2020
x; // string | undefined
21-
>x : string | undefined
21+
>x : "" | undefined
2222
}
2323
}
2424

@@ -42,7 +42,7 @@ function f2() {
4242
}
4343
else {
4444
x; // string | undefined
45-
>x : string | undefined
45+
>x : "" | undefined
4646
}
4747
}
4848

@@ -63,7 +63,7 @@ function f3() {
6363
}
6464
else {
6565
x; // string | undefined
66-
>x : string | undefined
66+
>x : "" | undefined
6767
}
6868
}
6969

@@ -82,7 +82,7 @@ function f4() {
8282
>foo : () => string | undefined
8383

8484
x; // string | undefined
85-
>x : string | undefined
85+
>x : "" | undefined
8686
}
8787
else {
8888
x; // string
@@ -115,10 +115,10 @@ function f5() {
115115
}
116116
else {
117117
x; // string | undefined
118-
>x : string | undefined
118+
>x : "" | undefined
119119

120120
y; // string | undefined
121-
>y : string | undefined
121+
>y : "" | undefined
122122
}
123123
}
124124

@@ -153,7 +153,7 @@ function f6() {
153153
>x : string | undefined
154154

155155
y; // string | undefined
156-
>y : string | undefined
156+
>y : "" | undefined
157157
}
158158
}
159159

tests/baselines/reference/expr.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ function f() {
208208
n||n;
209209
>n||n : number
210210
>n : number
211-
>n : number
211+
>n : 0
212212

213213
n||e;
214214
>n||e : number
@@ -238,7 +238,7 @@ function f() {
238238
s||s;
239239
>s||s : string
240240
>s : string
241-
>s : string
241+
>s : ""
242242

243243
s||e;
244244
>s||e : string | E

tests/baselines/reference/logicalOrOperatorWithEveryType.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ var rc3 = a3 || a3; // number || number is number
171171
>rc3 : number
172172
>a3 || a3 : number
173173
>a3 : number
174-
>a3 : number
174+
>a3 : 0
175175

176176
var rc4 = a4 || a3; // string || number is string | number
177177
>rc4 : string | number
@@ -237,7 +237,7 @@ var rd4 = a4 || a4; // string || string is string
237237
>rd4 : string
238238
>a4 || a4 : string
239239
>a4 : string
240-
>a4 : string
240+
>a4 : ""
241241

242242
var rd5 = a5 || a4; // void || string is void | string
243243
>rd5 : string

tests/baselines/reference/typeGuardsInRightOperandOfOrOrOperator.types

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,42 +19,42 @@ function foo(x: number | string) {
1919
>10 : 10
2020
}
2121
function foo2(x: number | string) {
22-
>foo2 : (x: number | string) => number | true
22+
>foo2 : (x: number | string) => true | 0 | 10
2323
>x : string | number
2424

2525
// modify x in right hand operand
2626
return typeof x !== "string" || ((x = 10) || x); // string | number
27-
>typeof x !== "string" || ((x = 10) || x) : number | true
27+
>typeof x !== "string" || ((x = 10) || x) : true | 0 | 10
2828
>typeof x !== "string" : boolean
2929
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
3030
>x : string | number
3131
>"string" : "string"
32-
>((x = 10) || x) : number
33-
>(x = 10) || x : number
32+
>((x = 10) || x) : 0 | 10
33+
>(x = 10) || x : 0 | 10
3434
>(x = 10) : 10
3535
>x = 10 : 10
3636
>x : string | number
3737
>10 : 10
38-
>x : number
38+
>x : 0
3939
}
4040
function foo3(x: number | string) {
41-
>foo3 : (x: number | string) => string | true
41+
>foo3 : (x: number | string) => true | "" | "hello"
4242
>x : string | number
4343

4444
// modify x in right hand operand with string type itself
4545
return typeof x !== "string" || ((x = "hello") || x); // string | number
46-
>typeof x !== "string" || ((x = "hello") || x) : string | true
46+
>typeof x !== "string" || ((x = "hello") || x) : true | "" | "hello"
4747
>typeof x !== "string" : boolean
4848
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
4949
>x : string | number
5050
>"string" : "string"
51-
>((x = "hello") || x) : string
52-
>(x = "hello") || x : string
51+
>((x = "hello") || x) : "" | "hello"
52+
>(x = "hello") || x : "" | "hello"
5353
>(x = "hello") : "hello"
5454
>x = "hello" : "hello"
5555
>x : string | number
5656
>"hello" : "hello"
57-
>x : string
57+
>x : ""
5858
}
5959
function foo4(x: number | string | boolean) {
6060
>foo4 : (x: number | string | boolean) => boolean
@@ -103,7 +103,7 @@ function foo5(x: number | string | boolean) {
103103
>typeof x === "number" // number | boolean || x : boolean
104104
>typeof x === "number" : boolean
105105
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
106-
>x : number | boolean
106+
>x : 0 | boolean
107107
>"number" : "number"
108108

109109
|| x)); // boolean
@@ -168,7 +168,7 @@ function foo7(x: number | string | boolean) {
168168
>typeof x === "number" // change value of x ? ((x = 10) && x.toString()) // number | boolean | string // do not change value : ((y = x) && x.toString()) : string
169169
>typeof x === "number" : boolean
170170
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
171-
>x : number | boolean
171+
>x : 0 | boolean
172172
>"number" : "number"
173173

174174
// change value of x

0 commit comments

Comments
 (0)