-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Numeric separators #20324
Numeric separators #20324
Changes from 3 commits
079b96e
e214d98
d980e4a
60d8bdf
771a1d6
cd0777a
cc84c0b
7ac914d
11dddbb
c2b81ec
ec4540d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -858,28 +858,76 @@ namespace ts { | |
} | ||
} | ||
|
||
function scanNumberFragment(): string { | ||
let start = pos; | ||
let allowSeperator = false; | ||
let result: string; | ||
while (true) { | ||
const ch = text.charCodeAt(pos); | ||
if (allowSeperator && ch === CharacterCodes._) { | ||
allowSeperator = false; | ||
tokenFlags |= TokenFlags.ContainsSeparator; | ||
result = (result || "") + text.substring(start, pos); | ||
pos++; | ||
start = pos; | ||
continue; | ||
} | ||
if (isDigit(ch)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Considering the similarities between this, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alternatively, you can pass in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth keeping focus on performance when parsing numbers, even at a cost of flexibility. Too hot a code path. |
||
allowSeperator = true; | ||
pos++; | ||
continue; | ||
} | ||
break; | ||
} | ||
if (text.charCodeAt(pos - 1) === CharacterCodes._) { | ||
pos--; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do this exact set of three lines multiple times in this PR. Would it make sense to provide an optional argument to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not add a look-ahead and check whether the subsequent character could continue the literal? That way you don't need to rewind at the end. |
||
error(Diagnostics.Numeric_seperators_are_not_allowed_at_the_end_of_a_literal, 1); | ||
pos++; | ||
} | ||
return result = (result || "") + text.substring(start, pos); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You definitely assign There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, you don't need to reassign There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Done. |
||
} | ||
|
||
function scanNumber(): string { | ||
const start = pos; | ||
while (isDigit(text.charCodeAt(pos))) pos++; | ||
const mainFragment = scanNumberFragment(); | ||
let decimalFragment: string; | ||
let scientificFragment: string; | ||
if (text.charCodeAt(pos) === CharacterCodes.dot) { | ||
pos++; | ||
while (isDigit(text.charCodeAt(pos))) pos++; | ||
decimalFragment = scanNumberFragment(); | ||
} | ||
let end = pos; | ||
if (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e) { | ||
pos++; | ||
tokenFlags |= TokenFlags.Scientific; | ||
if (text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) pos++; | ||
if (isDigit(text.charCodeAt(pos))) { | ||
pos++; | ||
while (isDigit(text.charCodeAt(pos))) pos++; | ||
const preNumericPart = pos; | ||
const finalFragment = scanNumberFragment(); | ||
if (!finalFragment) { | ||
error(Diagnostics.Digit_expected); | ||
} | ||
else { | ||
scientificFragment = text.substring(end, preNumericPart) + finalFragment; | ||
end = pos; | ||
} | ||
} | ||
if (tokenFlags & TokenFlags.ContainsSeparator) { | ||
if (decimalFragment && scientificFragment) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about just let result = mainFragment;
if (decimalFragment) {
result += "." + decimalFragment;
}
if (scientificFragment) {
result += "." + scientificFragment;
}
return "" + +mainFragment; |
||
return "" + +(mainFragment + "." + decimalFragment + scientificFragment); | ||
} | ||
else if (decimalFragment) { | ||
return "" + +(mainFragment + "." + decimalFragment); | ||
} | ||
else if (scientificFragment) { | ||
return "" + +(mainFragment + scientificFragment); | ||
} | ||
else { | ||
error(Diagnostics.Digit_expected); | ||
return "" + +mainFragment; | ||
} | ||
} | ||
return "" + +(text.substring(start, end)); | ||
else { | ||
return "" + +(text.substring(start, end)); // No need to use all the fragments; no _ removal needed | ||
} | ||
} | ||
|
||
function scanOctalDigits(): number { | ||
|
@@ -909,8 +957,16 @@ namespace ts { | |
function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean): number { | ||
let digits = 0; | ||
let value = 0; | ||
let seperatorAllowed = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
while (digits < minCount || scanAsManyAsPossible) { | ||
const ch = text.charCodeAt(pos); | ||
if (seperatorAllowed && ch === CharacterCodes._) { | ||
seperatorAllowed = false; | ||
tokenFlags |= TokenFlags.ContainsSeparator; | ||
pos++; | ||
continue; | ||
} | ||
seperatorAllowed = true; | ||
if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { | ||
value = value * 16 + ch - CharacterCodes._0; | ||
} | ||
|
@@ -929,6 +985,11 @@ namespace ts { | |
if (digits < minCount) { | ||
value = -1; | ||
} | ||
if (text.charCodeAt(pos - 1) === CharacterCodes._) { | ||
pos--; | ||
error(Diagnostics.Numeric_seperators_are_not_allowed_at_the_end_of_a_literal, 1); | ||
pos++; | ||
} | ||
return value; | ||
} | ||
|
||
|
@@ -1218,8 +1279,17 @@ namespace ts { | |
// For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b. | ||
// Similarly valid octalIntegerLiteral must have at least one octal digit following o or O. | ||
let numberOfDigits = 0; | ||
let seperatorAllowed = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. separator |
||
while (true) { | ||
const ch = text.charCodeAt(pos); | ||
// Numeric seperators are allowed anywhere within a numeric literal, except not at the beginning, or following another seperator | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. separator |
||
if (seperatorAllowed && ch === CharacterCodes._) { | ||
seperatorAllowed = false; | ||
tokenFlags |= TokenFlags.ContainsSeparator; | ||
pos++; | ||
continue; | ||
} | ||
seperatorAllowed = true; | ||
const valueOfCh = ch - CharacterCodes._0; | ||
if (!isDigit(ch) || valueOfCh >= base) { | ||
break; | ||
|
@@ -1232,6 +1302,13 @@ namespace ts { | |
if (numberOfDigits === 0) { | ||
return -1; | ||
} | ||
if (text.charCodeAt(pos - 1) === CharacterCodes._) { | ||
// Literal ends with underscore - not allowed | ||
pos--; | ||
error(Diagnostics.Numeric_seperators_are_not_allowed_at_the_end_of_a_literal, 1); | ||
pos++; // Consume character anyway to reduce followon errors from a single erroneous trailing `_`, like `Cannot find name '_'` | ||
return value; | ||
} | ||
return value; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -345,7 +345,7 @@ namespace ts { | |
export function getLiteralText(node: LiteralLikeNode, sourceFile: SourceFile) { | ||
// If we don't need to downlevel and we can reach the original source text using | ||
// the node's parent reference, then simply get the text as it was originally written. | ||
if (!nodeIsSynthesized(node) && node.parent) { | ||
if (!nodeIsSynthesized(node) && node.parent && !(isNumericLiteral(node) && node.numericLiteralFlags & TokenFlags.ContainsSeparator)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should emit them as is for ESNext i would say.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acctually There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mhegazy said offline that we should open an issue to add an ES2018 target, since there are some lib additions to make in addition to adding an output mode that preserves these separators. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opened #20342 to track. |
||
return getSourceTextOfNodeFromSourceFile(sourceFile, node); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//// [parser.numericSeparators.binary.ts] | ||
0b00_11; | ||
0B0_1; | ||
0b1100_0011; | ||
0B0_11_0101; | ||
|
||
|
||
//// [parser.numericSeparators.binary.js] | ||
3; | ||
1; | ||
195; | ||
53; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/parser.numericSeparators.binary.ts === | ||
0b00_11; | ||
No type information for this code.0B0_1; | ||
No type information for this code.0b1100_0011; | ||
No type information for this code.0B0_11_0101; | ||
No type information for this code. | ||
No type information for this code. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/parser.numericSeparators.binary.ts === | ||
0b00_11; | ||
>0b00_11 : 3 | ||
|
||
0B0_1; | ||
>0B0_1 : 1 | ||
|
||
0b1100_0011; | ||
>0b1100_0011 : 195 | ||
|
||
0B0_11_0101; | ||
>0B0_11_0101 : 53 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/1.ts(1,5): error TS6188: Numeric seperators are not allowed at the end of a literal. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/2.ts(1,3): error TS1177: Binary digit expected. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/2.ts(1,3): error TS2304: Cannot find name '_110'. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/3.ts(1,2): error TS6188: Numeric seperators are not allowed at the end of a literal. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/3.ts(1,3): error TS1005: ';' expected. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/3.ts(1,3): error TS2304: Cannot find name 'B0101'. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/4.ts(1,5): error TS6188: Numeric seperators are not allowed at the end of a literal. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/4.ts(1,6): error TS1005: ';' expected. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/4.ts(1,6): error TS2304: Cannot find name '_11'. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/5.ts(1,12): error TS6188: Numeric seperators are not allowed at the end of a literal. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/5.ts(1,13): error TS1005: ';' expected. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/5.ts(1,13): error TS2304: Cannot find name '_'. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/6.ts(1,3): error TS1177: Binary digit expected. | ||
tests/cases/conformance/parser/ecmascriptnext/numericSeparators/6.ts(1,3): error TS2304: Cannot find name '___0111010_0101_1'. | ||
|
||
|
||
==== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/1.ts (1 errors) ==== | ||
0b00_ | ||
~ | ||
!!! error TS6188: Numeric seperators are not allowed at the end of a literal. | ||
|
||
==== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/2.ts (2 errors) ==== | ||
0b_110 | ||
|
||
!!! error TS1177: Binary digit expected. | ||
~~~~ | ||
!!! error TS2304: Cannot find name '_110'. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we consider just parsing out the rest of the number rather than just stop? Then we'd avoid the excess There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The proposed grammar says the production is effectively |
||
|
||
==== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/3.ts (3 errors) ==== | ||
0_B0101 | ||
~ | ||
!!! error TS6188: Numeric seperators are not allowed at the end of a literal. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message isn't clear considering the context. Perhaps it should be |
||
~~~~~ | ||
!!! error TS1005: ';' expected. | ||
~~~~~ | ||
!!! error TS2304: Cannot find name 'B0101'. | ||
|
||
==== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/4.ts (3 errors) ==== | ||
0b01__11 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, after we report the numeric separator error, we may want to continue to parse out the rest of the number to avoid excessive unrelated errors, though I'd like to get @mhegazy's or @DanielRosenwasser's opinion on that. |
||
~ | ||
!!! error TS6188: Numeric seperators are not allowed at the end of a literal. | ||
~~~ | ||
!!! error TS1005: ';' expected. | ||
~~~ | ||
!!! error TS2304: Cannot find name '_11'. | ||
|
||
==== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/5.ts (3 errors) ==== | ||
0B0110_0110__ | ||
~ | ||
!!! error TS6188: Numeric seperators are not allowed at the end of a literal. | ||
~ | ||
!!! error TS1005: ';' expected. | ||
~ | ||
!!! error TS2304: Cannot find name '_'. | ||
|
||
==== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/6.ts (2 errors) ==== | ||
0b___0111010_0101_1 | ||
|
||
!!! error TS1177: Binary digit expected. | ||
~~~~~~~~~~~~~~~~~ | ||
!!! error TS2304: Cannot find name '___0111010_0101_1'. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
//// [tests/cases/conformance/parser/ecmascriptnext/numericSeparators/parser.numericSeparators.binaryNegative.ts] //// | ||
|
||
//// [1.ts] | ||
0b00_ | ||
|
||
//// [2.ts] | ||
0b_110 | ||
|
||
//// [3.ts] | ||
0_B0101 | ||
|
||
//// [4.ts] | ||
0b01__11 | ||
|
||
//// [5.ts] | ||
0B0110_0110__ | ||
|
||
//// [6.ts] | ||
0b___0111010_0101_1 | ||
|
||
|
||
//// [1.js] | ||
0; | ||
//// [2.js] | ||
0; | ||
_110; | ||
//// [3.js] | ||
0; | ||
B0101; | ||
//// [4.js] | ||
1; | ||
_11; | ||
//// [5.js] | ||
102; | ||
_; | ||
//// [6.js] | ||
0; | ||
___0111010_0101_1; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/1.ts === | ||
0b00_ | ||
No type information for this code. | ||
No type information for this code.=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/2.ts === | ||
0b_110 | ||
No type information for this code. | ||
No type information for this code.=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/3.ts === | ||
0_B0101 | ||
No type information for this code. | ||
No type information for this code.=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/4.ts === | ||
0b01__11 | ||
No type information for this code. | ||
No type information for this code.=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/5.ts === | ||
0B0110_0110__ | ||
No type information for this code. | ||
No type information for this code.=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/6.ts === | ||
0b___0111010_0101_1 | ||
No type information for this code. | ||
No type information for this code. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/1.ts === | ||
0b00_ | ||
>0b00_ : 0 | ||
|
||
=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/2.ts === | ||
0b_110 | ||
>0b : 0 | ||
>_110 : any | ||
|
||
=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/3.ts === | ||
0_B0101 | ||
>0_ : 0 | ||
>B0101 : any | ||
|
||
=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/4.ts === | ||
0b01__11 | ||
>0b01_ : 1 | ||
>_11 : any | ||
|
||
=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/5.ts === | ||
0B0110_0110__ | ||
>0B0110_0110_ : 102 | ||
>_ : any | ||
|
||
=== tests/cases/conformance/parser/ecmascriptnext/numericSeparators/6.ts === | ||
0b___0111010_0101_1 | ||
>0b : 0 | ||
>___0111010_0101_1 : any | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
//// [parser.numericSeparators.decimal.ts] | ||
1_000_000_000 | ||
1.1_00_01 | ||
1e1_0 | ||
1e+1_0 | ||
1e-1_0 | ||
1.1e10_0 | ||
1.1e+10_0 | ||
1.1e-10_0 | ||
12_34_56 | ||
1_22_333 | ||
1_2.3_4 | ||
1_2.3_4e5_6 | ||
1_2.3_4e+5_6 | ||
1_2.3_4e-5_6 | ||
|
||
|
||
//// [parser.numericSeparators.decimal.js] | ||
1000000000; | ||
1.10001; | ||
10000000000; | ||
10000000000; | ||
1e-10; | ||
1.1e+100; | ||
1.1e+100; | ||
1.1e-100; | ||
123456; | ||
122333; | ||
12.34; | ||
1.234e+57; | ||
1.234e+57; | ||
1.234e-55; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
separators