Skip to content

Commit b781645

Browse files
committed
Auto merge of #116184 - compiler-errors:afit-lint, r=tmandry
Add `async_fn_in_trait` lint cc #115822 (comment) Mostly unsure what the messaging should be. Feedback required. r? `@tmandry`
2 parents afe67fa + 2f52490 commit b781645

33 files changed

Lines changed: 308 additions & 60 deletions

‎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 = use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
9+
.note = you can suppress this lint if you plan to use the trait only in your own code, or do not care about auto traits like `Send` on the `Future`
10+
.suggestion = you can alternatively desugar to a normal `fn` that returns `impl Future` and add any desired bounds such as `Send`
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: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
usecrate::lints::AsyncFnInTraitDiag;
2+
usecrate::LateContext;
3+
usecrate::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+
/// The `async_fn_in_trait` lint detects use of `async fn` in the
9+
/// definition of a publicly-reachable trait.
10+
///
11+
/// ### Example
12+
///
13+
/// ```rust
14+
/// # #![feature(async_fn_in_trait)]
15+
/// pub trait Trait {
16+
/// async fn method(&self);
17+
/// }
18+
/// # fn main() {}
19+
/// ```
20+
///
21+
/// {{produces}}
22+
///
23+
/// ### Explanation
24+
///
25+
/// When `async fn` is used in a trait definition, the trait does not
26+
/// promise that the opaque [`Future`] returned by the associated function
27+
/// or method will implement any [auto traits] such as [`Send`]. This may
28+
/// be surprising and may make the associated functions or methods on the
29+
/// trait less useful than intended. On traits exposed publicly from a
30+
/// crate, this may affect downstream crates whose authors cannot alter
31+
/// the trait definition.
32+
///
33+
/// For example, this code is invalid:
34+
///
35+
/// ```rust,compile_fail
36+
/// # #![feature(async_fn_in_trait)]
37+
/// pub trait Trait {
38+
/// async fn method(&self) {}
39+
/// }
40+
///
41+
/// fn test<T: Trait>(x: T) {
42+
/// fn spawn<T: Send>(_: T) {}
43+
/// spawn(x.method()); // Not OK.
44+
/// }
45+
/// ```
46+
///
47+
/// This lint exists to warn authors of publicly-reachable traits that
48+
/// they may want to consider desugaring the `async fn` to a normal `fn`
49+
/// that returns an opaque `impl Future<..> + Send` type.
50+
///
51+
/// For example, instead of:
52+
///
53+
/// ```rust
54+
/// # #![feature(async_fn_in_trait)]
55+
/// pub trait Trait {
56+
/// async fn method(&self) {}
57+
/// }
58+
/// ```
59+
///
60+
/// The author of the trait may want to write:
61+
///
62+
///
63+
/// ```rust
64+
/// # #![feature(return_position_impl_trait_in_trait)]
65+
/// use core::future::Future;
66+
/// pub trait Trait {
67+
/// fn method(&self) -> impl Future<Output = ()> + Send { async {} }
68+
/// }
69+
/// ```
70+
///
71+
/// This still allows the use of `async fn` within impls of the trait.
72+
/// However, it also means that the trait will never be compatible with
73+
/// impls where the returned [`Future`] of the method does not implement
74+
/// `Send`.
75+
///
76+
/// Conversely, if the trait is used only locally, if it is never used in
77+
/// generic functions, or if it is only used in single-threaded contexts
78+
/// that do not care whether the returned [`Future`] implements [`Send`],
79+
/// then the lint may be suppressed.
80+
///
81+
/// [`Future`]: https://doc.rust-lang.org/core/future/trait.Future.html
82+
/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
83+
/// [auto traits]: https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits
84+
pubASYNC_FN_IN_TRAIT,
85+
Warn,
86+
"use of `async fn` in definition of a publicly-reachable trait"
87+
}
88+
89+
declare_lint_pass!(
90+
/// Lint for use of `async fn` in the definition of a publicly-reachable
91+
/// trait.
92+
AsyncFnInTrait => [ASYNC_FN_IN_TRAIT]
93+
);
94+
95+
impl<'tcx>LateLintPass<'tcx>forAsyncFnInTrait{
96+
fncheck_trait_item(&mutself,cx:&LateContext<'tcx>,item:&'tcx hir::TraitItem<'tcx>){
97+
iflet hir::TraitItemKind::Fn(sig, body) = item.kind
98+
&& let hir::IsAsync::Async(async_span) = sig.header.asyncness
99+
{
100+
// RTN can be used to bound `async fn` in traits in a better way than "always"
101+
if cx.tcx.features().return_type_notation{
102+
return;
103+
}
104+
105+
// Only need to think about library implications of reachable traits
106+
if !cx.tcx.effective_visibilities(()).is_reachable(item.owner_id.def_id){
107+
return;
108+
}
109+
110+
let hir::FnRetTy::Return(hir::Ty{kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
111+
sig.decl.output
112+
else{
113+
// This should never happen, but let's not ICE.
114+
return;
115+
};
116+
let sugg = suggest_desugaring_async_fn_to_impl_future_in_trait(
117+
cx.tcx,
118+
sig,
119+
body,
120+
def.owner_id.def_id,
121+
" + Send",
122+
);
123+
cx.tcx.emit_spanned_lint(ASYNC_FN_IN_TRAIT, item.hir_id(), async_span,AsyncFnInTraitDiag{
124+
sugg
125+
});
126+
}
127+
}
128+
}

‎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
externcrate tracing;
5151

5252
mod array_into_iter;
53+
mod async_fn_in_trait;
5354
pubmod 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‎

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
pubstructUnusedAllocationMutDiag;
1821+
1822+
pubstructAsyncFnInTraitDiag{
1823+
pubsugg:Option<Vec<(Span,String)>>,
1824+
}
1825+
1826+
impl<'a>DecorateLint<'a,()>forAsyncFnInTraitDiag{
1827+
fndecorate_lint<'b>(
1828+
self,
1829+
diag:&'bmut rustc_errors::DiagnosticBuilder<'a,()>,
1830+
) -> &'bmut rustc_errors::DiagnosticBuilder<'a,()>{
1831+
diag.note(fluent::lint_note);
1832+
ifletSome(sugg) = self.sugg{
1833+
diag.multipart_suggestion(fluent::lint_suggestion, sugg,Applicability::MaybeIncorrect);
1834+
}
1835+
diag
1836+
}
1837+
1838+
fnmsg(&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.asyncnesselse{
4004-
return;
4005-
};
4006-
letOk(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-
letSome(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-
letSome(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+
letSome(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-
letmut 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-
iflet 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+
pubfnsuggest_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.asyncnesselse{
4287+
returnNone;
4288+
};
4289+
letOk(async_span) = tcx.sess.source_map().span_extend_while(async_span, |c| c.is_whitespace())
4290+
else{
4291+
returnNone;
4292+
};
4293+
4294+
let future = tcx.hir().get_by_def_id(opaque_def_id).expect_item().expect_opaque_ty();
4295+
letSome(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+
returnNone;
4298+
};
4299+
letSome(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+
returnNone;
4304+
};
4305+
4306+
letmut 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+
iflet 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/async-associated-types.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ use std::fmt::Debug;
99
traitMyTrait<'a,'b,T>whereSelf:'a,T:Debug + Sized + 'b{
1010
typeMyAssoc;
1111

12+
#[allow(async_fn_in_trait)]
1213
asyncfnfoo(&'aself,key:&'bT) -> Self::MyAssoc;
1314
}
1415

1516
impl<'a,'b,T:Debug + Sized + 'b,U:'a>MyTrait<'a,'b,T>forU{
1617
typeMyAssoc = (&'aU,&'bT);
1718

19+
#[allow(async_fn_in_trait)]
1820
asyncfnfoo(&'aself,key:&'bT) -> (&'aU,&'bT){
1921
(self, key)
2022
}

‎tests/ui/async-await/in-trait/async-default-fn-overridden.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
use std::future::Future;
77

88
traitAsyncTrait{
9+
#[allow(async_fn_in_trait)]
910
asyncfndefault_impl(){
1011
assert!(false);
1112
}
1213

14+
#[allow(async_fn_in_trait)]
1315
asyncfncall_default_impl(){
1416
Self::default_impl().await
1517
}

‎tests/ui/async-await/in-trait/async-example-desugared-extra.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::pin::Pin;
1010
use std::task::Poll;
1111

1212
pubtraitMyTrait{
13+
#[allow(async_fn_in_trait)]
1314
asyncfnfoo(&self) -> i32;
1415
}
1516

‎tests/ui/async-await/in-trait/async-example-desugared.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use std::future::Future;
99

1010
traitMyTrait{
11+
#[allow(async_fn_in_trait)]
1112
asyncfnfoo(&self) -> i32;
1213
}
1314

‎tests/ui/async-await/in-trait/async-example.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
#![allow(incomplete_features)]
66

77
traitMyTrait{
8+
#[allow(async_fn_in_trait)]
89
asyncfnfoo(&self) -> i32;
10+
11+
#[allow(async_fn_in_trait)]
912
asyncfnbar(&self) -> i32;
1013
}
1114

0 commit comments

Comments
 (0)