Skip to main content

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
8#[doc = r" The `async_fn_in_trait` lint detects use of `async fn` in the"]
#[doc = r" definition of a publicly-reachable trait."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" pub trait Trait {"]
#[doc = r"     async fn method(&self);"]
#[doc = r" }"]
#[doc = r" # fn main() {}"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc = r" When `async fn` is used in a trait definition, the trait does not"]
#[doc =
r" promise that the opaque [`Future`] returned by the associated function"]
#[doc =
r" or method will implement any [auto traits] such as [`Send`]. This may"]
#[doc =
r" be surprising and may make the associated functions or methods on the"]
#[doc =
r" trait less useful than intended. On traits exposed publicly from a"]
#[doc =
r" crate, this may affect downstream crates whose authors cannot alter"]
#[doc = r" the trait definition."]
#[doc = r""]
#[doc = r" For example, this code is invalid:"]
#[doc = r""]
#[doc = r" ```rust,compile_fail"]
#[doc = r" pub trait Trait {"]
#[doc = r"     async fn method(&self) {}"]
#[doc = r" }"]
#[doc = r""]
#[doc = r" fn test<T: Trait>(x: T) {"]
#[doc = r"     fn spawn<T: Send>(_: T) {}"]
#[doc = r"     spawn(x.method()); // Not OK."]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc =
r" This lint exists to warn authors of publicly-reachable traits that"]
#[doc =
r" they may want to consider desugaring the `async fn` to a normal `fn`"]
#[doc = r" that returns an opaque `impl Future<..> + Send` type."]
#[doc = r""]
#[doc = r" For example, instead of:"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" pub trait Trait {"]
#[doc = r"     async fn method(&self) {}"]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" The author of the trait may want to write:"]
#[doc = r""]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" use core::future::Future;"]
#[doc = r" pub trait Trait {"]
#[doc =
r"     fn method(&self) -> impl Future<Output = ()> + Send { async {} }"]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc =
r" This still allows the use of `async fn` within impls of the trait."]
#[doc =
r" However, it also means that the trait will never be compatible with"]
#[doc =
r" impls where the returned [`Future`] of the method does not implement"]
#[doc = r" `Send`."]
#[doc = r""]
#[doc =
r" Conversely, if the trait is used only locally, if it is never used in"]
#[doc =
r" generic functions, or if it is only used in single-threaded contexts"]
#[doc =
r" that do not care whether the returned [`Future`] implements [`Send`],"]
#[doc = r" then the lint may be suppressed."]
#[doc = r""]
#[doc =
r" [`Future`]: https://doc.rust-lang.org/core/future/trait.Future.html"]
#[doc = r" [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html"]
#[doc =
r" [auto traits]: https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits"]
pub static ASYNC_FN_IN_TRAIT: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "ASYNC_FN_IN_TRAIT",
            default_level: ::rustc_lint_defs::Warn,
            desc: "use of `async fn` in definition of a publicly-reachable trait",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_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
86#[doc =
r" Lint for use of `async fn` in the definition of a publicly-reachable"]
#[doc = r" trait."]
pub struct AsyncFnInTrait;
#[automatically_derived]
impl ::core::marker::Copy for AsyncFnInTrait { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for AsyncFnInTrait { }
#[automatically_derived]
impl ::core::clone::Clone for AsyncFnInTrait {
    #[inline]
    fn clone(&self) -> AsyncFnInTrait { *self }
}
impl ::rustc_lint_defs::LintPass for AsyncFnInTrait {
    fn name(&self) -> &'static str { "AsyncFnInTrait" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        <[_]>::into_vec(::alloc::boxed::box_new([ASYNC_FN_IN_TRAIT]))
    }
}
impl AsyncFnInTrait {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        <[_]>::into_vec(::alloc::boxed::box_new([ASYNC_FN_IN_TRAIT]))
    }
}declare_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}