rust/clippy_lints/src/future_not_send.rs
2025-01-28 19:08:50 +00:00

156 lines
6.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::return_ty;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, FnDecl};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::print::PrintTraitRefExt;
use rustc_middle::ty::{
self, AliasTy, Binder, ClauseKind, PredicateKind, Ty, TyCtxt, TypeVisitable, TypeVisitableExt, TypeVisitor,
};
use rustc_session::declare_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, sym};
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
use rustc_trait_selection::traits::{self, FulfillmentError, ObligationCtxt};
declare_clippy_lint! {
/// ### What it does
/// This lint requires Future implementations returned from
/// functions and methods to implement the `Send` marker trait,
/// ignoring type parameters.
///
/// If a function is generic and its Future conditionally implements `Send`
/// based on a generic parameter then it is considered `Send` and no warning is emitted.
///
/// This can be used by library authors (public and internal) to ensure
/// their functions are compatible with both multi-threaded runtimes that require `Send` futures,
/// as well as single-threaded runtimes where callers may choose `!Send` types
/// for generic parameters.
///
/// ### Why is this bad?
/// A Future implementation captures some state that it
/// needs to eventually produce its final value. When targeting a multithreaded
/// executor (which is the norm on non-embedded devices) this means that this
/// state may need to be transported to other threads, in other words the
/// whole Future needs to implement the `Send` marker trait. If it does not,
/// then the resulting Future cannot be submitted to a thread pool in the
/// end users code.
///
/// Especially for generic functions it can be confusing to leave the
/// discovery of this problem to the end user: the reported error location
/// will be far from its cause and can in many cases not even be fixed without
/// modifying the library where the offending Future implementation is
/// produced.
///
/// ### Example
/// ```no_run
/// async fn not_send(bytes: std::rc::Rc<[u8]>) {}
/// ```
/// Use instead:
/// ```no_run
/// async fn is_send(bytes: std::sync::Arc<[u8]>) {}
/// ```
#[clippy::version = "1.44.0"]
pub FUTURE_NOT_SEND,
nursery,
"public Futures must be Send"
}
declare_lint_pass!(FutureNotSend => [FUTURE_NOT_SEND]);
impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl<'tcx>,
_: &'tcx Body<'tcx>,
_: Span,
fn_def_id: LocalDefId,
) {
if let FnKind::Closure = kind {
return;
}
let ret_ty = return_ty(cx, cx.tcx.local_def_id_to_hir_id(fn_def_id).expect_owner());
if let ty::Alias(ty::Opaque, AliasTy { def_id, args, .. }) = *ret_ty.kind()
&& let Some(future_trait) = cx.tcx.lang_items().future_trait()
&& let Some(send_trait) = cx.tcx.get_diagnostic_item(sym::Send)
{
let preds = cx.tcx.explicit_item_self_bounds(def_id);
let is_future = preds.iter_instantiated_copied(cx.tcx, args).any(|(p, _)| {
p.as_trait_clause()
.is_some_and(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait)
});
if is_future {
let span = decl.output.span();
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
let ocx = ObligationCtxt::new_with_diagnostics(&infcx);
let cause = traits::ObligationCause::misc(span, fn_def_id);
ocx.register_bound(cause, cx.param_env, ret_ty, send_trait);
let send_errors = ocx.select_all_or_error();
// Allow errors that try to prove `Send` for types that "mention" a generic parameter at the "top
// level".
// For example, allow errors that `T: Send` can't be proven, but reject `Rc<T>: Send` errors,
// which is always unconditionally `!Send` for any possible type `T`.
//
// We also allow associated type projections if the self type is either itself a projection or a
// type parameter.
// This is to prevent emitting warnings for e.g. holding a `<Fut as Future>::Output` across await
// points, where `Fut` is a type parameter.
let is_send = send_errors.iter().all(|err| {
err.obligation
.predicate
.as_trait_clause()
.map(Binder::skip_binder)
.is_some_and(|pred| {
pred.def_id() == send_trait
&& pred.self_ty().has_param()
&& TyParamAtTopLevelVisitor.visit_ty(pred.self_ty()) == ControlFlow::Break(true)
})
});
if !is_send {
span_lint_and_then(
cx,
FUTURE_NOT_SEND,
span,
"future cannot be sent between threads safely",
|db| {
for FulfillmentError { obligation, .. } in send_errors {
infcx
.err_ctxt()
.maybe_note_obligation_cause_for_async_await(db, &obligation);
if let PredicateKind::Clause(ClauseKind::Trait(trait_pred)) =
obligation.predicate.kind().skip_binder()
{
db.note(format!(
"`{}` doesn't implement `{}`",
trait_pred.self_ty(),
trait_pred.trait_ref.print_only_trait_path(),
));
}
}
},
);
}
}
}
}
}
struct TyParamAtTopLevelVisitor;
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for TyParamAtTopLevelVisitor {
type Result = ControlFlow<bool>;
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
match ty.kind() {
ty::Param(_) => ControlFlow::Break(true),
ty::Alias(ty::AliasTyKind::Projection, ty) => ty.visit_with(self),
_ => ControlFlow::Break(false),
}
}
}