diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cfad47b65336f..d14efb247e106 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -33681,9 +33681,33 @@ namespace ts { return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal); } } + + // Realism (size) checking + checkNumericLiteralValueSize(node); + return false; } + function checkNumericLiteralValueSize(node: NumericLiteral) { + // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint + // Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1 + // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway + if (node.numericLiteralFlags & TokenFlags.Scientific || node.text.length <= 15 || node.text.indexOf(".") !== -1) { + return; + } + + // We can't rely on the runtime to accurately store and compare extremely large numeric values + // Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298 + // Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER, + // it's likely addition operations on it will fail too + const apparentValue = +getTextOfNode(node); + if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) { + return; + } + + addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers)); + } + function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean { const literalType = isLiteralTypeNode(node.parent) || isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b296c224d089d..188977298d827 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4643,6 +4643,10 @@ "category": "Suggestion", "code": 80007 }, + "Numeric literals with absolute values equal to 2^53 or greater are too large to be represented accurately as integers.": { + "category": "Suggestion", + "code": 80008 + }, "Add missing 'super()' call": { "category": "Message", @@ -5124,6 +5128,14 @@ "category": "Message", "code": 95090 }, + "Convert to a bigint numeric literal": { + "category": "Message", + "code": 95091 + }, + "Convert all to bigint numeric literals": { + "category": "Message", + "code": 95092 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/services/codefixes/useBigintLiteral.ts b/src/services/codefixes/useBigintLiteral.ts new file mode 100644 index 0000000000000..0749cc1a677b6 --- /dev/null +++ b/src/services/codefixes/useBigintLiteral.ts @@ -0,0 +1,33 @@ +/* @internal */ +namespace ts.codefix { + const fixId = "useBigintLiteral"; + const errorCodes = [ + Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers.code, + ]; + + registerCodeFix({ + errorCodes, + getCodeActions: context => { + const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span)); + if (changes.length > 0) { + return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_a_bigint_numeric_literal, fixId, Diagnostics.Convert_all_to_bigint_numeric_literals)]; + } + }, + fixIds: [fixId], + getAllCodeActions: context => { + return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag)); + }, + }); + + function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, span: TextSpan) { + const numericLiteral = tryCast(getTokenAtPosition(sourceFile, span.start), isNumericLiteral); + if (!numericLiteral) { + return; + } + + // We use .getText to overcome parser inaccuracies: https://github.com/microsoft/TypeScript/issues/33298 + const newText = numericLiteral.getText(sourceFile) + "n"; + + changeTracker.replaceNode(sourceFile, numericLiteral, createBigIntLiteral(newText)); + } +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 6b52393422c98..74495bc54f88b 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -79,6 +79,7 @@ "codefixes/fixStrictClassInitialization.ts", "codefixes/requireInTs.ts", "codefixes/useDefaultImport.ts", + "codefixes/useBigintLiteral.ts", "codefixes/fixAddModuleReferTypeMissingTypeof.ts", "codefixes/convertToMappedObjectType.ts", "codefixes/removeUnnecessaryAwait.ts", diff --git a/tests/cases/fourslash/codeFixUseBigIntLiteral.ts b/tests/cases/fourslash/codeFixUseBigIntLiteral.ts new file mode 100644 index 0000000000000..dcdff381af332 --- /dev/null +++ b/tests/cases/fourslash/codeFixUseBigIntLiteral.ts @@ -0,0 +1,73 @@ +/// +////9007199254740991; +////-9007199254740991; +////9007199254740992; +////-9007199254740992; +////9007199254740993; +////-9007199254740993; +////9007199254740994; +////-9007199254740994; +////0x19999999999998; +////-0x19999999999998; +////0x19999999999999; +////-0x19999999999999; +////0x20000000000000; +////-0x20000000000000; +////0x20000000000001; +////-0x20000000000001; +////2e52; +////2e53; +////2e54; +////1e00000000010; + +verify.codeFix({ + description: ts.Diagnostics.Convert_to_a_bigint_numeric_literal.message, + index: 0, + newFileContent: +`9007199254740991; +-9007199254740991; +9007199254740992n; +-9007199254740992; +9007199254740993; +-9007199254740993; +9007199254740994; +-9007199254740994; +0x19999999999998; +-0x19999999999998; +0x19999999999999; +-0x19999999999999; +0x20000000000000; +-0x20000000000000; +0x20000000000001; +-0x20000000000001; +2e52; +2e53; +2e54; +1e00000000010;` +}); + +verify.codeFixAll({ + fixAllDescription: ts.Diagnostics.Convert_all_to_bigint_numeric_literals.message, + fixId: "useBigintLiteral", + newFileContent: +`9007199254740991; +-9007199254740991; +9007199254740992n; +-9007199254740992n; +9007199254740993n; +-9007199254740993n; +9007199254740994n; +-9007199254740994n; +0x19999999999998; +-0x19999999999998; +0x19999999999999; +-0x19999999999999; +0x20000000000000n; +-0x20000000000000n; +0x20000000000001n; +-0x20000000000001n; +2e52; +2e53; +2e54; +1e00000000010;` +});