Skip to content

Shortcircuit getTypeOfExpression for arithmetic operators when the type is trivially deducible by syntactically contained literals #30945

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3163,7 +3163,19 @@ namespace ts {
transformFlags |= TransformFlags.AssertES2016;
}

node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
let optimizationFlags: TransformFlags = 0;
if (isNumericArithmeticOperator(operatorTokenKind)) {
const lhs = skipOuterExpressions(node.left);
const rhs = skipOuterExpressions(node.right);
if (lhs.kind === SyntaxKind.NumericLiteral || rhs.kind === SyntaxKind.NumericLiteral || lhs.transformFlags & TransformFlags.ContainsNumberLiteral || rhs.transformFlags & TransformFlags.ContainsNumberLiteral) {
optimizationFlags |= TransformFlags.ContainsNumberLiteral;
}
if (lhs.kind === SyntaxKind.BigIntLiteral || rhs.kind === SyntaxKind.BigIntLiteral || lhs.transformFlags & TransformFlags.ContainsBigIntLiteral || rhs.transformFlags & TransformFlags.ContainsBigIntLiteral) {
optimizationFlags |= TransformFlags.ContainsBigIntLiteral;
}
}

node.transformFlags = transformFlags | optimizationFlags | TransformFlags.HasComputedFlags;
return transformFlags & ~TransformFlags.NodeExcludes;
}

Expand Down
18 changes: 16 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23146,6 +23146,7 @@ namespace ts {
const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type);
const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type);
let resultType: Type;
let skipAssignmentError = false;
// If both are any or unknown, allow operation; assume it will resolve to number
if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) ||
// Or, if neither could be bigint, implicit coercion results in a number result
Expand All @@ -23164,9 +23165,10 @@ namespace ts {
}
else {
reportOperatorError();
resultType = errorType;
resultType = bigintType;
skipAssignmentError = true;
}
if (leftOk && rightOk) {
if (leftOk && rightOk && !skipAssignmentError) {
checkAssignmentOperator(resultType);
}
return resultType;
Expand Down Expand Up @@ -23772,6 +23774,18 @@ namespace ts {
else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) {
return getTypeFromTypeNode((<TypeAssertion>expr).type);
}
else if (node.transformFlags & TransformFlags.ContainsEitherNumberLiterals) {
const flags = node.transformFlags & TransformFlags.ContainsEitherNumberLiterals;
if (flags === TransformFlags.ContainsEitherNumberLiterals) {
return bigintType; // Return `bigintType` when both literals are present, since the errorType is just going to remove completions for dependent statements
}
if (flags === TransformFlags.ContainsNumberLiteral) {
return numberType;
}
if (flags === TransformFlags.ContainsBigIntLiteral) {
return bigintType;
}
}
// Otherwise simply call checkExpression. Ideally, the entire family of checkXXX functions
// should have a parameter that indicates whether full error checking is required such that
// we can perform the optimizations locally.
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,8 @@ namespace ts {
LastJSDocTagNode = JSDocPropertyTag,
/* @internal */ FirstContextualKeyword = AbstractKeyword,
/* @internal */ LastContextualKeyword = OfKeyword,
/* @internal */ FirstNumericArithmeticOperator = MinusToken,
/* @internal */ LastNumericArithmeticOperator = TildeToken,
}

export const enum NodeFlags {
Expand Down Expand Up @@ -5155,6 +5157,10 @@ namespace ts {
ContainsHoistedDeclarationOrCompletion = 1 << 18,
ContainsDynamicImport = 1 << 19,

// Control Flow Optimization Markers
ContainsBigIntLiteral = 1 << 20,
ContainsNumberLiteral = 1 << 21,

// Please leave this as 1 << 29.
// It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system.
// It is a good reminder of how much room we have left
Expand Down Expand Up @@ -5200,6 +5206,7 @@ namespace ts {

// Masks
// - Additional bitmasks
ContainsEitherNumberLiterals = ContainsBigIntLiteral | ContainsNumberLiteral,
}

export interface SourceMapRange extends TextRange {
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3900,6 +3900,11 @@ namespace ts {
|| token === SyntaxKind.ExclamationToken;
}

// This does not include `+` since that is also used for string concatenation
export function isNumericArithmeticOperator(token: SyntaxKind): boolean {
return token >= SyntaxKind.FirstNumericArithmeticOperator && token <= SyntaxKind.LastNumericArithmeticOperator;
}

export function isAssignmentOperator(token: SyntaxKind): boolean {
return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment;
}
Expand Down
Loading