[cfe] Add basic support for if-case elements in lists

Part of https://github.com/dart-lang/sdk/issues/49749

Change-Id: Ib5dd6d1159c09856971ed5b39b2499971044160a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282860
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index 1df3998..a4c0605 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -3590,7 +3590,7 @@
       assert(checkState(token, [ValueKinds.Scope]));
       Scope thenScope = scope.createNestedScope(
           debugName: "then body", kind: ScopeKind.statementLocalScope);
-      exitLocalScope();
+      exitLocalScope(expectedScopeKinds: const [ScopeKind.ifCaseHead]);
       push(condition);
       enterLocalScope(thenScope);
     } else {
@@ -6473,10 +6473,39 @@
 
   @override
   void handleThenControlFlow(Token token) {
+    assert(checkState(token, [ValueKinds.Condition]));
     // This is matched by the call to [deferNode] in
     // [handleElseControlFlow] and by the call to [endNode] in
     // [endIfControlFlow].
     typeInferrer.assignedVariables.beginNode();
+
+    Condition condition = pop() as Condition;
+    PatternGuard? patternGuard = condition.patternGuard;
+    if (patternGuard != null) {
+      if (patternGuard.guard != null) {
+        Scope thenScope = scope.createNestedScope(
+            debugName: "then-control-flow", kind: ScopeKind.ifElement);
+        exitLocalScope(expectedScopeKinds: const [ScopeKind.ifCaseHead]);
+        enterLocalScope(thenScope);
+      } else {
+        createAndEnterLocalScope(
+            debugName: "if-case-head", kind: ScopeKind.ifCaseHead);
+        for (VariableDeclaration variable
+            in patternGuard.pattern.declaredVariables) {
+          declareVariable(variable, scope);
+          typeInferrer.assignedVariables.declare(variable);
+        }
+        Scope thenScope = scope.createNestedScope(
+            debugName: "then-control-flow", kind: ScopeKind.ifElement);
+        exitLocalScope(expectedScopeKinds: const [ScopeKind.ifCaseHead]);
+        enterLocalScope(thenScope);
+      }
+    } else {
+      createAndEnterLocalScope(
+          debugName: "then-control-flow", kind: ScopeKind.ifElement);
+    }
+    push(condition);
+
     super.handleThenControlFlow(token);
   }
 
@@ -6504,22 +6533,33 @@
         ValueKinds.MapLiteralEntry,
       ]),
       ValueKinds.Condition,
+      ValueKinds.Scope,
       ValueKinds.Token,
     ]));
 
     Object? entry = pop();
     Condition condition = pop() as Condition;
-    assert(condition.patternGuard == null,
-        "Unexpected pattern in control flow if: ${condition.patternGuard}.");
+    exitLocalScope(expectedScopeKinds: const [ScopeKind.ifElement]);
     Token ifToken = pop() as Token;
 
+    PatternGuard? patternGuard = condition.patternGuard;
     TreeNode node;
     if (entry is MapLiteralEntry) {
-      node = forest.createIfMapEntry(
-          offsetForToken(ifToken), condition.expression, entry);
+      if (patternGuard == null) {
+        node = forest.createIfMapEntry(
+            offsetForToken(ifToken), condition.expression, entry);
+      } else {
+        node = forest.createIfCaseMapEntry(
+            offsetForToken(ifToken), condition.expression, patternGuard, entry);
+      }
     } else {
-      node = forest.createIfElement(
-          offsetForToken(ifToken), condition.expression, toValue(entry));
+      if (patternGuard == null) {
+        node = forest.createIfElement(
+            offsetForToken(ifToken), condition.expression, toValue(entry));
+      } else {
+        node = forest.createIfCaseElement(offsetForToken(ifToken),
+            condition.expression, patternGuard, toValue(entry));
+      }
     }
     push(node);
     // This is matched by the call to [beginNode] in
@@ -6545,6 +6585,7 @@
       ]),
       ValueKinds.AssignedVariablesNodeInfo,
       ValueKinds.Condition,
+      ValueKinds.Scope,
       ValueKinds.Token,
     ]));
 
@@ -6553,8 +6594,7 @@
     AssignedVariablesNodeInfo assignedVariablesInfo =
         pop() as AssignedVariablesNodeInfo;
     Condition condition = pop() as Condition; // parenthesized expression
-    assert(condition.patternGuard == null,
-        "Unexpected pattern in control flow if: ${condition.patternGuard}.");
+    exitLocalScope(expectedScopeKinds: const [ScopeKind.ifElement]);
     Token ifToken = pop() as Token;
 
     TreeNode node;
@@ -6612,8 +6652,17 @@
           ..fileOffset = offsetForToken(ifToken);
       }
     } else {
-      node = forest.createIfElement(offsetForToken(ifToken),
-          condition.expression, toValue(thenEntry), toValue(elseEntry));
+      if (condition.patternGuard == null) {
+        node = forest.createIfElement(offsetForToken(ifToken),
+            condition.expression, toValue(thenEntry), toValue(elseEntry));
+      } else {
+        node = forest.createIfCaseElement(
+            offsetForToken(ifToken),
+            condition.expression,
+            condition.patternGuard!,
+            toValue(thenEntry),
+            toValue(elseEntry));
+      }
     }
     push(node);
     // This is matched by the call to [deferNode] in
diff --git a/pkg/front_end/lib/src/fasta/kernel/forest.dart b/pkg/front_end/lib/src/fasta/kernel/forest.dart
index d9468cd..076a278 100644
--- a/pkg/front_end/lib/src/fasta/kernel/forest.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/forest.dart
@@ -272,6 +272,15 @@
     return new IfElement(condition, then, otherwise)..fileOffset = fileOffset;
   }
 
+  Expression createIfCaseElement(int fileOffset, Expression expression,
+      PatternGuard patternGuard, Expression then,
+      [Expression? otherwise]) {
+    // ignore: unnecessary_null_comparison
+    assert(fileOffset != null);
+    return new IfCaseElement(expression, patternGuard, then, otherwise)
+      ..fileOffset = fileOffset;
+  }
+
   MapLiteralEntry createIfMapEntry(
       int fileOffset, Expression condition, MapLiteralEntry then,
       [MapLiteralEntry? otherwise]) {
@@ -280,6 +289,15 @@
     return new IfMapEntry(condition, then, otherwise)..fileOffset = fileOffset;
   }
 
+  MapLiteralEntry createIfCaseMapEntry(int fileOffset, Expression expression,
+      PatternGuard patternGuard, MapLiteralEntry then,
+      [MapLiteralEntry? otherwise]) {
+    // ignore: unnecessary_null_comparison
+    assert(fileOffset != null);
+    return new IfCaseMapEntry(expression, patternGuard, then, otherwise)
+      ..fileOffset = fileOffset;
+  }
+
   ForElement createForElement(
       int fileOffset,
       List<VariableDeclaration> variables,
diff --git a/pkg/front_end/lib/src/fasta/kernel/internal_ast.dart b/pkg/front_end/lib/src/fasta/kernel/internal_ast.dart
index 09cfba0..503b271 100644
--- a/pkg/front_end/lib/src/fasta/kernel/internal_ast.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/internal_ast.dart
@@ -32,6 +32,8 @@
 import '../type_inference/inference_results.dart';
 import '../type_inference/type_schema.dart' show UnknownType;
 
+import 'collections.dart';
+
 typedef SharedMatchContext = shared
     .MatchContext<TreeNode, Expression, Pattern, DartType, VariableDeclaration>;
 
@@ -4805,3 +4807,90 @@
   /// Erroneous property access.
   Error,
 }
+
+class IfCaseElement extends InternalExpression with ControlFlowElement {
+  Expression expression;
+  PatternGuard patternGuard;
+  Expression then;
+  Expression? otherwise;
+  late List<Statement> replacement;
+
+  IfCaseElement(this.expression, this.patternGuard, this.then, this.otherwise) {
+    expression.parent = this;
+    patternGuard.parent = this;
+    then.parent = this;
+    otherwise?.parent = this;
+  }
+
+  @override
+  ExpressionInferenceResult acceptInference(
+      InferenceVisitorImpl visitor, DartType typeContext) {
+    throw new UnsupportedError("IfCaseElement.acceptInference");
+  }
+
+  @override
+  void toTextInternal(AstPrinter printer) {
+    printer.write('if (');
+    printer.writeExpression(expression);
+    printer.write(' case ');
+    patternGuard.toTextInternal(printer);
+    printer.write(') ');
+    printer.writeExpression(then);
+    if (otherwise != null) {
+      printer.write(' else ');
+      printer.writeExpression(otherwise!);
+    }
+  }
+
+  @override
+  MapLiteralEntry? toMapLiteralEntry(
+      void Function(TreeNode from, TreeNode to) onConvertElement) {
+    // TODO(cstefantsova): implement toMapLiteralEntry
+    throw new UnimplementedError();
+  }
+
+  @override
+  String toString() {
+    return "IfCaseElement(${toStringInternal()})";
+  }
+}
+
+class IfCaseMapEntry extends TreeNode
+    with InternalTreeNode, ControlFlowMapEntry {
+  Expression expression;
+  PatternGuard patternGuard;
+  MapLiteralEntry then;
+  MapLiteralEntry? otherwise;
+
+  IfCaseMapEntry(
+      this.expression, this.patternGuard, this.then, this.otherwise) {
+    expression.parent = this;
+    patternGuard.parent = this;
+    then.parent = this;
+    otherwise?.parent = this;
+  }
+
+  ExpressionInferenceResult acceptInference(
+      InferenceVisitorImpl visitor, DartType typeContext) {
+    throw new UnsupportedError("IfCaseMapEntry.acceptInference");
+  }
+
+  @override
+  void toTextInternal(AstPrinter printer) {
+    printer.write('if (');
+    expression.toTextInternal(printer);
+    printer.write(' case ');
+    patternGuard.toTextInternal(printer);
+    printer.write(') ');
+    then.toTextInternal(printer);
+    if (otherwise != null) {
+      printer.write(' else ');
+      otherwise!.toTextInternal(printer);
+    }
+  }
+
+  @override
+  String toString() {
+    return "IfCaseMapEntry(${toStringInternal()})";
+  }
+}
diff --git a/pkg/front_end/lib/src/fasta/scope.dart b/pkg/front_end/lib/src/fasta/scope.dart
index 13c8972..9692c96 100644
--- a/pkg/front_end/lib/src/fasta/scope.dart
+++ b/pkg/front_end/lib/src/fasta/scope.dart
@@ -51,6 +51,9 @@
   /// Scope of the head of the if-case statement
   ifCaseHead,
 
+  /// Scope of an if-element in a collection
+  ifElement,
+
   /// Scope for the initializers of generative constructors
   initializers,
 
@@ -137,7 +140,7 @@
   Scope? get parent => _parent;
 
   @override
-  String toString() => "Scope($classNameOrDebugName, ${_local.keys})";
+  String toString() => "Scope(${kind}, $classNameOrDebugName, ${_local.keys})";
 }
 
 class Scope extends MutableScope {
diff --git a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart
index 0be280f..80df8bd 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart
@@ -2193,6 +2193,100 @@
                   isNonNullableByDefault:
                       libraryBuilder.isNonNullableByDefault),
           element);
+    } else if (element is IfCaseElement) {
+      int? stackBase;
+      assert(checkStackBase(element, stackBase = stackHeight));
+
+      CollectionElementInferenceContext context =
+          new CollectionElementInferenceContext(
+              typeContext: inferredTypeArgument,
+              inferredSpreadTypes: inferredSpreadTypes,
+              inferredConditionTypes: inferredConditionTypes);
+      analyzeIfCaseElement(
+          node: element,
+          expression: element.expression,
+          pattern: element.patternGuard.pattern,
+          variables: {
+            for (VariableDeclaration variable
+                in element.patternGuard.pattern.declaredVariables)
+              variable.name!: variable
+          },
+          guard: element.patternGuard.guard,
+          ifTrue: element.then,
+          ifFalse: element.otherwise,
+          context: context);
+
+      assert(checkStack(element, stackBase, [
+        /* ifFalse = */ ValueKinds.ExpressionOrNull,
+        /* ifTrue = */ ValueKinds.Expression,
+        /* guard = */ ValueKinds.ExpressionOrNull,
+        /* pattern = */ ValueKinds.Pattern,
+        /* scrutinee = */ ValueKinds.Expression,
+      ]));
+
+      Object? rewrite = popRewrite(NullValues.Expression);
+      if (!identical(element.otherwise, rewrite)) {
+        element.otherwise = (rewrite as Expression?)?..parent = element;
+      }
+
+      rewrite = popRewrite();
+      if (!identical(element.then, rewrite)) {
+        element.then = (rewrite as Expression)..parent = element;
+      }
+
+      PatternGuard patternGuard = element.patternGuard;
+      rewrite = popRewrite(NullValues.Expression);
+      if (!identical(patternGuard.guard, rewrite)) {
+        patternGuard.guard = (rewrite as Expression?)?..parent = patternGuard;
+      }
+
+      rewrite = popRewrite();
+      if (!identical(patternGuard.pattern, rewrite)) {
+        patternGuard.pattern = (rewrite as Pattern)..parent = patternGuard;
+      }
+
+      rewrite = popRewrite();
+      if (!identical(element.expression, rewrite)) {
+        element.expression = (rewrite as Expression)..parent = patternGuard;
+      }
+
+      DartType thenType = context.inferredConditionTypes[element.then]!;
+      DartType? otherwiseType = element.otherwise == null
+          ? null
+          : context.inferredConditionTypes[element.otherwise!]!;
+
+      MatchingCache matchingCache = createMatchingCache();
+      MatchingExpressionVisitor matchingExpressionVisitor =
+          new MatchingExpressionVisitor(matchingCache);
+      // TODO(cstefantsova): Provide a more precise scrutinee type.
+      CacheableExpression matchedExpression = matchingCache
+          .createRootExpression(element.expression, const DynamicType());
+      DelayedExpression matchingExpression = matchingExpressionVisitor
+          .visitPattern(element.patternGuard.pattern, matchedExpression);
+
+      matchingExpression.registerUse();
+
+      Expression condition =
+          matchingExpression.createExpression(typeSchemaEnvironment);
+      Expression? guard = element.patternGuard.guard;
+      if (guard != null) {
+        condition =
+            createAndExpression(condition, guard, fileOffset: guard.fileOffset);
+      }
+      element.expression = condition;
+      element.replacement = [
+        ...element.patternGuard.pattern.declaredVariables,
+        ...matchingCache.declarations,
+      ];
+
+      return new ExpressionInferenceResult(
+          otherwiseType == null
+              ? thenType
+              : typeSchemaEnvironment.getStandardUpperBound(
+                  thenType, otherwiseType,
+                  isNonNullableByDefault:
+                      libraryBuilder.isNonNullableByDefault),
+          element);
     } else if (element is ForElement) {
       // TODO(johnniwinther): Use _visitStatements instead.
       List<VariableDeclaration>? variables;
@@ -2561,6 +2655,9 @@
     } else if (element is IfElement) {
       _translateIfElement(element, receiverType, elementType, result, body,
           isSet: isSet);
+    } else if (element is IfCaseElement) {
+      _translateIfCaseElement(element, receiverType, elementType, result, body,
+          isSet: isSet);
     } else if (element is ForElement) {
       _translateForElement(element, receiverType, elementType, result, body,
           isSet: isSet);
@@ -2608,6 +2705,39 @@
     body.add(ifStatement);
   }
 
+  void _translateIfCaseElement(
+      IfCaseElement element,
+      InterfaceType receiverType,
+      DartType elementType,
+      VariableDeclaration result,
+      List<Statement> body,
+      {required bool isSet}) {
+    List<Statement> thenStatements = [];
+    _translateElement(
+        element.then, receiverType, elementType, result, thenStatements,
+        isSet: isSet);
+    List<Statement>? elseStatements;
+    if (element.otherwise != null) {
+      _translateElement(element.otherwise!, receiverType, elementType, result,
+          elseStatements = <Statement>[],
+          isSet: isSet);
+    }
+    Statement thenBody = thenStatements.length == 1
+        ? thenStatements.first
+        : _createBlock(thenStatements);
+    Statement? elseBody;
+    if (elseStatements != null && elseStatements.isNotEmpty) {
+      elseBody = elseStatements.length == 1
+          ? elseStatements.first
+          : _createBlock(elseStatements);
+    }
+    IfStatement ifStatement =
+        _createIf(element.fileOffset, element.expression, thenBody, elseBody);
+    libraryBuilder.loader.dataForTesting?.registerAlias(element, ifStatement);
+    body.addAll(element.replacement);
+    body.add(ifStatement);
+  }
+
   void _translateForElement(ForElement element, InterfaceType receiverType,
       DartType elementType, VariableDeclaration result, List<Statement> body,
       {required bool isSet}) {
@@ -3780,6 +3910,23 @@
       }
       flowAnalysis.ifStatement_end(entry.otherwise != null);
       return entry;
+    } else if (entry is IfCaseMapEntry) {
+      // TODO(cstefantsova): Pass an appropriate context message.
+      analyzeIfCaseElement(
+          node: entry,
+          expression: entry.expression,
+          pattern: entry.patternGuard.pattern,
+          variables: {
+            for (VariableDeclaration variable
+                in entry.patternGuard.pattern.declaredVariables)
+              variable.name!: variable
+          },
+          guard: entry.patternGuard.guard,
+          ifTrue: entry.then,
+          ifFalse: entry.otherwise,
+          context: null);
+      // TODO(cstefantsova): Implement inference for if-case map entries.
+      throw new UnimplementedError();
     } else if (entry is ForMapEntry) {
       // TODO(johnniwinther): Use _visitStatements instead.
       List<VariableDeclaration>? variables;
@@ -10440,9 +10587,17 @@
   }
 
   @override
-  void dispatchCollectionElement(TreeNode element, Object? context) {
-    // TODO(scheglov): implement dispatchCollectionElement
-    throw new UnimplementedError('TODO(scheglov)');
+  void dispatchCollectionElement(covariant Expression element,
+      covariant CollectionElementInferenceContext context) {
+    ExpressionInferenceResult inferenceResult = inferElement(
+        element,
+        context.typeContext,
+        context.inferredSpreadTypes,
+        context.inferredConditionTypes);
+    // TODO(cstefantsova): Should the key to the map be [element] instead?
+    context.inferredConditionTypes[inferenceResult.expression] =
+        inferenceResult.inferredType;
+    pushRewrite(inferenceResult.expression);
   }
 
   @override
@@ -10459,8 +10614,7 @@
 
   @override
   void handleNoCollectionElement(TreeNode element) {
-    // TODO(scheglov): implement handleNoCollectionElement
-    throw new UnimplementedError('TODO(scheglov)');
+    pushRewrite(NullValues.Expression);
   }
 
   @override
@@ -10656,3 +10810,14 @@
     return count;
   }
 }
+
+class CollectionElementInferenceContext {
+  DartType typeContext;
+  Map<TreeNode, DartType> inferredSpreadTypes;
+  Map<Expression, DartType> inferredConditionTypes;
+
+  CollectionElementInferenceContext(
+      {required this.typeContext,
+      required this.inferredSpreadTypes,
+      required this.inferredConditionTypes});
+}
diff --git a/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart
new file mode 100644
index 0000000..9bb5157
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2023, 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.
+
+test1(dynamic x) => [1, if (x case [int y, ...]) y, 1];
+
+test2(dynamic x) => [2, if (x case String y) y else null, 2];
+
+test3(dynamic x) => [3, if (x case bool b when b) b, 3];
+
+main() {
+  expectEquals(
+    listToString(test1([0, 1, 2])),
+    listToString([1, 0, 1]),
+  );
+  expectEquals(
+    listToString(test1([])),
+    listToString([1, 1]),
+  );
+  expectEquals(
+    listToString(test1([null])),
+    listToString([1, 1]),
+  );
+
+  expectEquals(
+    listToString(test2("foo")),
+    listToString([2, "foo", 2]),
+  );
+  expectEquals(
+    listToString(test2(0)),
+    listToString([2, null, 2]),
+  );
+
+  expectEquals(
+    listToString(test3(true)),
+    listToString([3, true, 3]),
+  );
+  expectEquals(
+    listToString(test3(false)),
+    listToString([3, 3]),
+  );
+  expectEquals(
+    listToString(test3(null)),
+    listToString([3, 3]),
+  );
+}
+
+expectEquals(x, y) {
+  if (x != y) {
+    throw "Expected '${x} (${x.runtimeType})' to be equal to '${y}' (${y.runtimeType}).";
+  }
+}
+
+listToString(List<dynamic> list) {
+  return "[${list.map((e) => e.toString()).join(',')}]";
+}
diff --git a/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.strong.expect b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.strong.expect
new file mode 100644
index 0000000..faaba62
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.strong.expect
@@ -0,0 +1,56 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+static method test1(dynamic x) → dynamic
+  return block {
+    final core::List<core::int> #t1 = <core::int>[1];
+    core::int y;
+    final dynamic #0#0 = x;
+    late final dynamic #0#6 = #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic};
+    if(#0#0 is{ForNonNullableByDefault} core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int}.{core::num::>=}(#C1){(core::num) → core::bool} && (#0#6 is{ForNonNullableByDefault} core::int && (let final dynamic #t2 = y = #0#6{core::int} in true)))
+      #t1.{core::List::add}{Invariant}(y){(core::int) → void};
+    #t1.{core::List::add}{Invariant}(1){(core::int) → void};
+  } =>#t1;
+static method test2(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object?> #t3 = <core::Object?>[2];
+    core::String y;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::String && (let final dynamic #t4 = y = #0#0{core::String} in true))
+      #t3.{core::List::add}{Invariant}(y){(core::Object?) → void};
+    else
+      #t3.{core::List::add}{Invariant}(null){(core::Object?) → void};
+    #t3.{core::List::add}{Invariant}(2){(core::Object?) → void};
+  } =>#t3;
+static method test3(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object> #t5 = <core::Object>[3];
+    core::bool b;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::bool && (let final dynamic #t6 = b = #0#0{core::bool} in true) && b)
+      #t5.{core::List::add}{Invariant}(b){(core::Object) → void};
+    #t5.{core::List::add}{Invariant}(3){(core::Object) → void};
+  } =>#t5;
+static method main() → dynamic {
+  self::expectEquals(self::listToString(self::test1(<core::int>[0, 1, 2]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[1, 0, 1]));
+  self::expectEquals(self::listToString(self::test1(<dynamic>[]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[1, 1]));
+  self::expectEquals(self::listToString(self::test1(<Null>[null]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[1, 1]));
+  self::expectEquals(self::listToString(self::test2("foo") as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[2, "foo", 2]));
+  self::expectEquals(self::listToString(self::test2(0) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[2, null, 2]));
+  self::expectEquals(self::listToString(self::test3(true) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[3, true, 3]));
+  self::expectEquals(self::listToString(self::test3(false) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[3, 3]));
+  self::expectEquals(self::listToString(self::test3(null) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[3, 3]));
+}
+static method expectEquals(dynamic x, dynamic y) → dynamic {
+  if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) {
+    throw "Expected '${x} (${x.{core::Object::runtimeType}{core::Type}})' to be equal to '${y}' (${y.{core::Object::runtimeType}{core::Type}}).";
+  }
+}
+static method listToString(core::List<dynamic> list) → dynamic {
+  return "[${list.{core::Iterable::map}<core::String>((dynamic e) → core::String => e.{core::Object::toString}(){() → core::String}){((dynamic) → core::String) → core::Iterable<core::String>}.{core::Iterable::join}(","){([core::String]) → core::String}}]";
+}
+
+constants  {
+  #C1 = 1
+}
diff --git a/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.strong.transformed.expect b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.strong.transformed.expect
new file mode 100644
index 0000000..e83084d
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.strong.transformed.expect
@@ -0,0 +1,58 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+static method test1(dynamic x) → dynamic
+  return block {
+    final core::List<core::int> #t1 = core::_GrowableList::_literal1<core::int>(1);
+    core::int y;
+    final dynamic #0#0 = x;
+    function ##0#6#initializer() → dynamic
+      return #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic};
+    late final dynamic #0#6 = ##0#6#initializer(){() → dynamic};
+    if(#0#0 is{ForNonNullableByDefault} core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int}.{core::num::>=}(#C1){(core::num) → core::bool} && (#0#6 is{ForNonNullableByDefault} core::int && (let final core::int #t2 = y = #0#6{core::int} in true)))
+      #t1.{core::List::add}{Invariant}(y){(core::int) → void};
+    #t1.{core::List::add}{Invariant}(1){(core::int) → void};
+  } =>#t1;
+static method test2(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object?> #t3 = core::_GrowableList::_literal1<core::Object?>(2);
+    core::String y;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::String && (let final core::String #t4 = y = #0#0{core::String} in true))
+      #t3.{core::List::add}{Invariant}(y){(core::Object?) → void};
+    else
+      #t3.{core::List::add}{Invariant}(null){(core::Object?) → void};
+    #t3.{core::List::add}{Invariant}(2){(core::Object?) → void};
+  } =>#t3;
+static method test3(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object> #t5 = core::_GrowableList::_literal1<core::Object>(3);
+    core::bool b;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::bool && (let final core::bool #t6 = b = #0#0{core::bool} in true) && b)
+      #t5.{core::List::add}{Invariant}(b){(core::Object) → void};
+    #t5.{core::List::add}{Invariant}(3){(core::Object) → void};
+  } =>#t5;
+static method main() → dynamic {
+  self::expectEquals(self::listToString(self::test1(core::_GrowableList::_literal3<core::int>(0, 1, 2)) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal3<dynamic>(1, 0, 1)));
+  self::expectEquals(self::listToString(self::test1(core::_GrowableList::•<dynamic>(0)) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal2<dynamic>(1, 1)));
+  self::expectEquals(self::listToString(self::test1(core::_GrowableList::_literal1<Null>(null)) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal2<dynamic>(1, 1)));
+  self::expectEquals(self::listToString(self::test2("foo") as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal3<dynamic>(2, "foo", 2)));
+  self::expectEquals(self::listToString(self::test2(0) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal3<dynamic>(2, null, 2)));
+  self::expectEquals(self::listToString(self::test3(true) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal3<dynamic>(3, true, 3)));
+  self::expectEquals(self::listToString(self::test3(false) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal2<dynamic>(3, 3)));
+  self::expectEquals(self::listToString(self::test3(null) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal2<dynamic>(3, 3)));
+}
+static method expectEquals(dynamic x, dynamic y) → dynamic {
+  if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) {
+    throw "Expected '${x} (${x.{core::Object::runtimeType}{core::Type}})' to be equal to '${y}' (${y.{core::Object::runtimeType}{core::Type}}).";
+  }
+}
+static method listToString(core::List<dynamic> list) → dynamic {
+  return "[${list.{core::Iterable::map}<core::String>((dynamic e) → core::String => e.{core::Object::toString}(){() → core::String}){((dynamic) → core::String) → core::Iterable<core::String>}.{core::Iterable::join}(","){([core::String]) → core::String}}]";
+}
+
+constants  {
+  #C1 = 1
+}
diff --git a/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.textual_outline.expect b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.textual_outline.expect
new file mode 100644
index 0000000..2e808ad
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.textual_outline.expect
@@ -0,0 +1,6 @@
+test1(dynamic x) => [1, if (x case [int y, ...]) y, 1];
+test2(dynamic x) => [2, if (x case String y) y else null, 2];
+test3(dynamic x) => [3, if (x case bool b when b) b, 3];
+main() {}
+expectEquals(x, y) {}
+listToString(List<dynamic> list) {}
diff --git a/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.expect b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.expect
new file mode 100644
index 0000000..faaba62
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.expect
@@ -0,0 +1,56 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+static method test1(dynamic x) → dynamic
+  return block {
+    final core::List<core::int> #t1 = <core::int>[1];
+    core::int y;
+    final dynamic #0#0 = x;
+    late final dynamic #0#6 = #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic};
+    if(#0#0 is{ForNonNullableByDefault} core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int}.{core::num::>=}(#C1){(core::num) → core::bool} && (#0#6 is{ForNonNullableByDefault} core::int && (let final dynamic #t2 = y = #0#6{core::int} in true)))
+      #t1.{core::List::add}{Invariant}(y){(core::int) → void};
+    #t1.{core::List::add}{Invariant}(1){(core::int) → void};
+  } =>#t1;
+static method test2(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object?> #t3 = <core::Object?>[2];
+    core::String y;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::String && (let final dynamic #t4 = y = #0#0{core::String} in true))
+      #t3.{core::List::add}{Invariant}(y){(core::Object?) → void};
+    else
+      #t3.{core::List::add}{Invariant}(null){(core::Object?) → void};
+    #t3.{core::List::add}{Invariant}(2){(core::Object?) → void};
+  } =>#t3;
+static method test3(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object> #t5 = <core::Object>[3];
+    core::bool b;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::bool && (let final dynamic #t6 = b = #0#0{core::bool} in true) && b)
+      #t5.{core::List::add}{Invariant}(b){(core::Object) → void};
+    #t5.{core::List::add}{Invariant}(3){(core::Object) → void};
+  } =>#t5;
+static method main() → dynamic {
+  self::expectEquals(self::listToString(self::test1(<core::int>[0, 1, 2]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[1, 0, 1]));
+  self::expectEquals(self::listToString(self::test1(<dynamic>[]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[1, 1]));
+  self::expectEquals(self::listToString(self::test1(<Null>[null]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[1, 1]));
+  self::expectEquals(self::listToString(self::test2("foo") as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[2, "foo", 2]));
+  self::expectEquals(self::listToString(self::test2(0) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[2, null, 2]));
+  self::expectEquals(self::listToString(self::test3(true) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[3, true, 3]));
+  self::expectEquals(self::listToString(self::test3(false) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[3, 3]));
+  self::expectEquals(self::listToString(self::test3(null) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[3, 3]));
+}
+static method expectEquals(dynamic x, dynamic y) → dynamic {
+  if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) {
+    throw "Expected '${x} (${x.{core::Object::runtimeType}{core::Type}})' to be equal to '${y}' (${y.{core::Object::runtimeType}{core::Type}}).";
+  }
+}
+static method listToString(core::List<dynamic> list) → dynamic {
+  return "[${list.{core::Iterable::map}<core::String>((dynamic e) → core::String => e.{core::Object::toString}(){() → core::String}){((dynamic) → core::String) → core::Iterable<core::String>}.{core::Iterable::join}(","){([core::String]) → core::String}}]";
+}
+
+constants  {
+  #C1 = 1
+}
diff --git a/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.modular.expect b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.modular.expect
new file mode 100644
index 0000000..faaba62
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.modular.expect
@@ -0,0 +1,56 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+static method test1(dynamic x) → dynamic
+  return block {
+    final core::List<core::int> #t1 = <core::int>[1];
+    core::int y;
+    final dynamic #0#0 = x;
+    late final dynamic #0#6 = #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic};
+    if(#0#0 is{ForNonNullableByDefault} core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int}.{core::num::>=}(#C1){(core::num) → core::bool} && (#0#6 is{ForNonNullableByDefault} core::int && (let final dynamic #t2 = y = #0#6{core::int} in true)))
+      #t1.{core::List::add}{Invariant}(y){(core::int) → void};
+    #t1.{core::List::add}{Invariant}(1){(core::int) → void};
+  } =>#t1;
+static method test2(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object?> #t3 = <core::Object?>[2];
+    core::String y;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::String && (let final dynamic #t4 = y = #0#0{core::String} in true))
+      #t3.{core::List::add}{Invariant}(y){(core::Object?) → void};
+    else
+      #t3.{core::List::add}{Invariant}(null){(core::Object?) → void};
+    #t3.{core::List::add}{Invariant}(2){(core::Object?) → void};
+  } =>#t3;
+static method test3(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object> #t5 = <core::Object>[3];
+    core::bool b;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::bool && (let final dynamic #t6 = b = #0#0{core::bool} in true) && b)
+      #t5.{core::List::add}{Invariant}(b){(core::Object) → void};
+    #t5.{core::List::add}{Invariant}(3){(core::Object) → void};
+  } =>#t5;
+static method main() → dynamic {
+  self::expectEquals(self::listToString(self::test1(<core::int>[0, 1, 2]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[1, 0, 1]));
+  self::expectEquals(self::listToString(self::test1(<dynamic>[]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[1, 1]));
+  self::expectEquals(self::listToString(self::test1(<Null>[null]) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[1, 1]));
+  self::expectEquals(self::listToString(self::test2("foo") as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[2, "foo", 2]));
+  self::expectEquals(self::listToString(self::test2(0) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[2, null, 2]));
+  self::expectEquals(self::listToString(self::test3(true) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[3, true, 3]));
+  self::expectEquals(self::listToString(self::test3(false) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[3, 3]));
+  self::expectEquals(self::listToString(self::test3(null) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(<dynamic>[3, 3]));
+}
+static method expectEquals(dynamic x, dynamic y) → dynamic {
+  if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) {
+    throw "Expected '${x} (${x.{core::Object::runtimeType}{core::Type}})' to be equal to '${y}' (${y.{core::Object::runtimeType}{core::Type}}).";
+  }
+}
+static method listToString(core::List<dynamic> list) → dynamic {
+  return "[${list.{core::Iterable::map}<core::String>((dynamic e) → core::String => e.{core::Object::toString}(){() → core::String}){((dynamic) → core::String) → core::Iterable<core::String>}.{core::Iterable::join}(","){([core::String]) → core::String}}]";
+}
+
+constants  {
+  #C1 = 1
+}
diff --git a/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.outline.expect b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.outline.expect
new file mode 100644
index 0000000..86cc3ab
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.outline.expect
@@ -0,0 +1,16 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+static method test1(dynamic x) → dynamic
+  ;
+static method test2(dynamic x) → dynamic
+  ;
+static method test3(dynamic x) → dynamic
+  ;
+static method main() → dynamic
+  ;
+static method expectEquals(dynamic x, dynamic y) → dynamic
+  ;
+static method listToString(core::List<dynamic> list) → dynamic
+  ;
diff --git a/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.transformed.expect b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.transformed.expect
new file mode 100644
index 0000000..e83084d
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/simple_if_case_in_lists.dart.weak.transformed.expect
@@ -0,0 +1,58 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+static method test1(dynamic x) → dynamic
+  return block {
+    final core::List<core::int> #t1 = core::_GrowableList::_literal1<core::int>(1);
+    core::int y;
+    final dynamic #0#0 = x;
+    function ##0#6#initializer() → dynamic
+      return #0#0{core::List<dynamic>}.{core::List::[]}(0){(core::int) → dynamic};
+    late final dynamic #0#6 = ##0#6#initializer(){() → dynamic};
+    if(#0#0 is{ForNonNullableByDefault} core::List<dynamic> && #0#0{core::List<dynamic>}.{core::List::length}{core::int}.{core::num::>=}(#C1){(core::num) → core::bool} && (#0#6 is{ForNonNullableByDefault} core::int && (let final core::int #t2 = y = #0#6{core::int} in true)))
+      #t1.{core::List::add}{Invariant}(y){(core::int) → void};
+    #t1.{core::List::add}{Invariant}(1){(core::int) → void};
+  } =>#t1;
+static method test2(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object?> #t3 = core::_GrowableList::_literal1<core::Object?>(2);
+    core::String y;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::String && (let final core::String #t4 = y = #0#0{core::String} in true))
+      #t3.{core::List::add}{Invariant}(y){(core::Object?) → void};
+    else
+      #t3.{core::List::add}{Invariant}(null){(core::Object?) → void};
+    #t3.{core::List::add}{Invariant}(2){(core::Object?) → void};
+  } =>#t3;
+static method test3(dynamic x) → dynamic
+  return block {
+    final core::List<core::Object> #t5 = core::_GrowableList::_literal1<core::Object>(3);
+    core::bool b;
+    final dynamic #0#0 = x;
+    if(#0#0 is{ForNonNullableByDefault} core::bool && (let final core::bool #t6 = b = #0#0{core::bool} in true) && b)
+      #t5.{core::List::add}{Invariant}(b){(core::Object) → void};
+    #t5.{core::List::add}{Invariant}(3){(core::Object) → void};
+  } =>#t5;
+static method main() → dynamic {
+  self::expectEquals(self::listToString(self::test1(core::_GrowableList::_literal3<core::int>(0, 1, 2)) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal3<dynamic>(1, 0, 1)));
+  self::expectEquals(self::listToString(self::test1(core::_GrowableList::•<dynamic>(0)) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal2<dynamic>(1, 1)));
+  self::expectEquals(self::listToString(self::test1(core::_GrowableList::_literal1<Null>(null)) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal2<dynamic>(1, 1)));
+  self::expectEquals(self::listToString(self::test2("foo") as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal3<dynamic>(2, "foo", 2)));
+  self::expectEquals(self::listToString(self::test2(0) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal3<dynamic>(2, null, 2)));
+  self::expectEquals(self::listToString(self::test3(true) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal3<dynamic>(3, true, 3)));
+  self::expectEquals(self::listToString(self::test3(false) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal2<dynamic>(3, 3)));
+  self::expectEquals(self::listToString(self::test3(null) as{TypeError,ForDynamic,ForNonNullableByDefault} core::List<dynamic>), self::listToString(core::_GrowableList::_literal2<dynamic>(3, 3)));
+}
+static method expectEquals(dynamic x, dynamic y) → dynamic {
+  if(!(x =={core::Object::==}{(core::Object) → core::bool} y)) {
+    throw "Expected '${x} (${x.{core::Object::runtimeType}{core::Type}})' to be equal to '${y}' (${y.{core::Object::runtimeType}{core::Type}}).";
+  }
+}
+static method listToString(core::List<dynamic> list) → dynamic {
+  return "[${list.{core::Iterable::map}<core::String>((dynamic e) → core::String => e.{core::Object::toString}(){() → core::String}){((dynamic) → core::String) → core::Iterable<core::String>}.{core::Iterable::join}(","){([core::String]) → core::String}}]";
+}
+
+constants  {
+  #C1 = 1
+}
diff --git a/pkg/front_end/testcases/textual_outline.status b/pkg/front_end/testcases/textual_outline.status
index 0d6ca5a..0043472 100644
--- a/pkg/front_end/testcases/textual_outline.status
+++ b/pkg/front_end/testcases/textual_outline.status
@@ -166,6 +166,7 @@
 patterns/pattern_types: FormatterCrash
 patterns/records/destructuring: FormatterCrash
 patterns/records/record_pattern_inside_if_case: FormatterCrash
+patterns/simple_if_case_in_lists: FormatterCrash
 patterns/switchExpression_empty: FormatterCrash
 patterns/switchExpression_onePattern_guarded: FormatterCrash
 patterns/switchExpression_onePattern_noTrailingComma: FormatterCrash