Skip to content

Commit 9636938

Browse files
committed
Widen widening literal types through compound-like assignments
1 parent 39a9ac8 commit 9636938

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
@@ -26208,7 +26208,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2620826208
if (!isReachableFlowNode(flow)) {
2620926209
return unreachableNeverType;
2621026210
}
26211-
if (getAssignmentTargetKind(node) === AssignmentKind.Compound) {
26211+
const assignmentKind = getAssignmentTargetKind(node);
26212+
if (assignmentKind === AssignmentKind.Compound) {
2621226213
const flowType = getTypeAtFlowNode(flow.antecedent);
2621326214
return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType));
2621426215
}
@@ -26219,10 +26220,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2621926220
const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow));
2622026221
return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType;
2622126222
}
26222-
if (declaredType.flags & TypeFlags.Union) {
26223-
return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow));
26223+
const t = assignmentKind === AssignmentKind.CompoundLike ? getBaseTypeOfLiteralType(declaredType) : declaredType;
26224+
if (t.flags & TypeFlags.Union) {
26225+
return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow));
2622426226
}
26225-
return declaredType;
26227+
return t;
2622626228
}
2622726229
// We didn't have a direct match. However, if the reference is a dotted name, this
2622826230
// may be an assignment to a left hand part of the reference. For example, for a
@@ -27570,6 +27572,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2757027572
if (assignmentKind === AssignmentKind.Definite) {
2757127573
return type;
2757227574
}
27575+
if (assignmentKind === AssignmentKind.CompoundLike) {
27576+
return getBaseTypeOfLiteralType(type);
27577+
}
2757327578
}
2757427579
else if (isAlias) {
2757527580
declaration = getDeclarationOfAliasSymbol(symbol);
@@ -31090,7 +31095,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3109031095
// assignment target, and the referenced property was declared as a variable, property,
3109131096
// accessor, or optional method.
3109231097
const assignmentKind = getAssignmentTargetKind(node);
31093-
if (assignmentKind === AssignmentKind.Definite) {
31098+
if (assignmentKind === AssignmentKind.Definite || assignmentKind === AssignmentKind.CompoundLike) {
3109431099
return removeMissingType(propType, !!(prop && prop.flags & SymbolFlags.Optional));
3109531100
}
3109631101
if (prop &&

src/compiler/factory/utilities.ts

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

1209-
function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher {
1209+
/** @internal */
1210+
export function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher {
12101211
return isAdditiveOperator(kind)
12111212
|| isMultiplicativeOperatorOrHigher(kind);
12121213
}

src/compiler/utilities.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ import {
230230
InterfaceDeclaration,
231231
InternalEmitFlags,
232232
isAccessor,
233+
isAdditiveOperatorOrHigher,
233234
isAnyDirectorySeparator,
234235
isArray,
235236
isArrayLiteralExpression,
@@ -4009,7 +4010,7 @@ export function hasTypeArguments(node: Node): node is HasTypeArguments {
40094010

40104011
/** @internal */
40114012
export const enum AssignmentKind {
4012-
None, Definite, Compound
4013+
None, Definite, Compound, CompoundLike
40134014
}
40144015

40154016
/** @internal */
@@ -4020,7 +4021,8 @@ export function getAssignmentTargetKind(node: Node): AssignmentKind {
40204021
case SyntaxKind.BinaryExpression:
40214022
const binaryOperator = (parent as BinaryExpression).operatorToken.kind;
40224023
return isAssignmentOperator(binaryOperator) && (parent as BinaryExpression).left === node ?
4023-
binaryOperator === SyntaxKind.EqualsToken || isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? AssignmentKind.Definite : AssignmentKind.Compound :
4024+
binaryOperator === SyntaxKind.EqualsToken ? (isCompoundLikeAssignment(parent as BinaryExpression) ? AssignmentKind.CompoundLike : AssignmentKind.Definite) :
4025+
isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? AssignmentKind.Definite : AssignmentKind.Compound :
40244026
AssignmentKind.None;
40254027
case SyntaxKind.PrefixUnaryExpression:
40264028
case SyntaxKind.PostfixUnaryExpression:
@@ -4066,6 +4068,11 @@ export function isAssignmentTarget(node: Node): boolean {
40664068
return getAssignmentTargetKind(node) !== AssignmentKind.None;
40674069
}
40684070

4071+
function isCompoundLikeAssignment(assignment: BinaryExpression): boolean {
4072+
const right = skipParentheses(assignment.right);
4073+
return right.kind === SyntaxKind.BinaryExpression && isAdditiveOperatorOrHigher((right as BinaryExpression).operatorToken.kind);
4074+
}
4075+
40694076
/** @internal */
40704077
export type NodeWithPossibleHoistedDeclaration =
40714078
| 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)