[CFE] Fix crash when using pattern in for loop when language version is too low

Fixes https://github.com/dart-lang/sdk/issues/59656

Change-Id: If59bb9271b8bf1db70ae63101f95444e3eb7cdc2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/399105
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
diff --git a/pkg/front_end/lib/src/kernel/body_builder.dart b/pkg/front_end/lib/src/kernel/body_builder.dart
index 781df23..5109399 100644
--- a/pkg/front_end/lib/src/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/kernel/body_builder.dart
@@ -7601,7 +7601,7 @@
   @override
   void handleForInLoopParts(Token? awaitToken, Token forToken,
       Token leftParenthesis, Token? patternKeyword, Token inKeyword) {
-    debugEvent("ForIntLoopParts");
+    debugEvent("ForInLoopParts");
     assert(checkState(forToken, [
       unionOfKinds([
         ValueKinds.Expression,
@@ -7614,6 +7614,7 @@
         ValueKinds.ProblemBuilder,
         ValueKinds.Pattern,
         ValueKinds.Statement, // Variable for non-pattern for-in loop.
+        ValueKinds.ParserRecovery,
       ]),
     ]));
     Object expression = pop() as Object;
@@ -7755,6 +7756,9 @@
             isFinal: false);
       } else if (lvalue is AmbiguousBuilder) {
         elements.expressionProblem = toValue(lvalue);
+      } else if (lvalue is ParserRecovery) {
+        elements.expressionProblem = buildProblem(
+            cfe.messageSyntheticToken, lvalue.charOffset, noLength);
       } else {
         Message message = forest.isVariablesDeclaration(lvalue)
             ? cfe.messageForInLoopExactlyOneVariable
@@ -7805,6 +7809,7 @@
         ValueKinds.ProblemBuilder,
         ValueKinds.Pattern,
         ValueKinds.Statement,
+        ValueKinds.ParserRecovery,
       ]),
     ]));
     Statement body = popStatement(endToken);
diff --git a/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart
new file mode 100644
index 0000000..3be5d69
--- /dev/null
+++ b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2024, 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.
+
+// @dart=2.12
+
+void foo() {
+  var repoPaths = [(user: "a", repo: "b")];
+  for (var (:user, :repo) in repoPaths)  {
+    print(user);
+    print(repo);
+  }
+}
diff --git a/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.expect b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.expect
new file mode 100644
index 0000000..8365ab2
--- /dev/null
+++ b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.expect
@@ -0,0 +1,63 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:8:20: Error: The 'records' language feature is disabled for this library.
+// Try removing the `@dart=` annotation or setting the language version to 3.0 or higher.
+//   var repoPaths = [(user: "a", repo: "b")];
+//                    ^
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:5:1: Context: This is the annotation that opts out this library from the 'records' language feature.
+// // @dart=2.12
+// ^^^^^^^^^^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:12: Error: Expected an identifier, but got '('.
+// Try inserting an identifier before '('.
+//   for (var (:user, :repo) in repoPaths)  {
+//            ^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:13: Error: For-in loops use 'in' rather than a colon.
+// Try replacing the colon with the keyword 'in'.
+//   for (var (:user, :repo) in repoPaths)  {
+//             ^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:18: Error: Expected ')' before this.
+//   for (var (:user, :repo) in repoPaths)  {
+//                  ^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:14: Error: Undefined name 'user'.
+//   for (var (:user, :repo) in repoPaths)  {
+//              ^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:10:11: Error: Undefined name 'user'.
+//     print(user);
+//           ^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:11:11: Error: Undefined name 'repo'.
+//     print(repo);
+//           ^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:13: Error: This couldn't be parsed.
+//   for (var (:user, :repo) in repoPaths)  {
+//             ^
+//
+import self as self;
+import "dart:core" as core;
+
+static method foo() → void {
+  core::List<dynamic> repoPaths = <dynamic>[let final core::String #t1 = "a" in invalid-expression "This requires the experimental 'records' language feature to be enabled."];
+  {
+    invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:13: Error: This couldn't be parsed.
+  for (var (:user, :repo) in repoPaths)  {
+            ^";
+    for (final dynamic #t2 in invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:14: Error: Undefined name 'user'.
+  for (var (:user, :repo) in repoPaths)  {
+             ^^^^") {
+      core::print(invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:10:11: Error: Undefined name 'user'.
+    print(user);
+          ^^^^");
+      core::print(invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:11:11: Error: Undefined name 'repo'.
+    print(repo);
+          ^^^^");
+    }
+  }
+}
diff --git a/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.modular.expect b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.modular.expect
new file mode 100644
index 0000000..8365ab2
--- /dev/null
+++ b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.modular.expect
@@ -0,0 +1,63 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:8:20: Error: The 'records' language feature is disabled for this library.
+// Try removing the `@dart=` annotation or setting the language version to 3.0 or higher.
+//   var repoPaths = [(user: "a", repo: "b")];
+//                    ^
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:5:1: Context: This is the annotation that opts out this library from the 'records' language feature.
+// // @dart=2.12
+// ^^^^^^^^^^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:12: Error: Expected an identifier, but got '('.
+// Try inserting an identifier before '('.
+//   for (var (:user, :repo) in repoPaths)  {
+//            ^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:13: Error: For-in loops use 'in' rather than a colon.
+// Try replacing the colon with the keyword 'in'.
+//   for (var (:user, :repo) in repoPaths)  {
+//             ^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:18: Error: Expected ')' before this.
+//   for (var (:user, :repo) in repoPaths)  {
+//                  ^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:14: Error: Undefined name 'user'.
+//   for (var (:user, :repo) in repoPaths)  {
+//              ^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:10:11: Error: Undefined name 'user'.
+//     print(user);
+//           ^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:11:11: Error: Undefined name 'repo'.
+//     print(repo);
+//           ^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:13: Error: This couldn't be parsed.
+//   for (var (:user, :repo) in repoPaths)  {
+//             ^
+//
+import self as self;
+import "dart:core" as core;
+
+static method foo() → void {
+  core::List<dynamic> repoPaths = <dynamic>[let final core::String #t1 = "a" in invalid-expression "This requires the experimental 'records' language feature to be enabled."];
+  {
+    invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:13: Error: This couldn't be parsed.
+  for (var (:user, :repo) in repoPaths)  {
+            ^";
+    for (final dynamic #t2 in invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:14: Error: Undefined name 'user'.
+  for (var (:user, :repo) in repoPaths)  {
+             ^^^^") {
+      core::print(invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:10:11: Error: Undefined name 'user'.
+    print(user);
+          ^^^^");
+      core::print(invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:11:11: Error: Undefined name 'repo'.
+    print(repo);
+          ^^^^");
+    }
+  }
+}
diff --git a/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.outline.expect b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.outline.expect
new file mode 100644
index 0000000..4a8528a
--- /dev/null
+++ b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.outline.expect
@@ -0,0 +1,5 @@
+library;
+import self as self;
+
+static method foo() → void
+  ;
diff --git a/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.transformed.expect b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.transformed.expect
new file mode 100644
index 0000000..e78cad9
--- /dev/null
+++ b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.strong.transformed.expect
@@ -0,0 +1,69 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:8:20: Error: The 'records' language feature is disabled for this library.
+// Try removing the `@dart=` annotation or setting the language version to 3.0 or higher.
+//   var repoPaths = [(user: "a", repo: "b")];
+//                    ^
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:5:1: Context: This is the annotation that opts out this library from the 'records' language feature.
+// // @dart=2.12
+// ^^^^^^^^^^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:12: Error: Expected an identifier, but got '('.
+// Try inserting an identifier before '('.
+//   for (var (:user, :repo) in repoPaths)  {
+//            ^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:13: Error: For-in loops use 'in' rather than a colon.
+// Try replacing the colon with the keyword 'in'.
+//   for (var (:user, :repo) in repoPaths)  {
+//             ^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:18: Error: Expected ')' before this.
+//   for (var (:user, :repo) in repoPaths)  {
+//                  ^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:14: Error: Undefined name 'user'.
+//   for (var (:user, :repo) in repoPaths)  {
+//              ^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:10:11: Error: Undefined name 'user'.
+//     print(user);
+//           ^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:11:11: Error: Undefined name 'repo'.
+//     print(repo);
+//           ^^^^
+//
+// pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:13: Error: This couldn't be parsed.
+//   for (var (:user, :repo) in repoPaths)  {
+//             ^
+//
+import self as self;
+import "dart:core" as core;
+
+static method foo() → void {
+  core::List<dynamic> repoPaths = core::_GrowableList::_literal1<dynamic>(let final core::String #t1 = "a" in invalid-expression "This requires the experimental 'records' language feature to be enabled.");
+  {
+    invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:13: Error: This couldn't be parsed.
+  for (var (:user, :repo) in repoPaths)  {
+            ^";
+    {
+      synthesized core::Iterator<Never> :sync-for-iterator = invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:9:14: Error: Undefined name 'user'.
+  for (var (:user, :repo) in repoPaths)  {
+             ^^^^".{core::Iterable::iterator}{core::Iterator<Never>};
+      for (; :sync-for-iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
+        final dynamic #t2 = :sync-for-iterator.{core::Iterator::current}{Never};
+        {
+          core::print(invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:10:11: Error: Undefined name 'user'.
+    print(user);
+          ^^^^");
+          core::print(invalid-expression "pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart:11:11: Error: Undefined name 'repo'.
+    print(repo);
+          ^^^^");
+        }
+      }
+    }
+  }
+}
diff --git a/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.textual_outline.expect b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.textual_outline.expect
new file mode 100644
index 0000000..7bc3030
--- /dev/null
+++ b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.textual_outline.expect
@@ -0,0 +1,3 @@
+// @dart = 2.12
+
+void foo() {}
diff --git a/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..7bc3030
--- /dev/null
+++ b/pkg/front_end/testcases/regress/use_pattern_in_for_loop_when_language_version_is_too_low.dart.textual_outline_modelled.expect
@@ -0,0 +1,3 @@
+// @dart = 2.12
+
+void foo() {}