Skip to content

Commit e8fc62e

Browse files
author
Orta
authored
Merge pull request #33300 from JoshuaKGoldberg/too-large-integer-bigint-codefix
Added codefix for numeric literals >= 2 ** 53
2 parents 6995a90 + 79e9bb1 commit e8fc62e

File tree

5 files changed

+143
-0
lines changed

5 files changed

+143
-0
lines changed

src/compiler/checker.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33716,9 +33716,33 @@ namespace ts {
3371633716
return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal);
3371733717
}
3371833718
}
33719+
33720+
// Realism (size) checking
33721+
checkNumericLiteralValueSize(node);
33722+
3371933723
return false;
3372033724
}
3372133725

33726+
function checkNumericLiteralValueSize(node: NumericLiteral) {
33727+
// Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint
33728+
// Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1
33729+
// Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway
33730+
if (node.numericLiteralFlags & TokenFlags.Scientific || node.text.length <= 15 || node.text.indexOf(".") !== -1) {
33731+
return;
33732+
}
33733+
33734+
// We can't rely on the runtime to accurately store and compare extremely large numeric values
33735+
// Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298
33736+
// Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER,
33737+
// it's likely addition operations on it will fail too
33738+
const apparentValue = +getTextOfNode(node);
33739+
if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) {
33740+
return;
33741+
}
33742+
33743+
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));
33744+
}
33745+
3372233746
function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean {
3372333747
const literalType = isLiteralTypeNode(node.parent) ||
3372433748
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: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
////1e00000000010;
22+
23+
verify.codeFix({
24+
description: ts.Diagnostics.Convert_to_a_bigint_numeric_literal.message,
25+
index: 0,
26+
newFileContent:
27+
`9007199254740991;
28+
-9007199254740991;
29+
9007199254740992n;
30+
-9007199254740992;
31+
9007199254740993;
32+
-9007199254740993;
33+
9007199254740994;
34+
-9007199254740994;
35+
0x19999999999998;
36+
-0x19999999999998;
37+
0x19999999999999;
38+
-0x19999999999999;
39+
0x20000000000000;
40+
-0x20000000000000;
41+
0x20000000000001;
42+
-0x20000000000001;
43+
2e52;
44+
2e53;
45+
2e54;
46+
1e00000000010;`
47+
});
48+
49+
verify.codeFixAll({
50+
fixAllDescription: ts.Diagnostics.Convert_all_to_bigint_numeric_literals.message,
51+
fixId: "useBigintLiteral",
52+
newFileContent:
53+
`9007199254740991;
54+
-9007199254740991;
55+
9007199254740992n;
56+
-9007199254740992n;
57+
9007199254740993n;
58+
-9007199254740993n;
59+
9007199254740994n;
60+
-9007199254740994n;
61+
0x19999999999998;
62+
-0x19999999999998;
63+
0x19999999999999;
64+
-0x19999999999999;
65+
0x20000000000000n;
66+
-0x20000000000000n;
67+
0x20000000000001n;
68+
-0x20000000000001n;
69+
2e52;
70+
2e53;
71+
2e54;
72+
1e00000000010;`
73+
});

0 commit comments

Comments
 (0)