Skip to content

Commit dd9d5d1

Browse files
authored
Limit the narrow-to-fresh rule added with boolean literals to only boolean literals (#27274) (#27319)
* Remove the narrow-to-fresh rule added with boolean literals * Revert "Remove the narrow-to-fresh rule added with boolean literals" This reverts commit 9f96fe5. * Only apply freshness to booleans for now * Add largeish example from issue * Should be AND not OR * Add minor improvements suggested by @ahejelsberg * Reorder conditional a bit
1 parent d715d83 commit dd9d5d1

6 files changed

+576
-4
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8868,7 +8868,7 @@ namespace ts {
88688868
}
88698869
switch (unionReduction) {
88708870
case UnionReduction.Literal:
8871-
if (includes & TypeFlags.StringOrNumberLiteralOrUnique) {
8871+
if (includes & TypeFlags.StringOrNumberLiteralOrUnique | TypeFlags.BooleanLiteral) {
88728872
removeRedundantLiteralTypes(typeSet, includes);
88738873
}
88748874
break;
@@ -12933,8 +12933,8 @@ namespace ts {
1293312933
function getDefinitelyFalsyPartOfType(type: Type): Type {
1293412934
return type.flags & TypeFlags.String ? emptyStringType :
1293512935
type.flags & TypeFlags.Number ? zeroType :
12936-
type.flags & TypeFlags.Boolean || type === regularFalseType ? regularFalseType :
12937-
type === falseType ? falseType :
12936+
type === regularFalseType ||
12937+
type === falseType ||
1293812938
type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null) ||
1293912939
type.flags & TypeFlags.StringLiteral && (<LiteralType>type).value === "" ||
1294012940
type.flags & TypeFlags.NumberLiteral && (<LiteralType>type).value === 0 ? type :
@@ -14200,7 +14200,7 @@ namespace ts {
1420014200
return assignedType;
1420114201
}
1420214202
let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
14203-
if (assignedType.flags & (TypeFlags.FreshLiteral | TypeFlags.Literal)) {
14203+
if (assignedType.flags & TypeFlags.FreshLiteral && assignedType.flags & TypeFlags.BooleanLiteral) {
1420414204
reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types
1420514205
}
1420614206
// Our crude heuristic produces an invalid result in some cases: see GH#26130.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(37,5): error TS2322: Type '"y"' is not assignable to type '"x"'.
2+
tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,5): error TS2322: Type 'string[]' is not assignable to type 'XY[]'.
3+
Type 'string' is not assignable to type 'XY'.
4+
5+
6+
==== tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts (2 errors) ====
7+
function f1() {
8+
let b = true;
9+
let obj = { b };
10+
// Desired: OK
11+
// 3.0: OK
12+
// 3.1 as-is: OK
13+
// 3.1 minus widening propagation: error
14+
obj.b = false;
15+
}
16+
17+
function f2() {
18+
type Element = (string | false);
19+
type ElementOrArray = Element | Element[];
20+
let el: Element = null as any;
21+
let arr: Element[] = null as any;
22+
let elOrA: ElementOrArray = null as any;
23+
24+
// Desired/actual: All OK
25+
let a1: ElementOrArray = el;
26+
let a2: ElementOrArray = arr;
27+
let a3: ElementOrArray = [el];
28+
let a4: ElementOrArray = Array.isArray(elOrA) ? elOrA : [elOrA];
29+
30+
// Desired: OK
31+
// 3.0: Error
32+
// 3.1: OK
33+
let a5: ElementOrArray = [...Array.isArray(elOrA) ? elOrA : [elOrA]];
34+
}
35+
36+
function f3() {
37+
type XY = 'x' | 'y';
38+
const x: XY = 'x';
39+
let x2 = x;
40+
// Desired: OK (up for debate?)
41+
// 3.0: Error
42+
// 3.1 as-is: OK
43+
x2 = 'y';
44+
~~
45+
!!! error TS2322: Type '"y"' is not assignable to type '"x"'.
46+
47+
// Desired/actual: All OK
48+
let x3: XY = x;
49+
x3 = 'y';
50+
}
51+
52+
function f4() {
53+
const x: boolean = true;
54+
let x1 = x;
55+
// Desired: OK
56+
// 3.0: OK
57+
// 3.1: OK
58+
// 3.1 minus widening propagation: error
59+
x1 = false;
60+
}
61+
62+
function f5() {
63+
type XY = 'x' | 'y';
64+
let arr: XY[] = ['x'];
65+
arr = ['y'];
66+
// Desired: OK
67+
// Error in all extant branches
68+
arr = [...['y']];
69+
~~~
70+
!!! error TS2322: Type 'string[]' is not assignable to type 'XY[]'.
71+
!!! error TS2322: Type 'string' is not assignable to type 'XY'.
72+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//// [literalFreshnessPropagationOnNarrowing.ts]
2+
function f1() {
3+
let b = true;
4+
let obj = { b };
5+
// Desired: OK
6+
// 3.0: OK
7+
// 3.1 as-is: OK
8+
// 3.1 minus widening propagation: error
9+
obj.b = false;
10+
}
11+
12+
function f2() {
13+
type Element = (string | false);
14+
type ElementOrArray = Element | Element[];
15+
let el: Element = null as any;
16+
let arr: Element[] = null as any;
17+
let elOrA: ElementOrArray = null as any;
18+
19+
// Desired/actual: All OK
20+
let a1: ElementOrArray = el;
21+
let a2: ElementOrArray = arr;
22+
let a3: ElementOrArray = [el];
23+
let a4: ElementOrArray = Array.isArray(elOrA) ? elOrA : [elOrA];
24+
25+
// Desired: OK
26+
// 3.0: Error
27+
// 3.1: OK
28+
let a5: ElementOrArray = [...Array.isArray(elOrA) ? elOrA : [elOrA]];
29+
}
30+
31+
function f3() {
32+
type XY = 'x' | 'y';
33+
const x: XY = 'x';
34+
let x2 = x;
35+
// Desired: OK (up for debate?)
36+
// 3.0: Error
37+
// 3.1 as-is: OK
38+
x2 = 'y';
39+
40+
// Desired/actual: All OK
41+
let x3: XY = x;
42+
x3 = 'y';
43+
}
44+
45+
function f4() {
46+
const x: boolean = true;
47+
let x1 = x;
48+
// Desired: OK
49+
// 3.0: OK
50+
// 3.1: OK
51+
// 3.1 minus widening propagation: error
52+
x1 = false;
53+
}
54+
55+
function f5() {
56+
type XY = 'x' | 'y';
57+
let arr: XY[] = ['x'];
58+
arr = ['y'];
59+
// Desired: OK
60+
// Error in all extant branches
61+
arr = [...['y']];
62+
}
63+
64+
//// [literalFreshnessPropagationOnNarrowing.js]
65+
function f1() {
66+
var b = true;
67+
var obj = { b: b };
68+
// Desired: OK
69+
// 3.0: OK
70+
// 3.1 as-is: OK
71+
// 3.1 minus widening propagation: error
72+
obj.b = false;
73+
}
74+
function f2() {
75+
var el = null;
76+
var arr = null;
77+
var elOrA = null;
78+
// Desired/actual: All OK
79+
var a1 = el;
80+
var a2 = arr;
81+
var a3 = [el];
82+
var a4 = Array.isArray(elOrA) ? elOrA : [elOrA];
83+
// Desired: OK
84+
// 3.0: Error
85+
// 3.1: OK
86+
var a5 = (Array.isArray(elOrA) ? elOrA : [elOrA]).slice();
87+
}
88+
function f3() {
89+
var x = 'x';
90+
var x2 = x;
91+
// Desired: OK (up for debate?)
92+
// 3.0: Error
93+
// 3.1 as-is: OK
94+
x2 = 'y';
95+
// Desired/actual: All OK
96+
var x3 = x;
97+
x3 = 'y';
98+
}
99+
function f4() {
100+
var x = true;
101+
var x1 = x;
102+
// Desired: OK
103+
// 3.0: OK
104+
// 3.1: OK
105+
// 3.1 minus widening propagation: error
106+
x1 = false;
107+
}
108+
function f5() {
109+
var arr = ['x'];
110+
arr = ['y'];
111+
// Desired: OK
112+
// Error in all extant branches
113+
arr = ['y'];
114+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
=== tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts ===
2+
function f1() {
3+
>f1 : Symbol(f1, Decl(literalFreshnessPropagationOnNarrowing.ts, 0, 0))
4+
5+
let b = true;
6+
>b : Symbol(b, Decl(literalFreshnessPropagationOnNarrowing.ts, 1, 7))
7+
8+
let obj = { b };
9+
>obj : Symbol(obj, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 7))
10+
>b : Symbol(b, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 15))
11+
12+
// Desired: OK
13+
// 3.0: OK
14+
// 3.1 as-is: OK
15+
// 3.1 minus widening propagation: error
16+
obj.b = false;
17+
>obj.b : Symbol(b, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 15))
18+
>obj : Symbol(obj, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 7))
19+
>b : Symbol(b, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 15))
20+
}
21+
22+
function f2() {
23+
>f2 : Symbol(f2, Decl(literalFreshnessPropagationOnNarrowing.ts, 8, 1))
24+
25+
type Element = (string | false);
26+
>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15))
27+
28+
type ElementOrArray = Element | Element[];
29+
>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36))
30+
>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15))
31+
>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15))
32+
33+
let el: Element = null as any;
34+
>el : Symbol(el, Decl(literalFreshnessPropagationOnNarrowing.ts, 13, 7))
35+
>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15))
36+
37+
let arr: Element[] = null as any;
38+
>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 14, 7))
39+
>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15))
40+
41+
let elOrA: ElementOrArray = null as any;
42+
>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7))
43+
>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36))
44+
45+
// Desired/actual: All OK
46+
let a1: ElementOrArray = el;
47+
>a1 : Symbol(a1, Decl(literalFreshnessPropagationOnNarrowing.ts, 18, 7))
48+
>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36))
49+
>el : Symbol(el, Decl(literalFreshnessPropagationOnNarrowing.ts, 13, 7))
50+
51+
let a2: ElementOrArray = arr;
52+
>a2 : Symbol(a2, Decl(literalFreshnessPropagationOnNarrowing.ts, 19, 7))
53+
>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36))
54+
>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 14, 7))
55+
56+
let a3: ElementOrArray = [el];
57+
>a3 : Symbol(a3, Decl(literalFreshnessPropagationOnNarrowing.ts, 20, 7))
58+
>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36))
59+
>el : Symbol(el, Decl(literalFreshnessPropagationOnNarrowing.ts, 13, 7))
60+
61+
let a4: ElementOrArray = Array.isArray(elOrA) ? elOrA : [elOrA];
62+
>a4 : Symbol(a4, Decl(literalFreshnessPropagationOnNarrowing.ts, 21, 7))
63+
>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36))
64+
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
65+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
66+
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
67+
>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7))
68+
>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7))
69+
>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7))
70+
71+
// Desired: OK
72+
// 3.0: Error
73+
// 3.1: OK
74+
let a5: ElementOrArray = [...Array.isArray(elOrA) ? elOrA : [elOrA]];
75+
>a5 : Symbol(a5, Decl(literalFreshnessPropagationOnNarrowing.ts, 26, 7))
76+
>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36))
77+
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
78+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
79+
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
80+
>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7))
81+
>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7))
82+
>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7))
83+
}
84+
85+
function f3() {
86+
>f3 : Symbol(f3, Decl(literalFreshnessPropagationOnNarrowing.ts, 27, 1))
87+
88+
type XY = 'x' | 'y';
89+
>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 29, 15))
90+
91+
const x: XY = 'x';
92+
>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 31, 9))
93+
>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 29, 15))
94+
95+
let x2 = x;
96+
>x2 : Symbol(x2, Decl(literalFreshnessPropagationOnNarrowing.ts, 32, 7))
97+
>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 31, 9))
98+
99+
// Desired: OK (up for debate?)
100+
// 3.0: Error
101+
// 3.1 as-is: OK
102+
x2 = 'y';
103+
>x2 : Symbol(x2, Decl(literalFreshnessPropagationOnNarrowing.ts, 32, 7))
104+
105+
// Desired/actual: All OK
106+
let x3: XY = x;
107+
>x3 : Symbol(x3, Decl(literalFreshnessPropagationOnNarrowing.ts, 39, 7))
108+
>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 29, 15))
109+
>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 31, 9))
110+
111+
x3 = 'y';
112+
>x3 : Symbol(x3, Decl(literalFreshnessPropagationOnNarrowing.ts, 39, 7))
113+
}
114+
115+
function f4() {
116+
>f4 : Symbol(f4, Decl(literalFreshnessPropagationOnNarrowing.ts, 41, 1))
117+
118+
const x: boolean = true;
119+
>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 44, 9))
120+
121+
let x1 = x;
122+
>x1 : Symbol(x1, Decl(literalFreshnessPropagationOnNarrowing.ts, 45, 7))
123+
>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 44, 9))
124+
125+
// Desired: OK
126+
// 3.0: OK
127+
// 3.1: OK
128+
// 3.1 minus widening propagation: error
129+
x1 = false;
130+
>x1 : Symbol(x1, Decl(literalFreshnessPropagationOnNarrowing.ts, 45, 7))
131+
}
132+
133+
function f5() {
134+
>f5 : Symbol(f5, Decl(literalFreshnessPropagationOnNarrowing.ts, 51, 1))
135+
136+
type XY = 'x' | 'y';
137+
>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 53, 15))
138+
139+
let arr: XY[] = ['x'];
140+
>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 55, 7))
141+
>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 53, 15))
142+
143+
arr = ['y'];
144+
>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 55, 7))
145+
146+
// Desired: OK
147+
// Error in all extant branches
148+
arr = [...['y']];
149+
>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 55, 7))
150+
}

0 commit comments

Comments
 (0)