linter: Support for inherited @awaitNotRequired annotations

Change-Id: I6c55f6423f6a44838674165ab311f31c64ffd399
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/424682
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/linter/lib/src/extensions.dart b/pkg/linter/lib/src/extensions.dart
index b3a954e..0136f79 100644
--- a/pkg/linter/lib/src/extensions.dart
+++ b/pkg/linter/lib/src/extensions.dart
@@ -343,18 +343,31 @@
 
 extension ExpressionExtension on Expression {
   /// Returns whether `await` is not required for this expression.
-  // TODO(srawlins): Handle inheritence in each of these cases; the
-  // `@awaitNotRequired` annotation should be inherited.
-  bool get isAwaitNotRequired => switch (this) {
-    BinaryExpression(:var element) => element.hasAwaitNotRequired,
-    MethodInvocation(:var methodName) => methodName.element.hasAwaitNotRequired,
-    PrefixedIdentifier(:var identifier) =>
-      identifier.element.hasAwaitNotRequired,
-    PrefixExpression(:var element) => element.hasAwaitNotRequired,
-    PropertyAccess(:var propertyName) =>
-      propertyName.element.hasAwaitNotRequired,
-    _ => false,
-  };
+  bool get isAwaitNotRequired {
+    var element = switch (this) {
+      BinaryExpression(:var element) => element,
+      MethodInvocation(:var methodName) => methodName.element,
+      PrefixedIdentifier(:var identifier) => identifier.element,
+      PrefixExpression(:var element) => element,
+      PropertyAccess(:var propertyName) => propertyName.element,
+      _ => null,
+    };
+    if (element == null) return false;
+    if (element.hasAwaitNotRequired) return true;
+
+    var elementName = element.name3;
+    if (elementName == null) return false;
+
+    var enclosingElement = element.enclosingElement2;
+    if (enclosingElement is! InterfaceElement2) return false;
+
+    var superTypes = enclosingElement.allSupertypes;
+    var superMembers =
+        element is MethodElement2
+            ? superTypes.map((t) => t.getMethod2(elementName))
+            : superTypes.map((t) => t.getGetter2(elementName));
+    return superMembers.any((e) => e.hasAwaitNotRequired);
+  }
 }
 
 extension ExpressionNullableExtension on Expression? {
diff --git a/pkg/linter/test/rules/unawaited_futures_test.dart b/pkg/linter/test/rules/unawaited_futures_test.dart
index 2aa0854..08aee44 100644
--- a/pkg/linter/test/rules/unawaited_futures_test.dart
+++ b/pkg/linter/test/rules/unawaited_futures_test.dart
@@ -181,8 +181,6 @@
     );
   }
 
-  // TODO(srawlins): Test that `@awaitNotRequired` is inherited.
-
   test_functionCallInCascade_assignment() async {
     await assertNoDiagnostics(r'''
 void f() async {
@@ -199,11 +197,27 @@
     await assertNoDiagnostics(r'''
 import 'package:meta/meta.dart';
 void f() async {
-  C()..doAsync();
+  C()..m();
 }
 class C {
   @awaitNotRequired
-  Future<void> doAsync() async {}
+  Future<void> m() async {}
+}
+''');
+  }
+
+  test_functionCallInCascade_awaitNotRequiredInherited() async {
+    await assertNoDiagnostics(r'''
+import 'package:meta/meta.dart';
+void f() async {
+  C()..m();
+}
+class C {
+  @awaitNotRequired
+  Future<void> m() async {}
+}
+class D {
+  Future<void> m() => Future.value();
 }
 ''');
   }
@@ -233,6 +247,20 @@
   }
 
   test_instanceProperty_unawaited() async {
+    await assertDiagnostics(
+      r'''
+void f(C c) async {
+  c.p;
+}
+abstract class C {
+  Future<int> get p;
+}
+''',
+      [lint(22, 4)],
+    );
+  }
+
+  test_instanceProperty_unawaited_awaitNotRequired() async {
     await assertNoDiagnostics(r'''
 import 'package:meta/meta.dart';
 void f(C c) async {
@@ -245,18 +273,20 @@
 ''');
   }
 
-  test_instanceProperty_unawaited_awaitNotRequired() async {
-    await assertDiagnostics(
-      r'''
-void f(C c) async {
-  c.p;
+  test_instanceProperty_unawaited_awaitNotRequiredInherited() async {
+    await assertNoDiagnostics(r'''
+import 'package:meta/meta.dart';
+void f(D d) async {
+  d.p;
 }
 abstract class C {
+  @awaitNotRequired
   Future<int> get p;
 }
-''',
-      [lint(22, 4)],
-    );
+class D extends C {
+  Future<int> p = Future.value(7);
+}
+''');
   }
 
   test_parameter_unawaited() async {