Skip to content

Commit ec79720

Browse files
Add async_fn_in_trait lint
1 parent 2e5a9dd commit ec79720

File tree

7 files changed

+186
-53
lines changed

7 files changed

+186
-53
lines changed

compiler/rustc_lint/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ lint_array_into_iter =
55
.use_explicit_into_iter_suggestion =
66
or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value
77
8+
lint_async_fn_in_trait = usage of `async fn` in trait is discouraged because they do not automatically have auto trait bounds
9+
.note = you can suppress this lint if you plan to use the trait locally, for concrete types, or do not care about auto traits like `Send` on the future
10+
.suggestion = you can alternatively desugar the `async fn` and any add additional traits such as `Send` to the signature
11+
812
lint_atomic_ordering_fence = memory fences cannot have `Relaxed` ordering
913
.help = consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst`
1014
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use crate::lints::AsyncFnInTraitDiag;
2+
use crate::LateContext;
3+
use crate::LateLintPass;
4+
use rustc_hir as hir;
5+
use rustc_trait_selection::traits::error_reporting::suggestions::suggest_desugaring_async_fn_to_impl_future_in_trait;
6+
7+
declare_lint! {
8+
/// TODO
9+
///
10+
/// ### Example
11+
///
12+
/// ```rust
13+
/// fn foo<T: Drop>() {}
14+
/// ```
15+
///
16+
/// {{produces}}
17+
///
18+
/// ### Explanation
19+
///
20+
/// TODO
21+
pub ASYNC_FN_IN_TRAIT,
22+
Warn,
23+
"TODO"
24+
}
25+
26+
declare_lint_pass!(
27+
// TODO:
28+
AsyncFnInTrait => [ASYNC_FN_IN_TRAIT]
29+
);
30+
31+
impl<'tcx> LateLintPass<'tcx> for AsyncFnInTrait {
32+
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
33+
if let hir::TraitItemKind::Fn(sig, body) = item.kind
34+
&& let hir::IsAsync::Async(async_span) = sig.header.asyncness
35+
{
36+
if cx.tcx.features().return_type_notation {
37+
return;
38+
}
39+
40+
let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
41+
sig.decl.output
42+
else {
43+
// This should never happen, but let's not ICE.
44+
return;
45+
};
46+
let sugg = suggest_desugaring_async_fn_to_impl_future_in_trait(
47+
cx.tcx,
48+
sig,
49+
body,
50+
def.owner_id.def_id,
51+
" + Send",
52+
);
53+
cx.tcx.emit_spanned_lint(ASYNC_FN_IN_TRAIT, item.hir_id(), async_span, AsyncFnInTraitDiag {
54+
sugg
55+
});
56+
}
57+
}
58+
}

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ extern crate rustc_session;
5050
extern crate tracing;
5151

5252
mod array_into_iter;
53+
mod async_fn_in_trait;
5354
pub mod builtin;
5455
mod context;
5556
mod deref_into_dyn_supertrait;
@@ -96,6 +97,7 @@ use rustc_session::lint::builtin::{
9697
};
9798

9899
use array_into_iter::ArrayIntoIter;
100+
use async_fn_in_trait::AsyncFnInTrait;
99101
use builtin::*;
100102
use deref_into_dyn_supertrait::*;
101103
use drop_forget_useless::*;
@@ -234,6 +236,7 @@ late_lint_methods!(
234236
MapUnitFn: MapUnitFn,
235237
MissingDebugImplementations: MissingDebugImplementations,
236238
MissingDoc: MissingDoc,
239+
AsyncFnInTrait: AsyncFnInTrait,
237240
]
238241
]
239242
);

compiler/rustc_lint/src/lints.rs

+21
Original file line numberDiff line numberDiff line change
@@ -1818,3 +1818,24 @@ pub struct UnusedAllocationDiag;
18181818
#[derive(LintDiagnostic)]
18191819
#[diag(lint_unused_allocation_mut)]
18201820
pub struct UnusedAllocationMutDiag;
1821+
1822+
pub struct AsyncFnInTraitDiag {
1823+
pub sugg: Option<Vec<(Span, String)>>,
1824+
}
1825+
1826+
impl<'a> DecorateLint<'a, ()> for AsyncFnInTraitDiag {
1827+
fn decorate_lint<'b>(
1828+
self,
1829+
diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>,
1830+
) -> &'b mut rustc_errors::DiagnosticBuilder<'a, ()> {
1831+
diag.note(fluent::lint_note);
1832+
if let Some(sugg) = self.sugg {
1833+
diag.multipart_suggestion(fluent::lint_suggestion, sugg, Applicability::MaybeIncorrect);
1834+
}
1835+
diag
1836+
}
1837+
1838+
fn msg(&self) -> rustc_errors::DiagnosticMessage {
1839+
fluent::lint_async_fn_in_trait
1840+
}
1841+
}

compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

+69-53
Original file line numberDiff line numberDiff line change
@@ -4000,14 +4000,6 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
40004000

40014001
// ... whose signature is `async` (i.e. this is an AFIT)
40024002
let (sig, body) = item.expect_fn();
4003-
let hir::IsAsync::Async(async_span) = sig.header.asyncness else {
4004-
return;
4005-
};
4006-
let Ok(async_span) =
4007-
self.tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
4008-
else {
4009-
return;
4010-
};
40114003
let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
40124004
sig.decl.output
40134005
else {
@@ -4021,55 +4013,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
40214013
return;
40224014
}
40234015

4024-
let future = self.tcx.hir().item(*def).expect_opaque_ty();
4025-
let Some(hir::GenericBound::LangItemTrait(_, _, _, generics)) = future.bounds.get(0) else {
4026-
// `async fn` should always lower to a lang item bound... but don't ICE.
4027-
return;
4028-
};
4029-
let Some(hir::TypeBindingKind::Equality { term: hir::Term::Ty(future_output_ty) }) =
4030-
generics.bindings.get(0).map(|binding| binding.kind)
4031-
else {
4032-
// Also should never happen.
4016+
let Some(sugg) = suggest_desugaring_async_fn_to_impl_future_in_trait(
4017+
self.tcx,
4018+
*sig,
4019+
*body,
4020+
opaque_def_id.expect_local(),
4021+
&format!(" + {auto_trait}"),
4022+
) else {
40334023
return;
40344024
};
40354025

40364026
let function_name = self.tcx.def_path_str(fn_def_id);
4037-
4038-
let mut sugg = if future_output_ty.span.is_empty() {
4039-
vec![
4040-
(async_span, String::new()),
4041-
(
4042-
future_output_ty.span,
4043-
format!(" -> impl std::future::Future<Output = ()> + {auto_trait}"),
4044-
),
4045-
]
4046-
} else {
4047-
vec![
4048-
(
4049-
future_output_ty.span.shrink_to_lo(),
4050-
"impl std::future::Future<Output = ".to_owned(),
4051-
),
4052-
(future_output_ty.span.shrink_to_hi(), format!("> + {auto_trait}")),
4053-
(async_span, String::new()),
4054-
]
4055-
};
4056-
4057-
// If there's a body, we also need to wrap it in `async {}`
4058-
if let hir::TraitFn::Provided(body) = body {
4059-
let body = self.tcx.hir().body(*body);
4060-
let body_span = body.value.span;
4061-
let body_span_without_braces =
4062-
body_span.with_lo(body_span.lo() + BytePos(1)).with_hi(body_span.hi() - BytePos(1));
4063-
if body_span_without_braces.is_empty() {
4064-
sugg.push((body_span_without_braces, " async {} ".to_owned()));
4065-
} else {
4066-
sugg.extend([
4067-
(body_span_without_braces.shrink_to_lo(), "async {".to_owned()),
4068-
(body_span_without_braces.shrink_to_hi(), "} ".to_owned()),
4069-
]);
4070-
}
4071-
}
4072-
40734027
err.multipart_suggestion(
40744028
format!(
40754029
"`{auto_trait}` can be made part of the associated future's \
@@ -4321,3 +4275,65 @@ impl<'tcx> TypeFolder<TyCtxt<'tcx>> for ReplaceImplTraitFolder<'tcx> {
43214275
self.tcx
43224276
}
43234277
}
4278+
4279+
pub fn suggest_desugaring_async_fn_to_impl_future_in_trait<'tcx>(
4280+
tcx: TyCtxt<'tcx>,
4281+
sig: hir::FnSig<'tcx>,
4282+
body: hir::TraitFn<'tcx>,
4283+
opaque_def_id: LocalDefId,
4284+
add_bounds: &str,
4285+
) -> Option<Vec<(Span, String)>> {
4286+
let hir::IsAsync::Async(async_span) = sig.header.asyncness else {
4287+
return None;
4288+
};
4289+
let Ok(async_span) = tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
4290+
else {
4291+
return None;
4292+
};
4293+
4294+
let future = tcx.hir().get_by_def_id(opaque_def_id).expect_item().expect_opaque_ty();
4295+
let Some(hir::GenericBound::LangItemTrait(_, _, _, generics)) = future.bounds.get(0) else {
4296+
// `async fn` should always lower to a lang item bound... but don't ICE.
4297+
return None;
4298+
};
4299+
let Some(hir::TypeBindingKind::Equality { term: hir::Term::Ty(future_output_ty) }) =
4300+
generics.bindings.get(0).map(|binding| binding.kind)
4301+
else {
4302+
// Also should never happen.
4303+
return None;
4304+
};
4305+
4306+
let mut sugg = if future_output_ty.span.is_empty() {
4307+
vec![
4308+
(async_span, String::new()),
4309+
(
4310+
future_output_ty.span,
4311+
format!(" -> impl std::future::Future<Output = ()>{add_bounds}"),
4312+
),
4313+
]
4314+
} else {
4315+
vec![
4316+
(future_output_ty.span.shrink_to_lo(), "impl std::future::Future<Output = ".to_owned()),
4317+
(future_output_ty.span.shrink_to_hi(), format!(">{add_bounds}")),
4318+
(async_span, String::new()),
4319+
]
4320+
};
4321+
4322+
// If there's a body, we also need to wrap it in `async {}`
4323+
if let hir::TraitFn::Provided(body) = body {
4324+
let body = tcx.hir().body(body);
4325+
let body_span = body.value.span;
4326+
let body_span_without_braces =
4327+
body_span.with_lo(body_span.lo() + BytePos(1)).with_hi(body_span.hi() - BytePos(1));
4328+
if body_span_without_braces.is_empty() {
4329+
sugg.push((body_span_without_braces, " async {} ".to_owned()));
4330+
} else {
4331+
sugg.extend([
4332+
(body_span_without_braces.shrink_to_lo(), "async {".to_owned()),
4333+
(body_span_without_braces.shrink_to_hi(), "} ".to_owned()),
4334+
]);
4335+
}
4336+
}
4337+
4338+
Some(sugg)
4339+
}

tests/ui/async-await/in-trait/warn.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// edition: 2021
2+
3+
#![feature(async_fn_in_trait)]
4+
#![deny(async_fn_in_trait)]
5+
6+
trait Foo {
7+
async fn not_send();
8+
//~^ ERROR
9+
}
10+
11+
fn main() {}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: usage of `async fn` in trait is discouraged because they do not automatically have auto trait bounds
2+
--> $DIR/warn.rs:7:5
3+
|
4+
LL | async fn not_send();
5+
| ^^^^^
6+
|
7+
= note: you can suppress this lint if you plan to use the trait locally, for concrete types, or do not care about auto traits like `Send` on the future
8+
note: the lint level is defined here
9+
--> $DIR/warn.rs:4:9
10+
|
11+
LL | #![deny(async_fn_in_trait)]
12+
| ^^^^^^^^^^^^^^^^^
13+
help: you can alternatively desugar the `async fn` and any add additional traits such as `Send` to the signature
14+
|
15+
LL - async fn not_send();
16+
LL + fn not_send() -> impl std::future::Future<Output = ()> + Send;
17+
|
18+
19+
error: aborting due to previous error
20+

0 commit comments

Comments
 (0)