Skip to content

Commit 83f7526

Browse files
authoredNov 15, 2024
Allow conditional Send futures in future_not_send (rust-lang#13590)
Closes rust-lang#6947 This changes the lint to allow futures which are not `Send` as a result of a generic type parameter not having a `Send` bound and only lint futures that are always `!Send` for any type, which I believe is the more useful behavior (like the comments in the linked issue explain). This is still only a heuristic (I'm not sure if there's a more general way to do this), but it should cover the common cases I could think of (including the code examples in the linked issue) changelog: [`future_not_send`]: allow conditional `Send` futures

File tree

5 files changed

+101
-59
lines changed

5 files changed

+101
-59
lines changed
 

‎clippy_lints/src/future_not_send.rs

+57-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
use std::ops::ControlFlow;
2+
13
use clippy_utils::diagnostics::span_lint_and_then;
24
use clippy_utils::return_ty;
35
use rustc_hir::intravisit::FnKind;
46
use rustc_hir::{Body, FnDecl};
57
use rustc_infer::infer::TyCtxtInferExt;
68
use rustc_lint::{LateContext, LateLintPass};
79
use rustc_middle::ty::print::PrintTraitRefExt;
8-
use rustc_middle::ty::{self, AliasTy, ClauseKind, PredicateKind};
10+
use rustc_middle::ty::{
11+
self, AliasTy, Binder, ClauseKind, PredicateKind, Ty, TyCtxt, TypeVisitable, TypeVisitableExt, TypeVisitor,
12+
};
913
use rustc_session::declare_lint_pass;
1014
use rustc_span::def_id::LocalDefId;
1115
use rustc_span::{Span, sym};
@@ -15,9 +19,16 @@ use rustc_trait_selection::traits::{self, FulfillmentError, ObligationCtxt};
1519
declare_clippy_lint! {
1620
/// ### What it does
1721
/// This lint requires Future implementations returned from
18-
/// functions and methods to implement the `Send` marker trait. It is mostly
19-
/// used by library authors (public and internal) that target an audience where
20-
/// multithreaded executors are likely to be used for running these Futures.
22+
/// functions and methods to implement the `Send` marker trait,
23+
/// ignoring type parameters.
24+
///
25+
/// If a function is generic and its Future conditionally implements `Send`
26+
/// based on a generic parameter then it is considered `Send` and no warning is emitted.
27+
///
28+
/// This can be used by library authors (public and internal) to ensure
29+
/// their functions are compatible with both multi-threaded runtimes that require `Send` futures,
30+
/// as well as single-threaded runtimes where callers may choose `!Send` types
31+
/// for generic parameters.
2132
///
2233
/// ### Why is this bad?
2334
/// A Future implementation captures some state that it
@@ -64,22 +75,46 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
6475
return;
6576
}
6677
let ret_ty = return_ty(cx, cx.tcx.local_def_id_to_hir_id(fn_def_id).expect_owner());
67-
if let ty::Alias(ty::Opaque, AliasTy { def_id, args, .. }) = *ret_ty.kind() {
78+
if let ty::Alias(ty::Opaque, AliasTy { def_id, args, .. }) = *ret_ty.kind()
79+
&& let Some(future_trait) = cx.tcx.lang_items().future_trait()
80+
&& let Some(send_trait) = cx.tcx.get_diagnostic_item(sym::Send)
81+
{
6882
let preds = cx.tcx.explicit_item_super_predicates(def_id);
6983
let is_future = preds.iter_instantiated_copied(cx.tcx, args).any(|(p, _)| {
70-
p.as_trait_clause().is_some_and(|trait_pred| {
71-
Some(trait_pred.skip_binder().trait_ref.def_id) == cx.tcx.lang_items().future_trait()
72-
})
84+
p.as_trait_clause()
85+
.is_some_and(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait)
7386
});
7487
if is_future {
75-
let send_trait = cx.tcx.get_diagnostic_item(sym::Send).unwrap();
7688
let span = decl.output.span();
7789
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
7890
let ocx = ObligationCtxt::new_with_diagnostics(&infcx);
7991
let cause = traits::ObligationCause::misc(span, fn_def_id);
8092
ocx.register_bound(cause, cx.param_env, ret_ty, send_trait);
8193
let send_errors = ocx.select_all_or_error();
82-
if !send_errors.is_empty() {
94+
95+
// Allow errors that try to prove `Send` for types that "mention" a generic parameter at the "top
96+
// level".
97+
// For example, allow errors that `T: Send` can't be proven, but reject `Rc<T>: Send` errors,
98+
// which is always unconditionally `!Send` for any possible type `T`.
99+
//
100+
// We also allow associated type projections if the self type is either itself a projection or a
101+
// type parameter.
102+
// This is to prevent emitting warnings for e.g. holding a `<Fut as Future>::Output` across await
103+
// points, where `Fut` is a type parameter.
104+
105+
let is_send = send_errors.iter().all(|err| {
106+
err.obligation
107+
.predicate
108+
.as_trait_clause()
109+
.map(Binder::skip_binder)
110+
.is_some_and(|pred| {
111+
pred.def_id() == send_trait
112+
&& pred.self_ty().has_param()
113+
&& TyParamAtTopLevelVisitor.visit_ty(pred.self_ty()) == ControlFlow::Break(true)
114+
})
115+
});
116+
117+
if !is_send {
83118
span_lint_and_then(
84119
cx,
85120
FUTURE_NOT_SEND,
@@ -107,3 +142,15 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
107142
}
108143
}
109144
}
145+
146+
struct TyParamAtTopLevelVisitor;
147+
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for TyParamAtTopLevelVisitor {
148+
type Result = ControlFlow<bool>;
149+
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
150+
match ty.kind() {
151+
ty::Param(_) => ControlFlow::Break(true),
152+
ty::Alias(ty::AliasTyKind::Projection, ty) => ty.visit_with(self),
153+
_ => ControlFlow::Break(false),
154+
}
155+
}
156+
}

‎tests/ui/crashes/ice-10645.rs

-7
This file was deleted.

‎tests/ui/crashes/ice-10645.stderr

-17
This file was deleted.

‎tests/ui/future_not_send.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![warn(clippy::future_not_send)]
22

33
use std::cell::Cell;
4+
use std::future::Future;
45
use std::rc::Rc;
56
use std::sync::Arc;
67

@@ -63,6 +64,22 @@ where
6364
t
6465
}
6566

67+
async fn maybe_send_generic_future<T>(t: T) -> T {
68+
async { true }.await;
69+
t
70+
}
71+
72+
async fn maybe_send_generic_future2<F: Fn() -> Fut, Fut: Future>(f: F) {
73+
async { true }.await;
74+
let res = f();
75+
async { true }.await;
76+
}
77+
78+
async fn generic_future_always_unsend<T>(_: Rc<T>) {
79+
//~^ ERROR: future cannot be sent between threads safely
80+
async { true }.await;
81+
}
82+
6683
async fn generic_future_send<T>(t: T)
6784
where
6885
T: Send,
@@ -71,7 +88,6 @@ where
7188
}
7289

7390
async fn unclear_future<T>(t: T) {}
74-
//~^ ERROR: future cannot be sent between threads safely
7591

7692
fn main() {
7793
let rc = Rc::new([1, 2, 3]);

‎tests/ui/future_not_send.stderr

+27-24
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
error: future cannot be sent between threads safely
2-
--> tests/ui/future_not_send.rs:7:1
2+
--> tests/ui/future_not_send.rs:8:1
33
|
44
LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `private_future` is not `Send`
66
|
77
note: future is not `Send` as this value is used across an await
8-
--> tests/ui/future_not_send.rs:9:20
8+
--> tests/ui/future_not_send.rs:10:20
99
|
1010
LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
1111
| -- has type `std::rc::Rc<[u8]>` which is not `Send`
@@ -14,7 +14,7 @@ LL | async { true }.await
1414
| ^^^^^ await occurs here, with `rc` maybe used later
1515
= note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send`
1616
note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync`
17-
--> tests/ui/future_not_send.rs:7:39
17+
--> tests/ui/future_not_send.rs:8:39
1818
|
1919
LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
2020
| ^^^^ has type `&std::cell::Cell<usize>` which is not `Send`, because `std::cell::Cell<usize>` is not `Sync`
@@ -23,13 +23,13 @@ LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
2323
= help: to override `-D warnings` add `#[allow(clippy::future_not_send)]`
2424

2525
error: future cannot be sent between threads safely
26-
--> tests/ui/future_not_send.rs:12:1
26+
--> tests/ui/future_not_send.rs:13:1
2727
|
2828
LL | pub async fn public_future(rc: Rc<[u8]>) {
2929
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `public_future` is not `Send`
3030
|
3131
note: future is not `Send` as this value is used across an await
32-
--> tests/ui/future_not_send.rs:14:20
32+
--> tests/ui/future_not_send.rs:15:20
3333
|
3434
LL | pub async fn public_future(rc: Rc<[u8]>) {
3535
| -- has type `std::rc::Rc<[u8]>` which is not `Send`
@@ -39,45 +39,45 @@ LL | async { true }.await;
3939
= note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send`
4040

4141
error: future cannot be sent between threads safely
42-
--> tests/ui/future_not_send.rs:21:1
42+
--> tests/ui/future_not_send.rs:22:1
4343
|
4444
LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
4545
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `private_future2` is not `Send`
4646
|
4747
note: captured value is not `Send`
48-
--> tests/ui/future_not_send.rs:21:26
48+
--> tests/ui/future_not_send.rs:22:26
4949
|
5050
LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
5151
| ^^ has type `std::rc::Rc<[u8]>` which is not `Send`
5252
= note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send`
5353
note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync`
54-
--> tests/ui/future_not_send.rs:21:40
54+
--> tests/ui/future_not_send.rs:22:40
5555
|
5656
LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell<usize>) -> bool {
5757
| ^^^^ has type `&std::cell::Cell<usize>` which is not `Send`, because `std::cell::Cell<usize>` is not `Sync`
5858
= note: `std::cell::Cell<usize>` doesn't implement `std::marker::Sync`
5959

6060
error: future cannot be sent between threads safely
61-
--> tests/ui/future_not_send.rs:26:1
61+
--> tests/ui/future_not_send.rs:27:1
6262
|
6363
LL | pub async fn public_future2(rc: Rc<[u8]>) {}
6464
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `public_future2` is not `Send`
6565
|
6666
note: captured value is not `Send`
67-
--> tests/ui/future_not_send.rs:26:29
67+
--> tests/ui/future_not_send.rs:27:29
6868
|
6969
LL | pub async fn public_future2(rc: Rc<[u8]>) {}
7070
| ^^ has type `std::rc::Rc<[u8]>` which is not `Send`
7171
= note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send`
7272

7373
error: future cannot be sent between threads safely
74-
--> tests/ui/future_not_send.rs:38:5
74+
--> tests/ui/future_not_send.rs:39:5
7575
|
7676
LL | async fn private_future(&self) -> usize {
7777
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `private_future` is not `Send`
7878
|
7979
note: future is not `Send` as this value is used across an await
80-
--> tests/ui/future_not_send.rs:40:24
80+
--> tests/ui/future_not_send.rs:41:24
8181
|
8282
LL | async fn private_future(&self) -> usize {
8383
| ----- has type `&Dummy` which is not `Send`
@@ -87,20 +87,20 @@ LL | async { true }.await;
8787
= note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync`
8888

8989
error: future cannot be sent between threads safely
90-
--> tests/ui/future_not_send.rs:44:5
90+
--> tests/ui/future_not_send.rs:45:5
9191
|
9292
LL | pub async fn public_future(&self) {
9393
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `public_future` is not `Send`
9494
|
9595
note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync`
96-
--> tests/ui/future_not_send.rs:44:32
96+
--> tests/ui/future_not_send.rs:45:32
9797
|
9898
LL | pub async fn public_future(&self) {
9999
| ^^^^^ has type `&Dummy` which is not `Send`, because `Dummy` is not `Sync`
100100
= note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync`
101101

102102
error: future cannot be sent between threads safely
103-
--> tests/ui/future_not_send.rs:55:1
103+
--> tests/ui/future_not_send.rs:56:1
104104
|
105105
LL | / async fn generic_future<T>(t: T) -> T
106106
LL | |
@@ -109,7 +109,7 @@ LL | | T: Send,
109109
| |____________^ future returned by `generic_future` is not `Send`
110110
|
111111
note: future is not `Send` as this value is used across an await
112-
--> tests/ui/future_not_send.rs:61:20
112+
--> tests/ui/future_not_send.rs:62:20
113113
|
114114
LL | let rt = &t;
115115
| -- has type `&T` which is not `Send`
@@ -118,17 +118,20 @@ LL | async { true }.await;
118118
= note: `T` doesn't implement `std::marker::Sync`
119119

120120
error: future cannot be sent between threads safely
121-
--> tests/ui/future_not_send.rs:73:1
121+
--> tests/ui/future_not_send.rs:78:1
122122
|
123-
LL | async fn unclear_future<T>(t: T) {}
124-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `unclear_future` is not `Send`
123+
LL | async fn generic_future_always_unsend<T>(_: Rc<T>) {
124+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `generic_future_always_unsend` is not `Send`
125125
|
126-
note: captured value is not `Send`
127-
--> tests/ui/future_not_send.rs:73:28
126+
note: future is not `Send` as this value is used across an await
127+
--> tests/ui/future_not_send.rs:80:20
128128
|
129-
LL | async fn unclear_future<T>(t: T) {}
130-
| ^ has type `T` which is not `Send`
131-
= note: `T` doesn't implement `std::marker::Send`
129+
LL | async fn generic_future_always_unsend<T>(_: Rc<T>) {
130+
| - has type `std::rc::Rc<T>` which is not `Send`
131+
LL |
132+
LL | async { true }.await;
133+
| ^^^^^ await occurs here, with `_` maybe used later
134+
= note: `std::rc::Rc<T>` doesn't implement `std::marker::Send`
132135

133136
error: aborting due to 8 previous errors
134137

0 commit comments

Comments
 (0)
Please sign in to comment.