[cfe] Generate covariant checks in pattern matching

Closes #52192

Change-Id: Iaac816273fb80eaf166fc300b2b3367f1a592d3f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/302223
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/front_end/lib/src/fasta/type_inference/delayed_expressions.dart b/pkg/front_end/lib/src/fasta/type_inference/delayed_expressions.dart
index 050a5b4..72a4a8b 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/delayed_expressions.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/delayed_expressions.dart
@@ -424,18 +424,25 @@
   final DartType _type;
   final bool isUnchecked;
   final bool isImplicit;
+  final bool isCovarianceCheck;
   final int fileOffset;
 
   DelayedAsExpression(this._operand, this._type,
       {this.isUnchecked = false,
       this.isImplicit = false,
+      this.isCovarianceCheck = false,
       required this.fileOffset});
 
   @override
   Expression createExpression(TypeEnvironment typeEnvironment,
       [List<Expression>? effects]) {
     Expression operand = _operand.createExpression(typeEnvironment, effects);
-    if (isImplicit) {
+    if (isCovarianceCheck) {
+      return createAsExpression(operand, _type,
+          forNonNullableByDefault: true,
+          isCovarianceCheck: true,
+          fileOffset: fileOffset);
+    } else if (isImplicit) {
       DartType operandType = _operand.getType(typeEnvironment);
       if (typeEnvironment.isSubtypeOf(
           operandType, _type, SubtypeCheckMode.withNullabilities)) {
diff --git a/pkg/front_end/lib/src/fasta/type_inference/external_ast_helper.dart b/pkg/front_end/lib/src/fasta/type_inference/external_ast_helper.dart
index 6b19814..f0a77cc 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/external_ast_helper.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/external_ast_helper.dart
@@ -253,11 +253,13 @@
 AsExpression createAsExpression(Expression operand, DartType type,
     {required bool forNonNullableByDefault,
     bool isUnchecked = false,
+    bool isCovarianceCheck = false,
     required int fileOffset}) {
   return new AsExpression(operand, type)
     ..fileOffset = fileOffset
     ..isForNonNullableByDefault = forNonNullableByDefault
-    ..isUnchecked = isUnchecked;
+    ..isUnchecked = isUnchecked
+    ..isCovarianceCheck = isCovarianceCheck;
 }
 
 /// Creates a [NullCheck] of [expression].
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 218d626..a6f8e26 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
@@ -10475,6 +10475,22 @@
           field.accessKind = ObjectAccessKind.Dynamic;
           break;
       }
+      if (fieldTarget.isInstanceMember || fieldTarget.isObjectMember) {
+        Member interfaceMember = fieldTarget.member!;
+        if (interfaceMember is Procedure) {
+          DartType typeToCheck = isNonNullableByDefault
+              ? interfaceMember.function
+                  .computeFunctionType(libraryBuilder.nonNullable)
+              : interfaceMember.function.returnType;
+          field.checkReturn =
+              InferenceVisitorBase.returnedTypeParametersOccurNonCovariantly(
+                  interfaceMember.enclosingClass!, typeToCheck);
+        } else if (interfaceMember is Field) {
+          field.checkReturn =
+              InferenceVisitorBase.returnedTypeParametersOccurNonCovariantly(
+                  interfaceMember.enclosingClass!, interfaceMember.type);
+        }
+      }
     }
 
     pushRewrite(replacement ?? node);
diff --git a/pkg/front_end/lib/src/fasta/type_inference/matching_cache.dart b/pkg/front_end/lib/src/fasta/type_inference/matching_cache.dart
index f5db434..bf2b062 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/matching_cache.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/matching_cache.dart
@@ -908,6 +908,56 @@
   }
 }
 
+/// A cacheable expression that performs a covariant check on the resulting
+/// value.
+class CovariantCheckCacheableExpression implements CacheableExpression {
+  final CacheableExpression _expression;
+
+  final DartType _checkedType;
+
+  final int fileOffset;
+
+  CovariantCheckCacheableExpression(this._expression, this._checkedType,
+      {required this.fileOffset});
+
+  @override
+  CacheKey get cacheKey => _expression.cacheKey;
+
+  @override
+  AccessKey get accessKey => _expression.accessKey;
+
+  @override
+  Expression createExpression(TypeEnvironment typeEnvironment,
+      [List<Expression>? effects]) {
+    Expression result = _expression.createExpression(typeEnvironment, effects);
+    return createAsExpression(result, _checkedType,
+        forNonNullableByDefault: true,
+        fileOffset: fileOffset,
+        isCovarianceCheck: true);
+  }
+
+  @override
+  DartType getType(TypeEnvironment typeEnvironment) {
+    return _checkedType;
+  }
+
+  @override
+  void registerUse() {
+    _expression.registerUse();
+  }
+
+  @override
+  bool uses(DelayedExpression expression) {
+    return identical(this, expression) || _expression.uses(expression);
+  }
+
+  @override
+  CacheableExpression promote(DartType type) {
+    if (type == _checkedType) return this;
+    return new PromotedCacheableExpression(_expression, type);
+  }
+}
+
 /// A [CacheableExpression] created using a potentially shared [Cache].
 class CacheExpression implements CacheableExpression {
   @override
diff --git a/pkg/front_end/lib/src/fasta/type_inference/matching_expressions.dart b/pkg/front_end/lib/src/fasta/type_inference/matching_expressions.dart
index 39644ff..1c481ff 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/matching_expressions.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/matching_expressions.dart
@@ -475,6 +475,11 @@
               staticTarget: staticTarget,
               typeArguments: typeArguments,
               fileOffset: field.fileOffset);
+      if (field.checkReturn) {
+        objectExpression = new CovariantCheckCacheableExpression(
+            objectExpression, field.resultType!,
+            fileOffset: field.fileOffset);
+      }
 
       DelayedExpression subExpression =
           visitPattern(field.pattern, objectExpression);
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart b/pkg/front_end/testcases/patterns/issue52192.dart
new file mode 100644
index 0000000..26dbdf13
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/issue52192.dart
@@ -0,0 +1,44 @@
+// 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.
+
+int callCount = 0;
+
+abstract class A<X> {
+  void Function(X) get g;
+}
+
+class B implements A<int> {
+  void Function(int) get g => (int i) => callCount++;
+}
+
+void foo(Object o, num value) {
+  switch (o) {
+    case B(g: _) && A<num>(g: var f):
+      f(value);
+  }
+}
+
+void main() {
+  expect(0, callCount);
+  throws(() => foo(B(), 25.7));
+  expect(0, callCount);
+  throws(() => foo(B(), 1));
+  expect(0, callCount);
+}
+
+expect(expected, actual) {
+  if (expected != actual) {
+    throw 'Expected $expected, actual $actual';
+  }
+}
+
+throws(void Function() f) {
+  try {
+    f();
+  } catch (e) {
+    print(e);
+    return;
+  }
+  throw 'No exception thrown';
+}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.strong.expect b/pkg/front_end/testcases/patterns/issue52192.dart.strong.expect
new file mode 100644
index 0000000..2c1771b
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/issue52192.dart.strong.expect
@@ -0,0 +1,55 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+abstract class A<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::A<self::A::X%>
+    : super core::Object::•()
+    ;
+  abstract get g() → (self::A::X%) → void;
+}
+class B extends core::Object implements self::A<core::int> {
+  synthetic constructor •() → self::B
+    : super core::Object::•()
+    ;
+  get g() → (core::int) → void
+    return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1;
+}
+static field core::int callCount = 0;
+static method foo(core::Object o, core::num value) → void {
+  #L1:
+  {
+    final synthesized core::Object #0#0 = o;
+    late final synthesized (core::int) → void #0#2 = #0#0{self::B}.{self::B::g}{(core::int) → void};
+    {
+      hoisted (core::num) → void f;
+      if(#0#0 is{ForNonNullableByDefault} self::B && (let final dynamic #t3 = #0#2 in true) && (let final dynamic #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) {
+        {
+          f(value){(core::num) → void};
+        }
+      }
+    }
+  }
+}
+static method main() → void {
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 25.7));
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 1));
+  self::expect(0, self::callCount);
+}
+static method expect(dynamic expected, dynamic actual) → dynamic {
+  if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) {
+    throw "Expected ${expected}, actual ${actual}";
+  }
+}
+static method throws(() → void f) → dynamic {
+  try {
+    f(){() → void};
+  }
+  on core::Object catch(final core::Object e) {
+    core::print(e);
+    return;
+  }
+  throw "No exception thrown";
+}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.strong.transformed.expect b/pkg/front_end/testcases/patterns/issue52192.dart.strong.transformed.expect
new file mode 100644
index 0000000..42ab55a
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/issue52192.dart.strong.transformed.expect
@@ -0,0 +1,57 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+abstract class A<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::A<self::A::X%>
+    : super core::Object::•()
+    ;
+  abstract get g() → (self::A::X%) → void;
+}
+class B extends core::Object implements self::A<core::int> {
+  synthetic constructor •() → self::B
+    : super core::Object::•()
+    ;
+  get g() → (core::int) → void
+    return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1;
+}
+static field core::int callCount = 0;
+static method foo(core::Object o, core::num value) → void {
+  #L1:
+  {
+    final synthesized core::Object #0#0 = o;
+    function ##0#2#initializer() → (core::int) → void
+      return #0#0{self::B}.{self::B::g}{(core::int) → void};
+    late final synthesized (core::int) → void #0#2 = ##0#2#initializer(){() → (core::int) → void};
+    {
+      hoisted (core::num) → void f;
+      if(#0#0 is{ForNonNullableByDefault} self::B && (let final (core::int) → void #t3 = #0#2 in true) && (let final (core::num) → void #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) {
+        {
+          f(value){(core::num) → void};
+        }
+      }
+    }
+  }
+}
+static method main() → void {
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 25.7));
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 1));
+  self::expect(0, self::callCount);
+}
+static method expect(dynamic expected, dynamic actual) → dynamic {
+  if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) {
+    throw "Expected ${expected}, actual ${actual}";
+  }
+}
+static method throws(() → void f) → dynamic {
+  try {
+    f(){() → void};
+  }
+  on core::Object catch(final core::Object e) {
+    core::print(e);
+    return;
+  }
+  throw "No exception thrown";
+}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline.expect b/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline.expect
new file mode 100644
index 0000000..213bc96
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline.expect
@@ -0,0 +1,14 @@
+int callCount = 0;
+
+abstract class A<X> {
+  void Function(X) get g;
+}
+
+class B implements A<int> {
+  void Function(int) get g => (int i) => callCount++;
+}
+
+void foo(Object o, num value) {}
+void main() {}
+expect(expected, actual) {}
+throws(void Function() f) {}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..aa84847
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/issue52192.dart.textual_outline_modelled.expect
@@ -0,0 +1,13 @@
+abstract class A<X> {
+  void Function(X) get g;
+}
+
+class B implements A<int> {
+  void Function(int) get g => (int i) => callCount++;
+}
+
+expect(expected, actual) {}
+int callCount = 0;
+throws(void Function() f) {}
+void foo(Object o, num value) {}
+void main() {}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.weak.expect b/pkg/front_end/testcases/patterns/issue52192.dart.weak.expect
new file mode 100644
index 0000000..2c1771b
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/issue52192.dart.weak.expect
@@ -0,0 +1,55 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+abstract class A<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::A<self::A::X%>
+    : super core::Object::•()
+    ;
+  abstract get g() → (self::A::X%) → void;
+}
+class B extends core::Object implements self::A<core::int> {
+  synthetic constructor •() → self::B
+    : super core::Object::•()
+    ;
+  get g() → (core::int) → void
+    return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1;
+}
+static field core::int callCount = 0;
+static method foo(core::Object o, core::num value) → void {
+  #L1:
+  {
+    final synthesized core::Object #0#0 = o;
+    late final synthesized (core::int) → void #0#2 = #0#0{self::B}.{self::B::g}{(core::int) → void};
+    {
+      hoisted (core::num) → void f;
+      if(#0#0 is{ForNonNullableByDefault} self::B && (let final dynamic #t3 = #0#2 in true) && (let final dynamic #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) {
+        {
+          f(value){(core::num) → void};
+        }
+      }
+    }
+  }
+}
+static method main() → void {
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 25.7));
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 1));
+  self::expect(0, self::callCount);
+}
+static method expect(dynamic expected, dynamic actual) → dynamic {
+  if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) {
+    throw "Expected ${expected}, actual ${actual}";
+  }
+}
+static method throws(() → void f) → dynamic {
+  try {
+    f(){() → void};
+  }
+  on core::Object catch(final core::Object e) {
+    core::print(e);
+    return;
+  }
+  throw "No exception thrown";
+}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.weak.modular.expect b/pkg/front_end/testcases/patterns/issue52192.dart.weak.modular.expect
new file mode 100644
index 0000000..2c1771b
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/issue52192.dart.weak.modular.expect
@@ -0,0 +1,55 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+abstract class A<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::A<self::A::X%>
+    : super core::Object::•()
+    ;
+  abstract get g() → (self::A::X%) → void;
+}
+class B extends core::Object implements self::A<core::int> {
+  synthetic constructor •() → self::B
+    : super core::Object::•()
+    ;
+  get g() → (core::int) → void
+    return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1;
+}
+static field core::int callCount = 0;
+static method foo(core::Object o, core::num value) → void {
+  #L1:
+  {
+    final synthesized core::Object #0#0 = o;
+    late final synthesized (core::int) → void #0#2 = #0#0{self::B}.{self::B::g}{(core::int) → void};
+    {
+      hoisted (core::num) → void f;
+      if(#0#0 is{ForNonNullableByDefault} self::B && (let final dynamic #t3 = #0#2 in true) && (let final dynamic #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) {
+        {
+          f(value){(core::num) → void};
+        }
+      }
+    }
+  }
+}
+static method main() → void {
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 25.7));
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 1));
+  self::expect(0, self::callCount);
+}
+static method expect(dynamic expected, dynamic actual) → dynamic {
+  if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) {
+    throw "Expected ${expected}, actual ${actual}";
+  }
+}
+static method throws(() → void f) → dynamic {
+  try {
+    f(){() → void};
+  }
+  on core::Object catch(final core::Object e) {
+    core::print(e);
+    return;
+  }
+  throw "No exception thrown";
+}
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.weak.outline.expect b/pkg/front_end/testcases/patterns/issue52192.dart.weak.outline.expect
new file mode 100644
index 0000000..877ce08
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/issue52192.dart.weak.outline.expect
@@ -0,0 +1,24 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+abstract class A<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::A<self::A::X%>
+    ;
+  abstract get g() → (self::A::X%) → void;
+}
+class B extends core::Object implements self::A<core::int> {
+  synthetic constructor •() → self::B
+    ;
+  get g() → (core::int) → void
+    ;
+}
+static field core::int callCount;
+static method foo(core::Object o, core::num value) → void
+  ;
+static method main() → void
+  ;
+static method expect(dynamic expected, dynamic actual) → dynamic
+  ;
+static method throws(() → void f) → dynamic
+  ;
diff --git a/pkg/front_end/testcases/patterns/issue52192.dart.weak.transformed.expect b/pkg/front_end/testcases/patterns/issue52192.dart.weak.transformed.expect
new file mode 100644
index 0000000..42ab55a
--- /dev/null
+++ b/pkg/front_end/testcases/patterns/issue52192.dart.weak.transformed.expect
@@ -0,0 +1,57 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+abstract class A<X extends core::Object? = dynamic> extends core::Object {
+  synthetic constructor •() → self::A<self::A::X%>
+    : super core::Object::•()
+    ;
+  abstract get g() → (self::A::X%) → void;
+}
+class B extends core::Object implements self::A<core::int> {
+  synthetic constructor •() → self::B
+    : super core::Object::•()
+    ;
+  get g() → (core::int) → void
+    return (core::int i) → void => let final core::int #t1 = self::callCount in let final core::int #t2 = self::callCount = #t1.{core::num::+}(1){(core::num) → core::int} in #t1;
+}
+static field core::int callCount = 0;
+static method foo(core::Object o, core::num value) → void {
+  #L1:
+  {
+    final synthesized core::Object #0#0 = o;
+    function ##0#2#initializer() → (core::int) → void
+      return #0#0{self::B}.{self::B::g}{(core::int) → void};
+    late final synthesized (core::int) → void #0#2 = ##0#2#initializer(){() → (core::int) → void};
+    {
+      hoisted (core::num) → void f;
+      if(#0#0 is{ForNonNullableByDefault} self::B && (let final (core::int) → void #t3 = #0#2 in true) && (let final (core::num) → void #t4 = f = #0#2 as{CovarianceCheck,ForNonNullableByDefault} (core::num) → void in true)) {
+        {
+          f(value){(core::num) → void};
+        }
+      }
+    }
+  }
+}
+static method main() → void {
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 25.7));
+  self::expect(0, self::callCount);
+  self::throws(() → void => self::foo(new self::B::•(), 1));
+  self::expect(0, self::callCount);
+}
+static method expect(dynamic expected, dynamic actual) → dynamic {
+  if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual)) {
+    throw "Expected ${expected}, actual ${actual}";
+  }
+}
+static method throws(() → void f) → dynamic {
+  try {
+    f(){() → void};
+  }
+  on core::Object catch(final core::Object e) {
+    core::print(e);
+    return;
+  }
+  throw "No exception thrown";
+}
diff --git a/pkg/kernel/lib/src/ast/patterns.dart b/pkg/kernel/lib/src/ast/patterns.dart
index cf1fc07..a5ea7c8 100644
--- a/pkg/kernel/lib/src/ast/patterns.dart
+++ b/pkg/kernel/lib/src/ast/patterns.dart
@@ -1088,6 +1088,11 @@
   /// This is set during inference.
   DartType? resultType;
 
+  /// When used in an object pattern, this is set to `true` if the field value
+  /// needs to be checked against the [resultType]. This is needed for fields
+  /// whose type contain covariant types that occur in non-covariant positions.
+  bool checkReturn = false;
+
   /// When used in an object pattern, this holds the record on which the
   /// property for this pattern is read.
   ///
diff --git a/pkg/kernel/lib/src/equivalence.dart b/pkg/kernel/lib/src/equivalence.dart
index 2e193f2..9e2d3d1 100644
--- a/pkg/kernel/lib/src/equivalence.dart
+++ b/pkg/kernel/lib/src/equivalence.dart
@@ -4917,6 +4917,9 @@
     if (!checkNamedPattern_resultType(visitor, node, other)) {
       result = visitor.resultOnInequivalence;
     }
+    if (!checkNamedPattern_checkReturn(visitor, node, other)) {
+      result = visitor.resultOnInequivalence;
+    }
     if (!checkNamedPattern_recordType(visitor, node, other)) {
       result = visitor.resultOnInequivalence;
     }
@@ -9149,6 +9152,12 @@
     return visitor.checkNodes(node.resultType, other.resultType, 'resultType');
   }
 
+  bool checkNamedPattern_checkReturn(
+      EquivalenceVisitor visitor, NamedPattern node, NamedPattern other) {
+    return visitor.checkValues(
+        node.checkReturn, other.checkReturn, 'checkReturn');
+  }
+
   bool checkNamedPattern_recordType(
       EquivalenceVisitor visitor, NamedPattern node, NamedPattern other) {
     return visitor.checkNodes(node.recordType, other.recordType, 'recordType');