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
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