[cfe] Remove ForElement from supertypes of PatternForElement

This CL makes the handling of the two elements to be free from a
specialized discipline where the implementor needed to remember that
PatternForElement should be handled every time ForElement is handled.

A test case is added for something that was fixed in one of the other
recent CLs.

Change-Id: I612667055d4036aa301060265ed63546067fcdb9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/378143
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/front_end/lib/src/kernel/collections.dart b/pkg/front_end/lib/src/kernel/collections.dart
index bcbf67c..660ee0a 100644
--- a/pkg/front_end/lib/src/kernel/collections.dart
+++ b/pkg/front_end/lib/src/kernel/collections.dart
@@ -264,10 +264,19 @@
 }
 
 /// A 'for' element in a list or set literal.
-class ForElement extends ControlFlowElement with ControlFlowElementMixin {
+class ForElement extends ControlFlowElement
+    with ControlFlowElementMixin
+    implements ForElementBase {
+  @override
   final List<VariableDeclaration> variables; // May be empty, but not null.
+
+  @override
   Expression? condition; // May be null.
+
+  @override
   final List<Expression> updates; // May be empty, but not null.
+
+  @override
   Expression body;
 
   ForElement(this.variables, this.condition, this.updates, this.body) {
@@ -555,9 +564,19 @@
   }
 }
 
+abstract interface class ForElementBase implements AuxiliaryExpression {
+  List<VariableDeclaration> get variables;
+
+  abstract Expression? condition;
+
+  List<Expression> get updates;
+
+  abstract Expression body;
+}
+
 class PatternForElement extends ControlFlowElementImpl
     with ControlFlowElementMixin
-    implements ForElement {
+    implements ForElementBase {
   PatternVariableDeclaration patternVariableDeclaration;
   List<VariableDeclaration> intermediateVariables;
 
@@ -1258,6 +1277,8 @@
                 isConvertibleToMapEntry(element.otherwise!));
       case ForElement():
         return isConvertibleToMapEntry(element.body);
+      case PatternForElement():
+        return isConvertibleToMapEntry(element.body);
       case ForInElement():
         return isConvertibleToMapEntry(element.body);
     }
diff --git a/pkg/front_end/lib/src/type_inference/inference_visitor.dart b/pkg/front_end/lib/src/type_inference/inference_visitor.dart
index ada0c75..7fe8e3ea 100644
--- a/pkg/front_end/lib/src/type_inference/inference_visitor.dart
+++ b/pkg/front_end/lib/src/type_inference/inference_visitor.dart
@@ -38,6 +38,7 @@
         ControlFlowElement,
         ControlFlowMapEntry,
         ForElement,
+        ForElementBase,
         ForInElement,
         ForInMapEntry,
         ForMapEntry,
@@ -2547,54 +2548,71 @@
         element);
   }
 
+  ExpressionInferenceResult _inferPatternForElement(
+      PatternForElement element,
+      DartType inferredTypeArgument,
+      Map<TreeNode, DartType> inferredSpreadTypes,
+      Map<Expression, DartType> inferredConditionTypes) {
+    int? stackBase;
+    assert(checkStackBase(element, stackBase = stackHeight));
+
+    PatternVariableDeclaration patternVariableDeclaration =
+        element.patternVariableDeclaration;
+    PatternVariableDeclarationAnalysisResult<DartType, DartType>
+        analysisResult = analyzePatternVariableDeclaration(
+            patternVariableDeclaration,
+            patternVariableDeclaration.pattern,
+            patternVariableDeclaration.initializer,
+            isFinal: patternVariableDeclaration.isFinal);
+    patternVariableDeclaration.matchedValueType =
+        analysisResult.initializerType;
+
+    assert(checkStack(element, stackBase, [
+      /* pattern = */ ValueKinds.Pattern,
+      /* initializer = */ ValueKinds.Expression,
+    ]));
+
+    Object? rewrite = popRewrite(NullValues.Expression);
+    if (!identical(patternVariableDeclaration.pattern, rewrite)) {
+      // Coverage-ignore-block(suite): Not run.
+      patternVariableDeclaration.pattern = (rewrite as Pattern)
+        ..parent = patternVariableDeclaration;
+    }
+
+    rewrite = popRewrite();
+    if (!identical(patternVariableDeclaration.initializer, rewrite)) {
+      patternVariableDeclaration.initializer = (rewrite as Expression)
+        ..parent = patternVariableDeclaration;
+    }
+
+    List<VariableDeclaration> declaredVariables =
+        patternVariableDeclaration.pattern.declaredVariables;
+    assert(declaredVariables.length == element.intermediateVariables.length);
+    assert(declaredVariables.length == element.variables.length);
+    for (int i = 0; i < declaredVariables.length; i++) {
+      DartType type = declaredVariables[i].type;
+      element.intermediateVariables[i].type = type;
+      element.variables[i].type = type;
+    }
+
+    return _inferForElementBase(element, inferredTypeArgument,
+        inferredSpreadTypes, inferredConditionTypes);
+  }
+
   ExpressionInferenceResult _inferForElement(
       ForElement element,
       DartType inferredTypeArgument,
       Map<TreeNode, DartType> inferredSpreadTypes,
       Map<Expression, DartType> inferredConditionTypes) {
-    if (element is PatternForElement) {
-      int? stackBase;
-      assert(checkStackBase(element, stackBase = stackHeight));
+    return _inferForElementBase(element, inferredTypeArgument,
+        inferredSpreadTypes, inferredConditionTypes);
+  }
 
-      PatternVariableDeclaration patternVariableDeclaration =
-          element.patternVariableDeclaration;
-      PatternVariableDeclarationAnalysisResult<DartType, DartType>
-          analysisResult = analyzePatternVariableDeclaration(
-              patternVariableDeclaration,
-              patternVariableDeclaration.pattern,
-              patternVariableDeclaration.initializer,
-              isFinal: patternVariableDeclaration.isFinal);
-      patternVariableDeclaration.matchedValueType =
-          analysisResult.initializerType;
-
-      assert(checkStack(element, stackBase, [
-        /* pattern = */ ValueKinds.Pattern,
-        /* initializer = */ ValueKinds.Expression,
-      ]));
-
-      Object? rewrite = popRewrite(NullValues.Expression);
-      if (!identical(patternVariableDeclaration.pattern, rewrite)) {
-        // Coverage-ignore-block(suite): Not run.
-        patternVariableDeclaration.pattern = (rewrite as Pattern)
-          ..parent = patternVariableDeclaration;
-      }
-
-      rewrite = popRewrite();
-      if (!identical(patternVariableDeclaration.initializer, rewrite)) {
-        patternVariableDeclaration.initializer = (rewrite as Expression)
-          ..parent = patternVariableDeclaration;
-      }
-
-      List<VariableDeclaration> declaredVariables =
-          patternVariableDeclaration.pattern.declaredVariables;
-      assert(declaredVariables.length == element.intermediateVariables.length);
-      assert(declaredVariables.length == element.variables.length);
-      for (int i = 0; i < declaredVariables.length; i++) {
-        DartType type = declaredVariables[i].type;
-        element.intermediateVariables[i].type = type;
-        element.variables[i].type = type;
-      }
-    }
+  ExpressionInferenceResult _inferForElementBase(
+      ForElementBase element,
+      DartType inferredTypeArgument,
+      Map<TreeNode, DartType> inferredSpreadTypes,
+      Map<Expression, DartType> inferredConditionTypes) {
     // TODO(johnniwinther): Use _visitStatements instead.
     List<VariableDeclaration>? variables;
     for (int index = 0; index < element.variables.length; index++) {
@@ -2727,6 +2745,9 @@
         case ForElement():
           return _inferForElement(element, inferredTypeArgument,
               inferredSpreadTypes, inferredConditionTypes);
+        case PatternForElement():
+          return _inferPatternForElement(element, inferredTypeArgument,
+              inferredSpreadTypes, inferredConditionTypes);
         case ForInElement():
           return _inferForInElement(element, inferredTypeArgument,
               inferredSpreadTypes, inferredConditionTypes);
@@ -2811,6 +2832,19 @@
           checkElement(body, item, typeArgument, inferredSpreadTypes,
               inferredConditionTypes);
         }
+      case PatternForElement(:Expression? condition, :Expression body):
+        if (condition != null) {
+          DartType conditionType = inferredConditionTypes[condition]!;
+          Expression assignableCondition = ensureAssignable(
+              coreTypes.boolRawType(Nullability.nonNullable),
+              conditionType,
+              condition);
+          item.condition = assignableCondition..parent = item;
+        }
+        if (body is ControlFlowElement) {
+          checkElement(body, item, typeArgument, inferredSpreadTypes,
+              inferredConditionTypes);
+        }
       case ForInElement(:Expression body):
         if (body is ControlFlowElement) {
           checkElement(body, item, typeArgument, inferredSpreadTypes,
@@ -3054,15 +3088,12 @@
               element, receiverType, elementType, result, body,
               isSet: isSet);
         case ForElement():
-          if (element is PatternForElement) {
-            _translatePatternForElement(
-                element, receiverType, elementType, result, body,
-                isSet: isSet);
-          } else {
-            _translateForElement(
-                element, receiverType, elementType, result, body,
-                isSet: isSet);
-          }
+          _translateForElement(element, receiverType, elementType, result, body,
+              isSet: isSet);
+        case PatternForElement():
+          _translatePatternForElement(
+              element, receiverType, elementType, result, body,
+              isSet: isSet);
         case ForInElement():
           _translateForInElement(
               element, receiverType, elementType, result, body,
@@ -3934,6 +3965,7 @@
           // Coverage-ignore(suite): Not run.
           case IfCaseElement():
           case ForElement():
+          case PatternForElement():
           case ForInElement():
             // Rejected earlier.
             problems.unhandled("${element.runtimeType}",
diff --git a/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart
new file mode 100644
index 0000000..e75c921
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart
@@ -0,0 +1,34 @@
+// 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.
+
+testList(dynamic x, dynamic list) {
+  return [if (list case [int _]) ...x else ...x];
+}
+
+testSet(dynamic x, dynamic list) {
+  return {0, if (list case [int _]) ...x else ...x};
+}
+
+main() {
+  testList([0], [0]);
+  expectThrows<TypeError>(() {testList(null, [0]);});
+
+  testSet([0], [0]);
+  expectThrows<TypeError>(() {testSet(null, [0]);});
+}
+
+expectThrows<Exception>(void Function() f) {
+  String? message;
+  try {
+    f();
+    message = "Expected the function to throw an exception, but it didn't.";
+  } on Exception catch (_) {
+    // Ok.
+  } on dynamic catch (e) {
+    message = "Expected the function to throw an exception of type '${Exception}', but got '${e.runtimeType}'.";
+  }
+  if (message != null) {
+    throw message;
+  }
+}
diff --git a/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.expect b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.expect
new file mode 100644
index 0000000..c9573aa
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.expect
@@ -0,0 +1,59 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "dart:collection" as col;
+
+static method testList(dynamic x, dynamic list) → dynamic {
+  return block {
+    final core::List<dynamic> #t1 = <dynamic>[];
+    {
+      final synthesized dynamic #0#0 = list;
+      if(#0#0 is core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int} =={core::num::==}{(core::Object) → core::bool} #C1 && #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic} is core::int)
+        #t1.{core::List::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+      else
+        #t1.{core::List::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+    }
+  } =>#t1;
+}
+static method testSet(dynamic x, dynamic list) → dynamic {
+  return block {
+    final core::Set<dynamic> #t2 = col::LinkedHashSet::•<dynamic>();
+    #t2.{core::Set::add}{Invariant}(0){(dynamic) → core::bool};
+    {
+      final synthesized dynamic #0#0 = list;
+      if(#0#0 is core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int} =={core::num::==}{(core::Object) → core::bool} #C1 && #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic} is core::int)
+        #t2.{core::Set::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+      else
+        #t2.{core::Set::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+    }
+  } =>#t2;
+}
+static method main() → dynamic {
+  self::testList(<core::int>[0], <core::int>[0]);
+  self::expectThrows<core::TypeError>(() → void {
+    self::testList(null, <core::int>[0]);
+  });
+  self::testSet(<core::int>[0], <core::int>[0]);
+  self::expectThrows<core::TypeError>(() → void {
+    self::testSet(null, <core::int>[0]);
+  });
+}
+static method expectThrows<Exception extends core::Object? = dynamic>(() → void f) → dynamic {
+  core::String? message;
+  try {
+    f(){() → void};
+    message = "Expected the function to throw an exception, but it didn't.";
+  }
+  on self::expectThrows::Exception% catch(final self::expectThrows::Exception% _) {
+  }
+  on dynamic catch(final dynamic e) {
+    message = "Expected the function to throw an exception of type '${self::expectThrows::Exception%}', but got '${e.{core::Object::runtimeType}{<object>}.{core::Type}}'.";
+  }
+  if(!(message == null)) {
+    throw message{core::String};
+  }
+}
+
+constants  {
+  #C1 = 1
+}
diff --git a/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.modular.expect b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.modular.expect
new file mode 100644
index 0000000..c9573aa
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.modular.expect
@@ -0,0 +1,59 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "dart:collection" as col;
+
+static method testList(dynamic x, dynamic list) → dynamic {
+  return block {
+    final core::List<dynamic> #t1 = <dynamic>[];
+    {
+      final synthesized dynamic #0#0 = list;
+      if(#0#0 is core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int} =={core::num::==}{(core::Object) → core::bool} #C1 && #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic} is core::int)
+        #t1.{core::List::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+      else
+        #t1.{core::List::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+    }
+  } =>#t1;
+}
+static method testSet(dynamic x, dynamic list) → dynamic {
+  return block {
+    final core::Set<dynamic> #t2 = col::LinkedHashSet::•<dynamic>();
+    #t2.{core::Set::add}{Invariant}(0){(dynamic) → core::bool};
+    {
+      final synthesized dynamic #0#0 = list;
+      if(#0#0 is core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int} =={core::num::==}{(core::Object) → core::bool} #C1 && #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic} is core::int)
+        #t2.{core::Set::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+      else
+        #t2.{core::Set::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+    }
+  } =>#t2;
+}
+static method main() → dynamic {
+  self::testList(<core::int>[0], <core::int>[0]);
+  self::expectThrows<core::TypeError>(() → void {
+    self::testList(null, <core::int>[0]);
+  });
+  self::testSet(<core::int>[0], <core::int>[0]);
+  self::expectThrows<core::TypeError>(() → void {
+    self::testSet(null, <core::int>[0]);
+  });
+}
+static method expectThrows<Exception extends core::Object? = dynamic>(() → void f) → dynamic {
+  core::String? message;
+  try {
+    f(){() → void};
+    message = "Expected the function to throw an exception, but it didn't.";
+  }
+  on self::expectThrows::Exception% catch(final self::expectThrows::Exception% _) {
+  }
+  on dynamic catch(final dynamic e) {
+    message = "Expected the function to throw an exception of type '${self::expectThrows::Exception%}', but got '${e.{core::Object::runtimeType}{<object>}.{core::Type}}'.";
+  }
+  if(!(message == null)) {
+    throw message{core::String};
+  }
+}
+
+constants  {
+  #C1 = 1
+}
diff --git a/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.outline.expect b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.outline.expect
new file mode 100644
index 0000000..17a502b
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.outline.expect
@@ -0,0 +1,12 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method testList(dynamic x, dynamic list) → dynamic
+  ;
+static method testSet(dynamic x, dynamic list) → dynamic
+  ;
+static method main() → dynamic
+  ;
+static method expectThrows<Exception extends core::Object? = dynamic>(() → void f) → dynamic
+  ;
diff --git a/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.transformed.expect b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.transformed.expect
new file mode 100644
index 0000000..21e09bf
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.strong.transformed.expect
@@ -0,0 +1,59 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "dart:collection" as col;
+
+static method testList(dynamic x, dynamic list) → dynamic {
+  return block {
+    final core::List<dynamic> #t1 = core::_GrowableList::•<dynamic>(0);
+    {
+      final synthesized dynamic #0#0 = list;
+      if(#0#0 is core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int} =={core::num::==}{(core::Object) → core::bool} #C1 && #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic} is core::int)
+        #t1.{core::List::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+      else
+        #t1.{core::List::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+    }
+  } =>#t1;
+}
+static method testSet(dynamic x, dynamic list) → dynamic {
+  return block {
+    final core::Set<dynamic> #t2 = new col::_Set::•<dynamic>();
+    #t2.{core::Set::add}{Invariant}(0){(dynamic) → core::bool};
+    {
+      final synthesized dynamic #0#0 = list;
+      if(#0#0 is core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int} =={core::num::==}{(core::Object) → core::bool} #C1 && #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic} is core::int)
+        #t2.{core::Set::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+      else
+        #t2.{core::Set::addAll}{Invariant}(x as{TypeError,ForDynamic} core::Iterable<dynamic>){(core::Iterable<dynamic>) → void};
+    }
+  } =>#t2;
+}
+static method main() → dynamic {
+  self::testList(core::_GrowableList::_literal1<core::int>(0), core::_GrowableList::_literal1<core::int>(0));
+  self::expectThrows<core::TypeError>(() → void {
+    self::testList(null, core::_GrowableList::_literal1<core::int>(0));
+  });
+  self::testSet(core::_GrowableList::_literal1<core::int>(0), core::_GrowableList::_literal1<core::int>(0));
+  self::expectThrows<core::TypeError>(() → void {
+    self::testSet(null, core::_GrowableList::_literal1<core::int>(0));
+  });
+}
+static method expectThrows<Exception extends core::Object? = dynamic>(() → void f) → dynamic {
+  core::String? message;
+  try {
+    f(){() → void};
+    message = "Expected the function to throw an exception, but it didn't.";
+  }
+  on self::expectThrows::Exception% catch(final self::expectThrows::Exception% _) {
+  }
+  on dynamic catch(final dynamic e) {
+    message = "Expected the function to throw an exception of type '${self::expectThrows::Exception%}', but got '${e.{core::Object::runtimeType}{<object>}.{core::Type}}'.";
+  }
+  if(!(message == null)) {
+    throw message{core::String};
+  }
+}
+
+constants  {
+  #C1 = 1
+}
diff --git a/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.textual_outline.expect b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.textual_outline.expect
new file mode 100644
index 0000000..d264d11
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+testList(dynamic x, dynamic list) {}
+
+testSet(dynamic x, dynamic list) {}
+
+main() {}
+
+expectThrows<Exception>(void Function() f) {}
diff --git a/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..93b3c14
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/coercion_in_if_case_element.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+expectThrows<Exception>(void Function() f) {}
+
+main() {}
+
+testList(dynamic x, dynamic list) {}
+
+testSet(dynamic x, dynamic list) {}