Widen widening literal types through compound-like assignments (#52493)

This commit is contained in:
Mateusz Burzyński 2023-08-16 01:53:11 +02:00 committed by GitHub
parent b8b0d26cb9
commit cac899d44d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 421 additions and 26 deletions

View File

@ -554,6 +554,7 @@ import {
isImportOrExportSpecifier,
isImportSpecifier,
isImportTypeNode,
isInCompoundLikeAssignment,
isIndexedAccessTypeNode,
isInExpressionContext,
isInfinityOrNaNString,
@ -26729,10 +26730,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow));
return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType;
}
if (declaredType.flags & TypeFlags.Union) {
return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow));
const t = isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(declaredType) : declaredType;
if (t.flags & TypeFlags.Union) {
return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow));
}
return declaredType;
return t;
}
// We didn't have a direct match. However, if the reference is a dotted name, this
// 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 {
// entities we simply return the declared type.
if (localOrExportSymbol.flags & SymbolFlags.Variable) {
if (assignmentKind === AssignmentKind.Definite) {
return type;
return isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type;
}
}
else if (isAlias) {

View File

@ -1222,7 +1222,8 @@ function isShiftOperator(kind: SyntaxKind): kind is ShiftOperator {
|| kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
}
function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher {
/** @internal */
export function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher {
return isShiftOperator(kind)
|| isAdditiveOperatorOrHigher(kind);
}

View File

@ -152,8 +152,6 @@ import {
forEachChild,
forEachChildRecursively,
ForInOrOfStatement,
ForInStatement,
ForOfStatement,
ForStatement,
FunctionBody,
FunctionDeclaration,
@ -331,6 +329,7 @@ import {
isQualifiedName,
isRootedDiskPath,
isSetAccessorDeclaration,
isShiftOperatorOrHigher,
isShorthandPropertyAssignment,
isSourceFile,
isString,
@ -3418,9 +3417,9 @@ export function isInExpressionContext(node: Node): boolean {
forStatement.incrementor === node;
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
const forInStatement = parent as ForInStatement | ForOfStatement;
return (forInStatement.initializer === node && forInStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) ||
forInStatement.expression === node;
const forInOrOfStatement = parent as ForInOrOfStatement;
return (forInOrOfStatement.initializer === node && forInOrOfStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) ||
forInOrOfStatement.expression === node;
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
return node === (parent as AssertionExpression).expression;
@ -4468,23 +4467,29 @@ export const enum AssignmentKind {
None, Definite, Compound
}
/** @internal */
export function getAssignmentTargetKind(node: Node): AssignmentKind {
type AssignmentTarget =
| BinaryExpression
| PrefixUnaryExpression
| PostfixUnaryExpression
| ForInOrOfStatement;
function getAssignmentTarget(node: Node): AssignmentTarget | undefined {
let parent = node.parent;
while (true) {
switch (parent.kind) {
case SyntaxKind.BinaryExpression:
const binaryOperator = (parent as BinaryExpression).operatorToken.kind;
return isAssignmentOperator(binaryOperator) && (parent as BinaryExpression).left === node ?
binaryOperator === SyntaxKind.EqualsToken || isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? AssignmentKind.Definite : AssignmentKind.Compound :
AssignmentKind.None;
const binaryExpression = parent as BinaryExpression;
const binaryOperator = binaryExpression.operatorToken.kind;
return isAssignmentOperator(binaryOperator) && binaryExpression.left === node ? binaryExpression : undefined;
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.PostfixUnaryExpression:
const unaryOperator = (parent as PrefixUnaryExpression | PostfixUnaryExpression).operator;
return unaryOperator === SyntaxKind.PlusPlusToken || unaryOperator === SyntaxKind.MinusMinusToken ? AssignmentKind.Compound : AssignmentKind.None;
const unaryExpression = (parent as PrefixUnaryExpression | PostfixUnaryExpression);
const unaryOperator = unaryExpression.operator;
return unaryOperator === SyntaxKind.PlusPlusToken || unaryOperator === SyntaxKind.MinusMinusToken ? unaryExpression : undefined;
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
return (parent as ForInOrOfStatement).initializer === node ? AssignmentKind.Definite : AssignmentKind.None;
const forInOrOfStatement = parent as ForInOrOfStatement;
return forInOrOfStatement.initializer === node ? forInOrOfStatement : undefined;
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.ArrayLiteralExpression:
case SyntaxKind.SpreadElement:
@ -4496,30 +4501,62 @@ export function getAssignmentTargetKind(node: Node): AssignmentKind {
break;
case SyntaxKind.ShorthandPropertyAssignment:
if ((parent as ShorthandPropertyAssignment).name !== node) {
return AssignmentKind.None;
return undefined;
}
node = parent.parent;
break;
case SyntaxKind.PropertyAssignment:
if ((parent as ShorthandPropertyAssignment).name === node) {
return AssignmentKind.None;
if ((parent as PropertyAssignment).name === node) {
return undefined;
}
node = parent.parent;
break;
default:
return AssignmentKind.None;
return undefined;
}
parent = node.parent;
}
}
/** @internal */
export function getAssignmentTargetKind(node: Node): AssignmentKind {
const target = getAssignmentTarget(node);
if (!target) {
return AssignmentKind.None;
}
switch (target.kind) {
case SyntaxKind.BinaryExpression:
const binaryOperator = target.operatorToken.kind;
return binaryOperator === SyntaxKind.EqualsToken || isLogicalOrCoalescingAssignmentOperator(binaryOperator) ?
AssignmentKind.Definite :
AssignmentKind.Compound;
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.PostfixUnaryExpression:
return AssignmentKind.Compound;
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
return AssignmentKind.Definite;
}
}
// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ a }] = xxx'.
// (Note that `p` is not a target in the above examples, only `a`.)
/** @internal */
export function isAssignmentTarget(node: Node): boolean {
return getAssignmentTargetKind(node) !== AssignmentKind.None;
return !!getAssignmentTarget(node);
}
function isCompoundLikeAssignment(assignment: AssignmentExpression<EqualsToken>): boolean {
const right = skipParentheses(assignment.right);
return right.kind === SyntaxKind.BinaryExpression && isShiftOperatorOrHigher((right as BinaryExpression).operatorToken.kind);
}
/** @internal */
export function isInCompoundLikeAssignment(node: Node): boolean {
const target = getAssignmentTarget(node);
return !!target && isAssignmentExpression(target, /*excludeCompoundAssignment*/ true) && isCompoundLikeAssignment(target);
}
/** @internal */
@ -4534,8 +4571,7 @@ export type NodeWithPossibleHoistedDeclaration =
| DefaultClause
| LabeledStatement
| ForStatement
| ForInStatement
| ForOfStatement
| ForInOrOfStatement
| DoStatement
| WhileStatement
| TryStatement

View File

@ -0,0 +1,135 @@
//// [tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts] ////
=== literalWideningWithCompoundLikeAssignments.ts ===
// repro from #13865
const empty: "" = "";
>empty : Symbol(empty, Decl(literalWideningWithCompoundLikeAssignments.ts, 2, 5))
let foo = empty;
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
>empty : Symbol(empty, Decl(literalWideningWithCompoundLikeAssignments.ts, 2, 5))
foo = foo + "bar"
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
foo // string
>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3))
declare const numLiteral: 0;
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
let t1 = numLiteral;
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
t1 = t1 + 42
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
t1 // number
>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3))
let t2 = numLiteral;
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
t2 = t2 - 42
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
t2 // number
>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3))
let t3 = numLiteral;
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
t3 = t3 * 42
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
t3 // number
>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3))
let t4 = numLiteral;
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
t4 = t4 ** 42
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
t4 // number
>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3))
let t5 = numLiteral;
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
t5 = t5 / 42
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
t5 // number
>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3))
let t6 = numLiteral;
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
t6 = t6 % 42
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
t6 // number
>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3))
let t7 = numLiteral;
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3))
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
t7 = t7 >> 0
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3))
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3))
t7 // number
>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3))
let t8 = numLiteral;
>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3))
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
t8 = t8 >>> 0
>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3))
>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3))
t8 // number
>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3))
let t9 = numLiteral;
>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3))
>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13))
t9 = t9 << 0
>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3))
>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3))
t9 // number
>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3))
declare const literalUnion: "a" | 0;
>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 45, 13))
let t10 = literalUnion;
>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3))
>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 45, 13))
t10 = t10 + 'b'
>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3))
>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3))
t10 // string
>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3))

View File

@ -0,0 +1,169 @@
//// [tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts] ////
=== literalWideningWithCompoundLikeAssignments.ts ===
// repro from #13865
const empty: "" = "";
>empty : ""
>"" : ""
let foo = empty;
>foo : ""
>empty : ""
foo = foo + "bar"
>foo = foo + "bar" : string
>foo : string
>foo + "bar" : string
>foo : ""
>"bar" : "bar"
foo // string
>foo : string
declare const numLiteral: 0;
>numLiteral : 0
let t1 = numLiteral;
>t1 : 0
>numLiteral : 0
t1 = t1 + 42
>t1 = t1 + 42 : number
>t1 : number
>t1 + 42 : number
>t1 : 0
>42 : 42
t1 // number
>t1 : number
let t2 = numLiteral;
>t2 : 0
>numLiteral : 0
t2 = t2 - 42
>t2 = t2 - 42 : number
>t2 : number
>t2 - 42 : number
>t2 : 0
>42 : 42
t2 // number
>t2 : number
let t3 = numLiteral;
>t3 : 0
>numLiteral : 0
t3 = t3 * 42
>t3 = t3 * 42 : number
>t3 : number
>t3 * 42 : number
>t3 : 0
>42 : 42
t3 // number
>t3 : number
let t4 = numLiteral;
>t4 : 0
>numLiteral : 0
t4 = t4 ** 42
>t4 = t4 ** 42 : number
>t4 : number
>t4 ** 42 : number
>t4 : 0
>42 : 42
t4 // number
>t4 : number
let t5 = numLiteral;
>t5 : 0
>numLiteral : 0
t5 = t5 / 42
>t5 = t5 / 42 : number
>t5 : number
>t5 / 42 : number
>t5 : 0
>42 : 42
t5 // number
>t5 : number
let t6 = numLiteral;
>t6 : 0
>numLiteral : 0
t6 = t6 % 42
>t6 = t6 % 42 : number
>t6 : number
>t6 % 42 : number
>t6 : 0
>42 : 42
t6 // number
>t6 : number
let t7 = numLiteral;
>t7 : 0
>numLiteral : 0
t7 = t7 >> 0
>t7 = t7 >> 0 : number
>t7 : number
>t7 >> 0 : number
>t7 : 0
>0 : 0
t7 // number
>t7 : number
let t8 = numLiteral;
>t8 : 0
>numLiteral : 0
t8 = t8 >>> 0
>t8 = t8 >>> 0 : number
>t8 : number
>t8 >>> 0 : number
>t8 : 0
>0 : 0
t8 // number
>t8 : number
let t9 = numLiteral;
>t9 : 0
>numLiteral : 0
t9 = t9 << 0
>t9 = t9 << 0 : number
>t9 : number
>t9 << 0 : number
>t9 : 0
>0 : 0
t9 // number
>t9 : number
declare const literalUnion: "a" | 0;
>literalUnion : 0 | "a"
let t10 = literalUnion;
>t10 : 0 | "a"
>literalUnion : 0 | "a"
t10 = t10 + 'b'
>t10 = t10 + 'b' : string
>t10 : string | number
>t10 + 'b' : string
>t10 : 0 | "a"
>'b' : "b"
t10 // string
>t10 : string

View File

@ -0,0 +1,52 @@
// @strict: true
// @noEmit: true
// repro from #13865
const empty: "" = "";
let foo = empty;
foo = foo + "bar"
foo // string
declare const numLiteral: 0;
let t1 = numLiteral;
t1 = t1 + 42
t1 // number
let t2 = numLiteral;
t2 = t2 - 42
t2 // number
let t3 = numLiteral;
t3 = t3 * 42
t3 // number
let t4 = numLiteral;
t4 = t4 ** 42
t4 // number
let t5 = numLiteral;
t5 = t5 / 42
t5 // number
let t6 = numLiteral;
t6 = t6 % 42
t6 // number
let t7 = numLiteral;
t7 = t7 >> 0
t7 // number
let t8 = numLiteral;
t8 = t8 >>> 0
t8 // number
let t9 = numLiteral;
t9 = t9 << 0
t9 // number
declare const literalUnion: "a" | 0;
let t10 = literalUnion;
t10 = t10 + 'b'
t10 // string