Skip to content

Commit cac899d

Browse files
authored
Widen widening literal types through compound-like assignments (#52493)
1 parent b8b0d26 commit cac899d

6 files changed

+421
-26
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ import {
554554
isImportOrExportSpecifier,
555555
isImportSpecifier,
556556
isImportTypeNode,
557+
isInCompoundLikeAssignment,
557558
isIndexedAccessTypeNode,
558559
isInExpressionContext,
559560
isInfinityOrNaNString,
@@ -26729,10 +26730,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2672926730
const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow));
2673026731
return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType;
2673126732
}
26732-
if (declaredType.flags & TypeFlags.Union) {
26733-
return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow));
26733+
const t = isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(declaredType) : declaredType;
26734+
if (t.flags & TypeFlags.Union) {
26735+
return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow));
2673426736
}
26735-
return declaredType;
26737+
return t;
2673626738
}
2673726739
// We didn't have a direct match. However, if the reference is a dotted name, this
2673826740
// may be an assignment to a left hand part of the reference. For example, for a
@@ -28079,7 +28081,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2807928081
// entities we simply return the declared type.
2808028082
if (localOrExportSymbol.flags & SymbolFlags.Variable) {
2808128083
if (assignmentKind === AssignmentKind.Definite) {
28082-
return type;
28084+
return isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type;
2808328085
}
2808428086
}
2808528087
else if (isAlias) {

src/compiler/factory/utilities.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1222,7 +1222,8 @@ function isShiftOperator(kind: SyntaxKind): kind is ShiftOperator {
12221222
|| kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
12231223
}
12241224

1225-
function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher {
1225+
/** @internal */
1226+
export function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher {
12261227
return isShiftOperator(kind)
12271228
|| isAdditiveOperatorOrHigher(kind);
12281229
}

src/compiler/utilities.ts

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,6 @@ import {
152152
forEachChild,
153153
forEachChildRecursively,
154154
ForInOrOfStatement,
155-
ForInStatement,
156-
ForOfStatement,
157155
ForStatement,
158156
FunctionBody,
159157
FunctionDeclaration,
@@ -331,6 +329,7 @@ import {
331329
isQualifiedName,
332330
isRootedDiskPath,
333331
isSetAccessorDeclaration,
332+
isShiftOperatorOrHigher,
334333
isShorthandPropertyAssignment,
335334
isSourceFile,
336335
isString,
@@ -3418,9 +3417,9 @@ export function isInExpressionContext(node: Node): boolean {
34183417
forStatement.incrementor === node;
34193418
case SyntaxKind.ForInStatement:
34203419
case SyntaxKind.ForOfStatement:
3421-
const forInStatement = parent as ForInStatement | ForOfStatement;
3422-
return (forInStatement.initializer === node && forInStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) ||
3423-
forInStatement.expression === node;
3420+
const forInOrOfStatement = parent as ForInOrOfStatement;
3421+
return (forInOrOfStatement.initializer === node && forInOrOfStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) ||
3422+
forInOrOfStatement.expression === node;
34243423
case SyntaxKind.TypeAssertionExpression:
34253424
case SyntaxKind.AsExpression:
34263425
return node === (parent as AssertionExpression).expression;
@@ -4468,23 +4467,29 @@ export const enum AssignmentKind {
44684467
None, Definite, Compound
44694468
}
44704469

4471-
/** @internal */
4472-
export function getAssignmentTargetKind(node: Node): AssignmentKind {
4470+
type AssignmentTarget =
4471+
| BinaryExpression
4472+
| PrefixUnaryExpression
4473+
| PostfixUnaryExpression
4474+
| ForInOrOfStatement;
4475+
4476+
function getAssignmentTarget(node: Node): AssignmentTarget | undefined {
44734477
let parent = node.parent;
44744478
while (true) {
44754479
switch (parent.kind) {
44764480
case SyntaxKind.BinaryExpression:
4477-
const binaryOperator = (parent as BinaryExpression).operatorToken.kind;
4478-
return isAssignmentOperator(binaryOperator) && (parent as BinaryExpression).left === node ?
4479-
binaryOperator === SyntaxKind.EqualsToken || isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? AssignmentKind.Definite : AssignmentKind.Compound :
4480-
AssignmentKind.None;
4481+
const binaryExpression = parent as BinaryExpression;
4482+
const binaryOperator = binaryExpression.operatorToken.kind;
4483+
return isAssignmentOperator(binaryOperator) && binaryExpression.left === node ? binaryExpression : undefined;
44814484
case SyntaxKind.PrefixUnaryExpression:
44824485
case SyntaxKind.PostfixUnaryExpression:
4483-
const unaryOperator = (parent as PrefixUnaryExpression | PostfixUnaryExpression).operator;
4484-
return unaryOperator === SyntaxKind.PlusPlusToken || unaryOperator === SyntaxKind.MinusMinusToken ? AssignmentKind.Compound : AssignmentKind.None;
4486+
const unaryExpression = (parent as PrefixUnaryExpression | PostfixUnaryExpression);
4487+
const unaryOperator = unaryExpression.operator;
4488+
return unaryOperator === SyntaxKind.PlusPlusToken || unaryOperator === SyntaxKind.MinusMinusToken ? unaryExpression : undefined;
44854489
case SyntaxKind.ForInStatement:
44864490
case SyntaxKind.ForOfStatement:
4487-
return (parent as ForInOrOfStatement).initializer === node ? AssignmentKind.Definite : AssignmentKind.None;
4491+
const forInOrOfStatement = parent as ForInOrOfStatement;
4492+
return forInOrOfStatement.initializer === node ? forInOrOfStatement : undefined;
44884493
case SyntaxKind.ParenthesizedExpression:
44894494
case SyntaxKind.ArrayLiteralExpression:
44904495
case SyntaxKind.SpreadElement:
@@ -4496,30 +4501,62 @@ export function getAssignmentTargetKind(node: Node): AssignmentKind {
44964501
break;
44974502
case SyntaxKind.ShorthandPropertyAssignment:
44984503
if ((parent as ShorthandPropertyAssignment).name !== node) {
4499-
return AssignmentKind.None;
4504+
return undefined;
45004505
}
45014506
node = parent.parent;
45024507
break;
45034508
case SyntaxKind.PropertyAssignment:
4504-
if ((parent as ShorthandPropertyAssignment).name === node) {
4505-
return AssignmentKind.None;
4509+
if ((parent as PropertyAssignment).name === node) {
4510+
return undefined;
45064511
}
45074512
node = parent.parent;
45084513
break;
45094514
default:
4510-
return AssignmentKind.None;
4515+
return undefined;
45114516
}
45124517
parent = node.parent;
45134518
}
45144519
}
45154520

4521+
/** @internal */
4522+
export function getAssignmentTargetKind(node: Node): AssignmentKind {
4523+
const target = getAssignmentTarget(node);
4524+
if (!target) {
4525+
return AssignmentKind.None;
4526+
}
4527+
switch (target.kind) {
4528+
case SyntaxKind.BinaryExpression:
4529+
const binaryOperator = target.operatorToken.kind;
4530+
return binaryOperator === SyntaxKind.EqualsToken || isLogicalOrCoalescingAssignmentOperator(binaryOperator) ?
4531+
AssignmentKind.Definite :
4532+
AssignmentKind.Compound;
4533+
case SyntaxKind.PrefixUnaryExpression:
4534+
case SyntaxKind.PostfixUnaryExpression:
4535+
return AssignmentKind.Compound;
4536+
case SyntaxKind.ForInStatement:
4537+
case SyntaxKind.ForOfStatement:
4538+
return AssignmentKind.Definite;
4539+
}
4540+
}
4541+
45164542
// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
45174543
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
45184544
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ a }] = xxx'.
45194545
// (Note that `p` is not a target in the above examples, only `a`.)
45204546
/** @internal */
45214547
export function isAssignmentTarget(node: Node): boolean {
4522-
return getAssignmentTargetKind(node) !== AssignmentKind.None;
4548+
return !!getAssignmentTarget(node);
4549+
}
4550+
4551+
function isCompoundLikeAssignment(assignment: AssignmentExpression<EqualsToken>): boolean {
4552+
const right = skipParentheses(assignment.right);
4553+
return right.kind === SyntaxKind.BinaryExpression && isShiftOperatorOrHigher((right as BinaryExpression).operatorToken.kind);
4554+
}
4555+
4556+
/** @internal */
4557+
export function isInCompoundLikeAssignment(node: Node): boolean {
4558+
const target = getAssignmentTarget(node);
4559+
return !!target && isAssignmentExpression(target, /*excludeCompoundAssignment*/ true) && isCompoundLikeAssignment(target);
45234560
}
45244561

45254562
/** @internal */
@@ -4534,8 +4571,7 @@ export type NodeWithPossibleHoistedDeclaration =
45344571
| DefaultClause
45354572
| LabeledStatement
45364573
| ForStatement
4537-
| ForInStatement
4538-
| ForOfStatement
4574+
| ForInOrOfStatement
45394575
| DoStatement
45404576
| WhileStatement
45414577
| TryStatement
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//// [tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts] ////
2+
3+
=== literalWideningWithCompoundLikeAssignments.ts ===
4+
// repro from #13865
5+
6+
const empty: "" = "";
7+
>empty : Symbol(empty, Decl(literalWideningWithCompoundLikeAssignments.ts, 2, 5))
8+
9+
let foo = empty;
10+
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
11+
>empty : Symbol(empty, Decl(literalWideningWithCompoundLikeAssignments.ts, 2, 5))
12+
13+
foo = foo + "bar"
14+
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
15+
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
16+
17+
foo // string
18+
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
19+
20+
declare const numLiteral: 0;
21+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
22+
23+
let t1 = numLiteral;
24+
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
25+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
26+
27+
t1 = t1 + 42
28+
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
29+
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
30+
31+
t1 // number
32+
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
33+
34+
let t2 = numLiteral;
35+
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
36+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
37+
38+
t2 = t2 - 42
39+
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
40+
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
41+
42+
t2 // number
43+
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
44+
45+
let t3 = numLiteral;
46+
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
47+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
48+
49+
t3 = t3 * 42
50+
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
51+
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
52+
53+
t3 // number
54+
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
55+
56+
let t4 = numLiteral;
57+
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
58+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
59+
60+
t4 = t4 ** 42
61+
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
62+
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
63+
64+
t4 // number
65+
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
66+
67+
let t5 = numLiteral;
68+
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
69+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
70+
71+
t5 = t5 / 42
72+
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
73+
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
74+
75+
t5 // number
76+
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
77+
78+
let t6 = numLiteral;
79+
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
80+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
81+
82+
t6 = t6 % 42
83+
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
84+
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
85+
86+
t6 // number
87+
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
88+
89+
let t7 = numLiteral;
90+
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3))
91+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
92+
93+
t7 = t7 >> 0
94+
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3))
95+
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3))
96+
97+
t7 // number
98+
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3))
99+
100+
let t8 = numLiteral;
101+
>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3))
102+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
103+
104+
t8 = t8 >>> 0
105+
>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3))
106+
>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3))
107+
108+
t8 // number
109+
>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3))
110+
111+
let t9 = numLiteral;
112+
>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3))
113+
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
114+
115+
t9 = t9 << 0
116+
>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3))
117+
>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3))
118+
119+
t9 // number
120+
>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3))
121+
122+
declare const literalUnion: "a" | 0;
123+
>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 45, 13))
124+
125+
let t10 = literalUnion;
126+
>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3))
127+
>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 45, 13))
128+
129+
t10 = t10 + 'b'
130+
>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3))
131+
>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3))
132+
133+
t10 // string
134+
>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3))
135+

0 commit comments

Comments
 (0)