From 54e34caaa82a1bb819edab8ba15a10b7f0bf5b37 Mon Sep 17 00:00:00 2001 From: LowR Date: Sat, 16 Oct 2021 04:28:44 +0900 Subject: [PATCH 1/2] fix(46195): handle numeric separators and larger integers correctly --- src/compiler/checker.ts | 21 ++++++++++++------- .../fourslash/codeFixUseBigIntLiteral2.ts | 17 +++++++++++++++ ...ixUseBigIntLiteralWithNumericSeparators.ts | 5 +++++ 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/codeFixUseBigIntLiteral2.ts create mode 100644 tests/cases/fourslash/codeFixUseBigIntLiteralWithNumericSeparators.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f5aed2e1d5c10..45a0308e4519d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -43558,19 +43558,24 @@ namespace ts { } function checkNumericLiteralValueSize(node: NumericLiteral) { + // We should test against `getTextOfNode(node)` rather than `node.text`, because `node.text` for large numeric literals can contain "." + // e.g. `node.text` for numeric literal `1100000000000000000000` is `1.1e21`. + const isFractional = getTextOfNode(node).includes("."); + const isScientific = node.numericLiteralFlags & TokenFlags.Scientific; + // 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) { + if (isFractional || isScientific) { 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) { + // Here `node` is guaranteed to be a numeric literal representing an integer. + // We need to judge whether the integer `node` represents is <= 2 ** 53 - 1, which can be accomplished by comparing to `value` defined below because: + // 1) when `node` represents an integer <= 2 ** 53 - 1, `node.text` is its exact string representation and thus `value` precisely represents the integer. + // 2) otherwise, although `node.text` may be imprecise string representation, its mathematical value and consequently `value` cannot be less than 2 ** 53, + // thus the result of the predicate won't be affected. + const value = +node.text; + if (value <= 2 ** 53 - 1) { return; } diff --git a/tests/cases/fourslash/codeFixUseBigIntLiteral2.ts b/tests/cases/fourslash/codeFixUseBigIntLiteral2.ts new file mode 100644 index 0000000000000..e34afe3c8a7ec --- /dev/null +++ b/tests/cases/fourslash/codeFixUseBigIntLiteral2.ts @@ -0,0 +1,17 @@ +/// +////1000000000000000000000; // 1e21 +////1_000_000_000_000_000_000_000; // 1e21 +////0x3635C9ADC5DEA00000; // 1e21 +////100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000; // 1e320 +////100_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000; // 1e320 + +verify.codeFixAll({ + fixAllDescription: ts.Diagnostics.Convert_all_to_bigint_numeric_literals.message, + fixId: "useBigintLiteral", + newFileContent: +`1000000000000000000000n; // 1e21 +1_000_000_000_000_000_000_000n; // 1e21 +0x3635C9ADC5DEA00000n; // 1e21 +100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n; // 1e320 +100_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000n; // 1e320`, +}); diff --git a/tests/cases/fourslash/codeFixUseBigIntLiteralWithNumericSeparators.ts b/tests/cases/fourslash/codeFixUseBigIntLiteralWithNumericSeparators.ts new file mode 100644 index 0000000000000..fd464765c0361 --- /dev/null +++ b/tests/cases/fourslash/codeFixUseBigIntLiteralWithNumericSeparators.ts @@ -0,0 +1,5 @@ +/// +////6_402_373_705_728_000; // 18! < 2 ** 53 +////0x16_BE_EC_CA_73_00_00; // 18! < 2 ** 53 + +verify.not.codeFixAvailable("useBigintLiteral"); From fcc38fcae9cd72b03ba2eaeeaae4dd7ed539b412 Mon Sep 17 00:00:00 2001 From: LowR Date: Thu, 18 Nov 2021 22:01:50 +0900 Subject: [PATCH 2/2] Use `indexOf()` instead of `includes()` --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 45a0308e4519d..99e781e25c0c0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -43560,7 +43560,7 @@ namespace ts { function checkNumericLiteralValueSize(node: NumericLiteral) { // We should test against `getTextOfNode(node)` rather than `node.text`, because `node.text` for large numeric literals can contain "." // e.g. `node.text` for numeric literal `1100000000000000000000` is `1.1e21`. - const isFractional = getTextOfNode(node).includes("."); + const isFractional = getTextOfNode(node).indexOf(".") !== -1; const isScientific = node.numericLiteralFlags & TokenFlags.Scientific; // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint