From 7d1c867f4275bf61d8ba8d59b7cc233748c3e292 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 29 Apr 2025 17:44:59 -0700 Subject: [PATCH] linter: Fix await_only_futures: Fix for type variables and intersection types Fixes https://github.com/dart-lang/sdk/issues/58492 Change-Id: Iae036742b05815332553a540c95b4fdf5ad83413 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/425402 Commit-Queue: Samuel Rawlins Reviewed-by: Brian Wilkerson --- .../lib/src/rules/await_only_futures.dart | 29 ++++--- .../test/rules/await_only_futures_test.dart | 80 ++++++++++++++++--- 2 files changed, 84 insertions(+), 25 deletions(-) diff --git a/pkg/linter/lib/src/rules/await_only_futures.dart b/pkg/linter/lib/src/rules/await_only_futures.dart index df419951ea1..a92e3060c81 100644 --- a/pkg/linter/lib/src/rules/await_only_futures.dart +++ b/pkg/linter/lib/src/rules/await_only_futures.dart @@ -8,7 +8,6 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import '../analyzer.dart'; -import '../extensions.dart'; const _desc = r'Await only futures.'; @@ -24,7 +23,7 @@ class AwaitOnlyFutures extends LintRule { NodeLintRegistry registry, LinterContext context, ) { - var visitor = _Visitor(this); + var visitor = _Visitor(this, context); registry.addAwaitExpression(this, visitor); } } @@ -32,22 +31,28 @@ class AwaitOnlyFutures extends LintRule { class _Visitor extends SimpleAstVisitor { final LintRule rule; - _Visitor(this.rule); + final LinterContext context; + + _Visitor(this.rule, this.context); @override void visitAwaitExpression(AwaitExpression node) { if (node.expression is NullLiteral) return; var type = node.expression.staticType; - if (!(type == null || - type.element3 is ExtensionTypeElement || - type.isDartAsyncFuture || - type is DynamicType || - type is InvalidType || - type.extendsClass('Future', 'dart.async') || - type.implementsInterface('Future', 'dart.async') || - type.isDartAsyncFutureOr)) { - rule.reportAtToken(node.awaitKeyword, arguments: [type]); + if (type == null || type is DynamicType) return; + type = context.typeSystem.promoteToNonNull(type); + if (type.isDartAsyncFutureOr) return; + if (type.element3 is ExtensionTypeElement) return; + if (type is InvalidType) return; + + if (context.typeSystem.isAssignableTo( + type, + context.typeProvider.futureDynamicType, + )) { + return; } + + rule.reportAtToken(node.awaitKeyword, arguments: [type]); } } diff --git a/pkg/linter/test/rules/await_only_futures_test.dart b/pkg/linter/test/rules/await_only_futures_test.dart index 68b86f62f85..fb24fa4d6b0 100644 --- a/pkg/linter/test/rules/await_only_futures_test.dart +++ b/pkg/linter/test/rules/await_only_futures_test.dart @@ -19,20 +19,28 @@ class AwaitOnlyFuturesTest extends LintRuleTest { test_dynamic() async { await assertNoDiagnostics(r''' -void f(dynamic future) async { - await future; +void f(dynamic p) async { + await p; } '''); } - // TODO(srawlins): Test `await x` for `T extends Future` type variable. - test_extensionType_implementingFuture() async { await assertNoDiagnostics(r''' -extension type E(Future f) implements Future { } +extension type E(Future f) implements Future {} -void f() async { - await E(Future.value()); +void f(E p) async { + await p; +} +'''); + } + + test_extensionType_implementingFuture_nullable() async { + await assertNoDiagnostics(r''' +extension type E(Future f) implements Future {} + +void f(E? p) async { + await p; } '''); } @@ -55,8 +63,16 @@ void f() async { test_future() async { await assertNoDiagnostics(r''' -void f(Future future) async { - await future; +void f(Future p) async { + await p; +} +'''); + } + + test_future_nullable() async { + await assertNoDiagnostics(r''' +void f(Future? p) async { + await p; } '''); } @@ -64,16 +80,25 @@ void f(Future future) async { test_futureOr() async { await assertNoDiagnostics(r''' import 'dart:async'; -void f(FutureOr future) async { - await future; +void f(FutureOr p) async { + await p; +} +'''); + } + + test_futureOr_nullable() async { + await assertNoDiagnostics(r''' +import 'dart:async'; +void f(FutureOr? p) async { + await p; } '''); } test_futureSubClass() async { await assertNoDiagnostics(r''' -void f(MyFuture future) async { - await future; +void f(MyFuture p) async { + await p; } abstract class MyFuture implements Future {} '''); @@ -90,6 +115,16 @@ void f() async { ); } + test_intersectionType_subtypeOfFuture() async { + await assertNoDiagnostics(r''' +void f(T f) async { + if (f is Future) { + await f; + } +} +'''); + } + test_null() async { await assertNoDiagnostics(r''' void f() async { @@ -98,6 +133,25 @@ void f() async { '''); } + test_typeVariable() async { + await assertDiagnostics( + r''' +void f(T f) async { + await f; +} +''', + [lint(25, 5)], + ); + } + + test_typeVariable_boundToFuture() async { + await assertNoDiagnostics(r''' +void f>(T f) async { + await f; +} +'''); + } + test_undefinedClass() async { await assertDiagnostics( r'''