[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) {}