Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a3aabed

Browse files
Add async_fn_in_trait lint
1 parent 21627d6 commit a3aabed

File tree

7 files changed

+186
-53
lines changed

7 files changed

+186
-53
lines changed

‎compiler/rustc_lint/messages.ftl‎

Lines changed: 4 additions & 0 deletions
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
Lines changed: 58 additions & 0 deletions
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‎

Lines changed: 3 additions & 0 deletions
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;
@@ -100,6 +101,7 @@ use rustc_span::symbol::Ident;
100101
use rustc_span::Span;
101102

102103
use array_into_iter::ArrayIntoIter;
104+
use async_fn_in_trait::AsyncFnInTrait;
103105
use builtin::*;
104106
use deref_into_dyn_supertrait::*;
105107
use drop_forget_useless::*;
@@ -238,6 +240,7 @@ late_lint_methods!(
238240
MapUnitFn: MapUnitFn,
239241
MissingDebugImplementations: MissingDebugImplementations,
240242
MissingDoc: MissingDoc,
243+
AsyncFnInTrait: AsyncFnInTrait,
241244
]
242245
]
243246
);

‎compiler/rustc_lint/src/lints.rs‎

Lines changed: 21 additions & 0 deletions
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‎

Lines changed: 69 additions & 53 deletions
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+
}
Lines changed: 11 additions & 0 deletions
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() {}
Lines changed: 20 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /