Auto merge of #116184 - compiler-errors:afit-lint, r=tmandry
Add `async_fn_in_trait` lint cc https://github.com/rust-lang/rust/pull/115822#issuecomment-1731168465 Mostly unsure what the messaging should be. Feedback required. r? `@tmandry`
This commit is contained in:
commit
b781645332
33 changed files with 308 additions and 60 deletions
128
compiler/rustc_lint/src/async_fn_in_trait.rs
Normal file
128
compiler/rustc_lint/src/async_fn_in_trait.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
use crate::lints::AsyncFnInTraitDiag;
|
||||
use crate::LateContext;
|
||||
use crate::LateLintPass;
|
||||
use rustc_hir as hir;
|
||||
use rustc_trait_selection::traits::error_reporting::suggestions::suggest_desugaring_async_fn_to_impl_future_in_trait;
|
||||
|
||||
declare_lint! {
|
||||
/// The `async_fn_in_trait` lint detects use of `async fn` in the
|
||||
/// definition of a publicly-reachable trait.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(async_fn_in_trait)]
|
||||
/// pub trait Trait {
|
||||
/// async fn method(&self);
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// {{produces}}
|
||||
///
|
||||
/// ### Explanation
|
||||
///
|
||||
/// When `async fn` is used in a trait definition, the trait does not
|
||||
/// promise that the opaque [`Future`] returned by the associated function
|
||||
/// or method will implement any [auto traits] such as [`Send`]. This may
|
||||
/// be surprising and may make the associated functions or methods on the
|
||||
/// trait less useful than intended. On traits exposed publicly from a
|
||||
/// crate, this may affect downstream crates whose authors cannot alter
|
||||
/// the trait definition.
|
||||
///
|
||||
/// For example, this code is invalid:
|
||||
///
|
||||
/// ```rust,compile_fail
|
||||
/// # #![feature(async_fn_in_trait)]
|
||||
/// pub trait Trait {
|
||||
/// async fn method(&self) {}
|
||||
/// }
|
||||
///
|
||||
/// fn test<T: Trait>(x: T) {
|
||||
/// fn spawn<T: Send>(_: T) {}
|
||||
/// spawn(x.method()); // Not OK.
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This lint exists to warn authors of publicly-reachable traits that
|
||||
/// they may want to consider desugaring the `async fn` to a normal `fn`
|
||||
/// that returns an opaque `impl Future<..> + Send` type.
|
||||
///
|
||||
/// For example, instead of:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(async_fn_in_trait)]
|
||||
/// pub trait Trait {
|
||||
/// async fn method(&self) {}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The author of the trait may want to write:
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(return_position_impl_trait_in_trait)]
|
||||
/// use core::future::Future;
|
||||
/// pub trait Trait {
|
||||
/// fn method(&self) -> impl Future<Output = ()> + Send { async {} }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This still allows the use of `async fn` within impls of the trait.
|
||||
/// However, it also means that the trait will never be compatible with
|
||||
/// impls where the returned [`Future`] of the method does not implement
|
||||
/// `Send`.
|
||||
///
|
||||
/// Conversely, if the trait is used only locally, if it is never used in
|
||||
/// generic functions, or if it is only used in single-threaded contexts
|
||||
/// that do not care whether the returned [`Future`] implements [`Send`],
|
||||
/// then the lint may be suppressed.
|
||||
///
|
||||
/// [`Future`]: https://doc.rust-lang.org/core/future/trait.Future.html
|
||||
/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
|
||||
/// [auto traits]: https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits
|
||||
pub ASYNC_FN_IN_TRAIT,
|
||||
Warn,
|
||||
"use of `async fn` in definition of a publicly-reachable trait"
|
||||
}
|
||||
|
||||
declare_lint_pass!(
|
||||
/// Lint for use of `async fn` in the definition of a publicly-reachable
|
||||
/// trait.
|
||||
AsyncFnInTrait => [ASYNC_FN_IN_TRAIT]
|
||||
);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for AsyncFnInTrait {
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
|
||||
if let hir::TraitItemKind::Fn(sig, body) = item.kind
|
||||
&& let hir::IsAsync::Async(async_span) = sig.header.asyncness
|
||||
{
|
||||
// RTN can be used to bound `async fn` in traits in a better way than "always"
|
||||
if cx.tcx.features().return_type_notation {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only need to think about library implications of reachable traits
|
||||
if !cx.tcx.effective_visibilities(()).is_reachable(item.owner_id.def_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) =
|
||||
sig.decl.output
|
||||
else {
|
||||
// This should never happen, but let's not ICE.
|
||||
return;
|
||||
};
|
||||
let sugg = suggest_desugaring_async_fn_to_impl_future_in_trait(
|
||||
cx.tcx,
|
||||
sig,
|
||||
body,
|
||||
def.owner_id.def_id,
|
||||
" + Send",
|
||||
);
|
||||
cx.tcx.emit_spanned_lint(ASYNC_FN_IN_TRAIT, item.hir_id(), async_span, AsyncFnInTraitDiag {
|
||||
sugg
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ extern crate rustc_session;
|
|||
extern crate tracing;
|
||||
|
||||
mod array_into_iter;
|
||||
mod async_fn_in_trait;
|
||||
pub mod builtin;
|
||||
mod context;
|
||||
mod deref_into_dyn_supertrait;
|
||||
|
@ -96,6 +97,7 @@ use rustc_session::lint::builtin::{
|
|||
};
|
||||
|
||||
use array_into_iter::ArrayIntoIter;
|
||||
use async_fn_in_trait::AsyncFnInTrait;
|
||||
use builtin::*;
|
||||
use deref_into_dyn_supertrait::*;
|
||||
use drop_forget_useless::*;
|
||||
|
@ -234,6 +236,7 @@ late_lint_methods!(
|
|||
MapUnitFn: MapUnitFn,
|
||||
MissingDebugImplementations: MissingDebugImplementations,
|
||||
MissingDoc: MissingDoc,
|
||||
AsyncFnInTrait: AsyncFnInTrait,
|
||||
]
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1818,3 +1818,24 @@ pub struct UnusedAllocationDiag;
|
|||
#[derive(LintDiagnostic)]
|
||||
#[diag(lint_unused_allocation_mut)]
|
||||
pub struct UnusedAllocationMutDiag;
|
||||
|
||||
pub struct AsyncFnInTraitDiag {
|
||||
pub sugg: Option<Vec<(Span, String)>>,
|
||||
}
|
||||
|
||||
impl<'a> DecorateLint<'a, ()> for AsyncFnInTraitDiag {
|
||||
fn decorate_lint<'b>(
|
||||
self,
|
||||
diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>,
|
||||
) -> &'b mut rustc_errors::DiagnosticBuilder<'a, ()> {
|
||||
diag.note(fluent::lint_note);
|
||||
if let Some(sugg) = self.sugg {
|
||||
diag.multipart_suggestion(fluent::lint_suggestion, sugg, Applicability::MaybeIncorrect);
|
||||
}
|
||||
diag
|
||||
}
|
||||
|
||||
fn msg(&self) -> rustc_errors::DiagnosticMessage {
|
||||
fluent::lint_async_fn_in_trait
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue