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 <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Sam Rawlins
2025-04-29 17:44:59 -07:00
committed by Commit Queue
parent c497d099db
commit 7d1c867f42
2 changed files with 84 additions and 25 deletions
@@ -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<void> {
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]);
}
}
@@ -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<void> future) async {
await future;
void f(Future<void> p) async {
await p;
}
''');
}
test_future_nullable() async {
await assertNoDiagnostics(r'''
void f(Future<void>? p) async {
await p;
}
''');
}
@@ -64,16 +80,25 @@ void f(Future<void> future) async {
test_futureOr() async {
await assertNoDiagnostics(r'''
import 'dart:async';
void f(FutureOr<int> future) async {
await future;
void f(FutureOr<int> p) async {
await p;
}
''');
}
test_futureOr_nullable() async {
await assertNoDiagnostics(r'''
import 'dart:async';
void f(FutureOr<int>? p) async {
await p;
}
''');
}
test_futureSubClass() async {
await assertNoDiagnostics(r'''
void f(MyFuture future) async {
await future;
void f(MyFuture<int> p) async {
await p;
}
abstract class MyFuture<T> implements Future<T> {}
''');
@@ -90,6 +115,16 @@ void f() async {
);
}
test_intersectionType_subtypeOfFuture() async {
await assertNoDiagnostics(r'''
void f<T>(T f) async {
if (f is Future<int>) {
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>(T f) async {
await f;
}
''',
[lint(25, 5)],
);
}
test_typeVariable_boundToFuture() async {
await assertNoDiagnostics(r'''
void f<T extends Future<dynamic>>(T f) async {
await f;
}
''');
}
test_undefinedClass() async {
await assertDiagnostics(
r'''