[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