[CFE/Analyzer/parser] Fix crash upon double with separators and missing number after e
E.g. the user typing "1_234e" (and having yet to type a number) would
crash the analyzer.
Change-Id: Iff71c274a5e4719b3c8877ddbc9775d8d091c42f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/437240
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/scanner/abstract_scanner.dart b/pkg/_fe_analyzer_shared/lib/src/scanner/abstract_scanner.dart
index b17ddd2..df46757 100644
--- a/pkg/_fe_analyzer_shared/lib/src/scanner/abstract_scanner.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/scanner/abstract_scanner.dart
@@ -1696,7 +1696,9 @@
} else {
if (!hasExponentDigits) {
appendSyntheticSubstringToken(
- TokenType.DOUBLE,
+ hasSeparators
+ ? TokenType.DOUBLE_WITH_SEPARATORS
+ : TokenType.DOUBLE,
start,
/* asciiOnly = */ true,
'0',
diff --git a/pkg/analyzer/test/src/diagnostics/number_literals_with_separators_test.dart b/pkg/analyzer/test/src/diagnostics/number_literals_with_separators_test.dart
new file mode 100644
index 0000000..d4d8709
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/number_literals_with_separators_test.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/src/dart/error/syntactic_errors.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(NumberLiteralsWithSeparatorsTest);
+ });
+}
+
+@reflectiveTest
+class NumberLiteralsWithSeparatorsTest extends PubPackageResolutionTest {
+ Future<void> assertHasErrors(String code) async {
+ addTestFile(code);
+ await resolveTestFile();
+ expect(result.diagnostics, isNotEmpty);
+ }
+
+ Future<void> test_double_with_separators_and_e() async {
+ await assertNoErrorsInCode('double x = 1.234_567e2;');
+ }
+
+ Future<void> test_missing_number_after_e_1() async {
+ await assertErrorsInCode('dynamic x = 1_234_567e;', [
+ error(ScannerErrorCode.MISSING_DIGIT, 21, 1),
+ ]);
+ }
+
+ Future<void> test_missing_number_after_e_2() async {
+ await assertErrorsInCode('dynamic x = 1.234_567e;', [
+ error(ScannerErrorCode.MISSING_DIGIT, 21, 1),
+ ]);
+ }
+
+ Future<void> test_other_erroneous_1() async {
+ await assertHasErrors('dynamic x = 1_1e;');
+ }
+
+ Future<void> test_other_erroneous_2() async {
+ await assertHasErrors('dynamic x = 1_e;');
+ }
+
+ Future<void> test_other_erroneous_3() async {
+ await assertHasErrors('dynamic x = 1e_;');
+ }
+
+ Future<void> test_other_erroneous_4() async {
+ await assertHasErrors('dynamic x = 1e-_;');
+ }
+
+ Future<void> test_other_erroneous_5() async {
+ await assertHasErrors('dynamic x = 1e-_1;');
+ }
+
+ Future<void> test_other_erroneous_6() async {
+ await assertHasErrors('dynamic x = 1e+_;');
+ }
+
+ Future<void> test_other_erroneous_7() async {
+ await assertHasErrors('dynamic x = 1e+_1;');
+ }
+
+ Future<void> test_simple_double_with_separators() async {
+ await assertNoErrorsInCode('double x = 1.234_567;');
+ }
+
+ Future<void> test_simple_int_with_separators() async {
+ await assertNoErrorsInCode('int x = 1_234_567;');
+ }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 58159d3..f4e687b 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -692,6 +692,7 @@
as nullable_type_in_implements_clause;
import 'nullable_type_in_on_clause_test.dart' as nullable_type_in_on_clause;
import 'nullable_type_in_with_clause_test.dart' as nullable_type_in_with_clause;
+import 'number_literals_with_separators_test.dart' as number_literals_with_separators;
import 'object_cannot_extend_another_class_test.dart'
as object_cannot_extend_another_class;
import 'obsolete_colon_for_default_value_test.dart'
@@ -1385,6 +1386,7 @@
nullable_type_in_implements_clause.main();
nullable_type_in_on_clause.main();
nullable_type_in_with_clause.main();
+ number_literals_with_separators.main();
object_cannot_extend_another_class.main();
obsolete_colon_for_default_value.main();
on_repeated.main();
diff --git a/pkg/front_end/test/precedence_info_test.dart b/pkg/front_end/test/precedence_info_test.dart
index 8ef6bc3..8d22392 100644
--- a/pkg/front_end/test/precedence_info_test.dart
+++ b/pkg/front_end/test/precedence_info_test.dart
@@ -398,8 +398,14 @@
}
void test_type() {
- void assertLexeme(String source, TokenType tt) {
+ void assertLexeme(String source, TokenType tt,
+ {bool skipErrorTokens = false}) {
var token = scanString(source, includeComments: true).tokens;
+ if (skipErrorTokens) {
+ while (token is ErrorToken) {
+ token = token.next!;
+ }
+ }
expect(token.type, same(tt), reason: source);
}
@@ -413,5 +419,9 @@
assertLexeme('#!/', TokenType.SCRIPT_TAG);
assertLexeme('foo', TokenType.IDENTIFIER);
assertLexeme('"foo"', TokenType.STRING);
+
+ // Invalid but recovers nicely.
+ assertLexeme('1_e', TokenType.DOUBLE_WITH_SEPARATORS,
+ skipErrorTokens: true);
}
}
diff --git a/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart
new file mode 100644
index 0000000..872e6ea
--- /dev/null
+++ b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+void foo() {
+ print(123_456e);
+ print(1.234_567e);
+}
\ No newline at end of file
diff --git a/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.expect b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.expect
new file mode 100644
index 0000000..faf37dd
--- /dev/null
+++ b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.expect
@@ -0,0 +1,21 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:6:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
+// Make sure there is an exponent, and remove any whitespace before it.
+// print(123_456e);
+// ^
+//
+// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:7:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
+// Make sure there is an exponent, and remove any whitespace before it.
+// print(1.234_567e);
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static method foo() → void {
+ core::print(123456.0);
+ core::print(1.234567);
+}
diff --git a/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.modular.expect b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.modular.expect
new file mode 100644
index 0000000..faf37dd
--- /dev/null
+++ b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.modular.expect
@@ -0,0 +1,21 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:6:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
+// Make sure there is an exponent, and remove any whitespace before it.
+// print(123_456e);
+// ^
+//
+// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:7:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
+// Make sure there is an exponent, and remove any whitespace before it.
+// print(1.234_567e);
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static method foo() → void {
+ core::print(123456.0);
+ core::print(1.234567);
+}
diff --git a/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.outline.expect b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.outline.expect
new file mode 100644
index 0000000..0125757
--- /dev/null
+++ b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.outline.expect
@@ -0,0 +1,18 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:6:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
+// Make sure there is an exponent, and remove any whitespace before it.
+// print(123_456e);
+// ^
+//
+// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:7:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
+// Make sure there is an exponent, and remove any whitespace before it.
+// print(1.234_567e);
+// ^
+//
+import self as self;
+
+static method foo() → void
+ ;
diff --git a/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.transformed.expect b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.transformed.expect
new file mode 100644
index 0000000..faf37dd
--- /dev/null
+++ b/pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart.strong.transformed.expect
@@ -0,0 +1,21 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:6:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
+// Make sure there is an exponent, and remove any whitespace before it.
+// print(123_456e);
+// ^
+//
+// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:7:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
+// Make sure there is an exponent, and remove any whitespace before it.
+// print(1.234_567e);
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static method foo() → void {
+ core::print(123456.0);
+ core::print(1.234567);
+}
diff --git a/pkg/front_end/testcases/textual_outline.status b/pkg/front_end/testcases/textual_outline.status
index d117142..0d70e93 100644
--- a/pkg/front_end/testcases/textual_outline.status
+++ b/pkg/front_end/testcases/textual_outline.status
@@ -28,6 +28,7 @@
super_parameters/var_before_super: FormatterCrash
triple_shift/invalid_operator: FormatterCrash
+coverage/digit-separators/separators_errors_test: EmptyOutput
general/error_recovery/issue_38415.crash: EmptyOutput
general/error_recovery/issue_39024.crash: EmptyOutput
general/error_recovery/issue_39058.crash: EmptyOutput
diff --git a/tests/language/number/separators_error_test.dart b/tests/language/number/separators_error_test.dart
index b32cb4f..5663422 100644
--- a/tests/language/number/separators_error_test.dart
+++ b/tests/language/number/separators_error_test.dart
@@ -148,4 +148,10 @@
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_GETTER
// [cfe] The getter '_0e' isn't defined for the type 'int'.
+
+ x = 1.234_456e;
+ // ^
+ // [cfe] Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
+ // ^
+ // [analyzer] SYNTACTIC_ERROR.MISSING_DIGIT
}