Skip to content

Commit 99f9719

Browse files
author
Josh Goldberg
committed
Added codefix for numeric literals >= 2 ** 53
`Number.MAX_SAFE_INTEGER` is `2 ** 53 - 1`, so anything greater than that is a 'dangerous' integer to store as a traditional number. This adds a codefix to suggest converting them to a `bigint` literal.
1 parent f41472b commit 99f9719

File tree

5 files changed

+139
-0
lines changed

5 files changed

+139
-0
lines changed

src/compiler/checker.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33681,9 +33681,32 @@ namespace ts {
3368133681
return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal);
3368233682
}
3368333683
}
33684+
33685+
// Realism (size) checking
33686+
checkNumericLiteralValueSize(node);
33687+
3368433688
return false;
3368533689
}
3368633690

33691+
function checkNumericLiteralValueSize(node: NumericLiteral) {
33692+
// Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1
33693+
// Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway
33694+
if (node.text.length <= 15 || node.text.indexOf(".") !== -1) {
33695+
return;
33696+
}
33697+
33698+
// We can't rely on the runtime to accurately store and compare extremely large numeric values
33699+
// Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298
33700+
// Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER,
33701+
// it's likely addition operations on it will fail too
33702+
const apparentValue = +getTextOfNode(node);
33703+
if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) {
33704+
return;
33705+
}
33706+
33707+
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));
33708+
}
33709+
3368733710
function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean {
3368833711
const literalType = isLiteralTypeNode(node.parent) ||
3368933712
isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent);

src/compiler/diagnosticMessages.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4643,6 +4643,10 @@
46434643
"category": "Suggestion",
46444644
"code": 80007
46454645
},
4646+
"Numeric literals with absolute values equal to 2^53 or greater are too large to be represented accurately as integers.": {
4647+
"category": "Suggestion",
4648+
"code": 80008
4649+
},
46464650

46474651
"Add missing 'super()' call": {
46484652
"category": "Message",
@@ -5124,6 +5128,14 @@
51245128
"category": "Message",
51255129
"code": 95090
51265130
},
5131+
"Convert to a bigint numeric literal": {
5132+
"category": "Message",
5133+
"code": 95091
5134+
},
5135+
"Convert all to bigint numeric literals": {
5136+
"category": "Message",
5137+
"code": 95092
5138+
},
51275139

51285140
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
51295141
"category": "Error",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "useBigintLiteral";
4+
const errorCodes = [
5+
Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers.code,
6+
];
7+
8+
registerCodeFix({
9+
errorCodes,
10+
getCodeActions: context => {
11+
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span));
12+
if (changes.length > 0) {
13+
return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_a_bigint_numeric_literal, fixId, Diagnostics.Convert_all_to_bigint_numeric_literals)];
14+
}
15+
},
16+
fixIds: [fixId],
17+
getAllCodeActions: context => {
18+
return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag));
19+
},
20+
});
21+
22+
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, span: TextSpan) {
23+
const numericLiteral = tryCast(getTokenAtPosition(sourceFile, span.start), isNumericLiteral);
24+
if (!numericLiteral) {
25+
return;
26+
}
27+
28+
// We use .getText to overcome parser inaccuracies: https://github.com/microsoft/TypeScript/issues/33298
29+
const newText = numericLiteral.getText(sourceFile) + "n";
30+
31+
changeTracker.replaceNode(sourceFile, numericLiteral, createBigIntLiteral(newText));
32+
}
33+
}

src/services/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"codefixes/fixStrictClassInitialization.ts",
8080
"codefixes/requireInTs.ts",
8181
"codefixes/useDefaultImport.ts",
82+
"codefixes/useBigintLiteral.ts",
8283
"codefixes/fixAddModuleReferTypeMissingTypeof.ts",
8384
"codefixes/convertToMappedObjectType.ts",
8485
"codefixes/removeUnnecessaryAwait.ts",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/// <reference path="fourslash.ts" />
2+
////9007199254740991;
3+
////-9007199254740991;
4+
////9007199254740992;
5+
////-9007199254740992;
6+
////9007199254740993;
7+
////-9007199254740993;
8+
////9007199254740994;
9+
////-9007199254740994;
10+
////0x19999999999998;
11+
////-0x19999999999998;
12+
////0x19999999999999;
13+
////-0x19999999999999;
14+
////0x20000000000000;
15+
////-0x20000000000000;
16+
////0x20000000000001;
17+
////-0x20000000000001;
18+
////2e52;
19+
////2e53;
20+
////2e54;
21+
22+
verify.codeFix({
23+
description: ts.Diagnostics.Convert_to_a_bigint_numeric_literal.message,
24+
index: 0,
25+
newFileContent:
26+
`9007199254740991;
27+
-9007199254740991;
28+
9007199254740992n;
29+
-9007199254740992;
30+
9007199254740993;
31+
-9007199254740993;
32+
9007199254740994;
33+
-9007199254740994;
34+
0x19999999999998;
35+
-0x19999999999998;
36+
0x19999999999999;
37+
-0x19999999999999;
38+
0x20000000000000;
39+
-0x20000000000000;
40+
0x20000000000001;
41+
-0x20000000000001;
42+
2e52;
43+
2e53;
44+
2e54;`
45+
});
46+
47+
verify.codeFixAll({
48+
fixAllDescription: ts.Diagnostics.Convert_all_to_bigint_numeric_literals.message,
49+
fixId: "useBigintLiteral",
50+
newFileContent:
51+
`9007199254740991;
52+
-9007199254740991;
53+
9007199254740992n;
54+
-9007199254740992n;
55+
9007199254740993n;
56+
-9007199254740993n;
57+
9007199254740994n;
58+
-9007199254740994n;
59+
0x19999999999998;
60+
-0x19999999999998;
61+
0x19999999999999;
62+
-0x19999999999999;
63+
0x20000000000000n;
64+
-0x20000000000000n;
65+
0x20000000000001n;
66+
-0x20000000000001n;
67+
2e52;
68+
2e53;
69+
2e54;`
70+
});

0 commit comments

Comments
 (0)