rustc_lint/async_fn_in_trait.rs
1use rustc_hir as hir;
2use rustc_session::{declare_lint, declare_lint_pass};
3use rustc_trait_selection::error_reporting::traits::suggestions::suggest_desugaring_async_fn_to_impl_future_in_trait;
4
5use crate::lints::AsyncFnInTraitDiag;
6use crate::{LateContext, LateLintPass};
7
8declare_lint! {
9 /// The `async_fn_in_trait` lint detects use of `async fn` in the
10 /// definition of a publicly-reachable trait.
11 ///
12 /// ### Example
13 ///
14 /// ```rust
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 /// pub trait Trait {
37 /// async fn method(&self) {}
38 /// }
39 ///
40 /// fn test<T: Trait>(x: T) {
41 /// fn spawn<T: Send>(_: T) {}
42 /// spawn(x.method()); // Not OK.
43 /// }
44 /// ```
45 ///
46 /// This lint exists to warn authors of publicly-reachable traits that
47 /// they may want to consider desugaring the `async fn` to a normal `fn`
48 /// that returns an opaque `impl Future<..> + Send` type.
49 ///
50 /// For example, instead of:
51 ///
52 /// ```rust
53 /// pub trait Trait {
54 /// async fn method(&self) {}
55 /// }
56 /// ```
57 ///
58 /// The author of the trait may want to write:
59 ///
60 ///
61 /// ```rust
62 /// use core::future::Future;
63 /// pub trait Trait {
64 /// fn method(&self) -> impl Future<Output = ()> + Send { async {} }
65 /// }
66 /// ```
67 ///
68 /// This still allows the use of `async fn` within impls of the trait.
69 /// However, it also means that the trait will never be compatible with
70 /// impls where the returned [`Future`] of the method does not implement
71 /// `Send`.
72 ///
73 /// Conversely, if the trait is used only locally, if it is never used in
74 /// generic functions, or if it is only used in single-threaded contexts
75 /// that do not care whether the returned [`Future`] implements [`Send`],
76 /// then the lint may be suppressed.
77 ///
78 /// [`Future`]: https://doc.rust-lang.org/core/future/trait.Future.html
79 /// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
80 /// [auto traits]: https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits
81 pub ASYNC_FN_IN_TRAIT,
82 Warn,
83 "use of `async fn` in definition of a publicly-reachable trait"
84}
85
86declare_lint_pass!(
87 /// Lint for use of `async fn` in the definition of a publicly-reachable
88 /// trait.
89 AsyncFnInTrait => [ASYNC_FN_IN_TRAIT]
90);
91
92impl<'tcx> LateLintPass<'tcx> for AsyncFnInTrait {
93 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
94 if let hir::TraitItemKind::Fn(sig, body) = item.kind
95 && let hir::IsAsync::Async(async_span) = sig.header.asyncness
96 {
97 // RTN can be used to bound `async fn` in traits in a better way than "always"
98 if cx.tcx.features().return_type_notation() {
99 return;
100 }
101
102 // Only need to think about library implications of reachable traits
103 if !cx.tcx.effective_visibilities(()).is_reachable(item.owner_id.def_id) {
104 return;
105 }
106
107 let hir::FnRetTy::Return(hir::Ty {
108 kind: hir::TyKind::OpaqueDef(opaq_def, ..), ..
109 }) = sig.decl.output
110 else {
111 // This should never happen, but let's not ICE.
112 return;
113 };
114 let sugg = suggest_desugaring_async_fn_to_impl_future_in_trait(
115 cx.tcx,
116 sig,
117 body,
118 opaq_def.def_id,
119 " + Send",
120 );
121 cx.tcx.emit_node_span_lint(
122 ASYNC_FN_IN_TRAIT,
123 item.hir_id(),
124 async_span,
125 AsyncFnInTraitDiag { sugg },
126 );
127 }
128 }
129}