Skip to content

Commit d18dc2c

Browse files
committed
Widen widening literal types through compound-like assignments
1 parent 9c9d4b0 commit d18dc2c

File tree

6 files changed

+289
-8
lines changed

6 files changed

+289
-8
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26065,7 +26065,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2606526065
if (!isReachableFlowNode(flow)) {
2606626066
return unreachableNeverType;
2606726067
}
26068-
if (getAssignmentTargetKind(node) === AssignmentKind.Compound) {
26068+
const assignmentKind = getAssignmentTargetKind(node);
26069+
if (assignmentKind === AssignmentKind.Compound) {
2606926070
const flowType = getTypeAtFlowNode(flow.antecedent);
2607026071
return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType));
2607126072
}
@@ -26076,10 +26077,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2607626077
const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow));
2607726078
return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType;
2607826079
}
26079-
if (declaredType.flags & TypeFlags.Union) {
26080-
return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow));
26080+
const t = assignmentKind === AssignmentKind.CompoundLike ? getBaseTypeOfLiteralType(declaredType) : declaredType;
26081+
if (t.flags & TypeFlags.Union) {
26082+
return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow));
2608126083
}
26082-
return declaredType;
26084+
return t;
2608326085
}
2608426086
// We didn't have a direct match. However, if the reference is a dotted name, this
2608526087
// may be an assignment to a left hand part of the reference. For example, for a
@@ -27424,6 +27426,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2742427426
if (assignmentKind === AssignmentKind.Definite) {
2742527427
return type;
2742627428
}
27429+
if (assignmentKind === AssignmentKind.CompoundLike) {
27430+
return getBaseTypeOfLiteralType(type);
27431+
}
2742727432
}
2742827433
else if (isAlias) {
2742927434
declaration = getDeclarationOfAliasSymbol(symbol);
@@ -30937,7 +30942,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3093730942
// assignment target, and the referenced property was declared as a variable, property,
3093830943
// accessor, or optional method.
3093930944
const assignmentKind = getAssignmentTargetKind(node);
30940-
if (assignmentKind === AssignmentKind.Definite) {
30945+
if (assignmentKind === AssignmentKind.Definite || assignmentKind === AssignmentKind.CompoundLike) {
3094130946
return removeMissingType(propType, !!(prop && prop.flags & SymbolFlags.Optional));
3094230947
}
3094330948
if (prop &&

src/compiler/factory/utilities.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1175,7 +1175,8 @@ function isAdditiveOperator(kind: SyntaxKind): kind is AdditiveOperator {
11751175
|| kind === SyntaxKind.MinusToken;
11761176
}
11771177

1178-
function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher {
1178+
/** @internal */
1179+
export function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher {
11791180
return isAdditiveOperator(kind)
11801181
|| isMultiplicativeOperatorOrHigher(kind);
11811182
}

src/compiler/utilities.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ import {
225225
insertSorted,
226226
InterfaceDeclaration,
227227
isAccessor,
228+
isAdditiveOperatorOrHigher,
228229
isAnyDirectorySeparator,
229230
isArray,
230231
isArrayLiteralExpression,
@@ -3899,7 +3900,7 @@ export function hasTypeArguments(node: Node): node is HasTypeArguments {
38993900

39003901
/** @internal */
39013902
export const enum AssignmentKind {
3902-
None, Definite, Compound
3903+
None, Definite, Compound, CompoundLike
39033904
}
39043905

39053906
/** @internal */
@@ -3910,7 +3911,8 @@ export function getAssignmentTargetKind(node: Node): AssignmentKind {
39103911
case SyntaxKind.BinaryExpression:
39113912
const binaryOperator = (parent as BinaryExpression).operatorToken.kind;
39123913
return isAssignmentOperator(binaryOperator) && (parent as BinaryExpression).left === node ?
3913-
binaryOperator === SyntaxKind.EqualsToken || isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? AssignmentKind.Definite : AssignmentKind.Compound :
3914+
binaryOperator === SyntaxKind.EqualsToken ? (isCompoundLikeAssignment(parent as BinaryExpression) ? AssignmentKind.CompoundLike : AssignmentKind.Definite) :
3915+
isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? AssignmentKind.Definite : AssignmentKind.Compound :
39143916
AssignmentKind.None;
39153917
case SyntaxKind.PrefixUnaryExpression:
39163918
case SyntaxKind.PostfixUnaryExpression:
@@ -3956,6 +3958,11 @@ export function isAssignmentTarget(node: Node): boolean {
39563958
return getAssignmentTargetKind(node) !== AssignmentKind.None;
39573959
}
39583960

3961+
function isCompoundLikeAssignment(assignment: BinaryExpression): boolean {
3962+
const right = skipParentheses(assignment.right);
3963+
return right.kind === SyntaxKind.BinaryExpression && isAdditiveOperatorOrHigher((right as BinaryExpression).operatorToken.kind);
3964+
}
3965+
39593966
/** @internal */
39603967
export type NodeWithPossibleHoistedDeclaration =
39613968
| Block
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
=== tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts ===
2+
// repro from #13865
3+
4+
const empty: "" = "";
5+
>empty : Symbol(empty, Decl(literalWideningWithCompoundLikeAssignments.ts, 2, 5))
6+
7+
let foo = empty;
8+
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
9+
>empty : Symbol(empty, Decl(literalWideningWithCompoundLikeAssignments.ts, 2, 5))
10+
11+
foo = foo + "bar"
12+
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
13+
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
14+
15+
foo // string
16+
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
17+
18+
declare const numLiteral: 0;
19+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
20+
21+
let t1 = numLiteral;
22+
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
23+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
24+
25+
t1 = t1 + 42
26+
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
27+
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
28+
29+
t1 // number
30+
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
31+
32+
let t2 = numLiteral;
33+
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
34+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
35+
36+
t2 = t2 - 42
37+
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
38+
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
39+
40+
t2 // number
41+
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
42+
43+
let t3 = numLiteral;
44+
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
45+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
46+
47+
t3 = t3 * 42
48+
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
49+
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
50+
51+
t3 // number
52+
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
53+
54+
let t4 = numLiteral;
55+
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
56+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
57+
58+
t4 = t4 ** 42
59+
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
60+
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
61+
62+
t4 // number
63+
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
64+
65+
let t5 = numLiteral;
66+
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
67+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
68+
69+
t5 = t5 / 42
70+
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
71+
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
72+
73+
t5 // number
74+
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
75+
76+
let t6 = numLiteral;
77+
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
78+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
79+
80+
t6 = t6 % 42
81+
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
82+
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
83+
84+
t6 // number
85+
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
86+
87+
88+
declare const literalUnion: "a" | 0;
89+
>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 34, 13))
90+
91+
let t7 = literalUnion;
92+
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3))
93+
>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 34, 13))
94+
95+
t7 = t7 + 'b'
96+
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3))
97+
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3))
98+
99+
t7 // string
100+
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3))
101+
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
=== tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts ===
2+
// repro from #13865
3+
4+
const empty: "" = "";
5+
>empty : ""
6+
>"" : ""
7+
8+
let foo = empty;
9+
>foo : ""
10+
>empty : ""
11+
12+
foo = foo + "bar"
13+
>foo = foo + "bar" : string
14+
>foo : string
15+
>foo + "bar" : string
16+
>foo : ""
17+
>"bar" : "bar"
18+
19+
foo // string
20+
>foo : string
21+
22+
declare const numLiteral: 0;
23+
>numLiteral : 0
24+
25+
let t1 = numLiteral;
26+
>t1 : 0
27+
>numLiteral : 0
28+
29+
t1 = t1 + 42
30+
>t1 = t1 + 42 : number
31+
>t1 : number
32+
>t1 + 42 : number
33+
>t1 : 0
34+
>42 : 42
35+
36+
t1 // number
37+
>t1 : number
38+
39+
let t2 = numLiteral;
40+
>t2 : 0
41+
>numLiteral : 0
42+
43+
t2 = t2 - 42
44+
>t2 = t2 - 42 : number
45+
>t2 : number
46+
>t2 - 42 : number
47+
>t2 : 0
48+
>42 : 42
49+
50+
t2 // number
51+
>t2 : number
52+
53+
let t3 = numLiteral;
54+
>t3 : 0
55+
>numLiteral : 0
56+
57+
t3 = t3 * 42
58+
>t3 = t3 * 42 : number
59+
>t3 : number
60+
>t3 * 42 : number
61+
>t3 : 0
62+
>42 : 42
63+
64+
t3 // number
65+
>t3 : number
66+
67+
let t4 = numLiteral;
68+
>t4 : 0
69+
>numLiteral : 0
70+
71+
t4 = t4 ** 42
72+
>t4 = t4 ** 42 : number
73+
>t4 : number
74+
>t4 ** 42 : number
75+
>t4 : 0
76+
>42 : 42
77+
78+
t4 // number
79+
>t4 : number
80+
81+
let t5 = numLiteral;
82+
>t5 : 0
83+
>numLiteral : 0
84+
85+
t5 = t5 / 42
86+
>t5 = t5 / 42 : number
87+
>t5 : number
88+
>t5 / 42 : number
89+
>t5 : 0
90+
>42 : 42
91+
92+
t5 // number
93+
>t5 : number
94+
95+
let t6 = numLiteral;
96+
>t6 : 0
97+
>numLiteral : 0
98+
99+
t6 = t6 % 42
100+
>t6 = t6 % 42 : number
101+
>t6 : number
102+
>t6 % 42 : number
103+
>t6 : 0
104+
>42 : 42
105+
106+
t6 // number
107+
>t6 : number
108+
109+
110+
declare const literalUnion: "a" | 0;
111+
>literalUnion : 0 | "a"
112+
113+
let t7 = literalUnion;
114+
>t7 : 0 | "a"
115+
>literalUnion : 0 | "a"
116+
117+
t7 = t7 + 'b'
118+
>t7 = t7 + 'b' : string
119+
>t7 : string | number
120+
>t7 + 'b' : string
121+
>t7 : 0 | "a"
122+
>'b' : "b"
123+
124+
t7 // string
125+
>t7 : string
126+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// repro from #13865
5+
6+
const empty: "" = "";
7+
let foo = empty;
8+
foo = foo + "bar"
9+
foo // string
10+
11+
declare const numLiteral: 0;
12+
13+
let t1 = numLiteral;
14+
t1 = t1 + 42
15+
t1 // number
16+
17+
let t2 = numLiteral;
18+
t2 = t2 - 42
19+
t2 // number
20+
21+
let t3 = numLiteral;
22+
t3 = t3 * 42
23+
t3 // number
24+
25+
let t4 = numLiteral;
26+
t4 = t4 ** 42
27+
t4 // number
28+
29+
let t5 = numLiteral;
30+
t5 = t5 / 42
31+
t5 // number
32+
33+
let t6 = numLiteral;
34+
t6 = t6 % 42
35+
t6 // number
36+
37+
38+
declare const literalUnion: "a" | 0;
39+
let t7 = literalUnion;
40+
t7 = t7 + 'b'
41+
t7 // string

0 commit comments

Comments
 (0)