Skip to content

Commit

Permalink
allow conditional send futures in future_not_send
Browse files Browse the repository at this point in the history
  • Loading branch information
y21 committed Oct 22, 2024
1 parent f2f0175 commit 58fe1b1
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6220,6 +6220,7 @@ Released 2018-09-13
[`too-many-lines-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-lines-threshold
[`trivial-copy-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trivial-copy-size-limit
[`type-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#type-complexity-threshold
[`unconditional-future-not-send`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unconditional-future-not-send
[`unnecessary-box-size`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unnecessary-box-size
[`unreadable-literal-lint-fractions`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unreadable-literal-lint-fractions
[`upper-case-acronyms-aggressive`]: https://doc.rust-lang.org/clippy/lint_configuration.html#upper-case-acronyms-aggressive
Expand Down
27 changes: 27 additions & 0 deletions book/src/lint_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,33 @@ The maximum complexity a type can have
* [`type_complexity`](https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity)


## `unconditional-future-not-send`
Whether `future_not_send` should require futures to implement `Send` unconditionally.
Enabling this makes the lint more strict and requires generic functions have `Send` bounds
on type parameters.

### Example
```
async fn foo<R>(r: R) {
std::future::ready(()).await;
r;
}
```
The returned future by this async function may or may not be `Send` - it depends on the particular type
that `R` is instantiated with at call site, so it is **not** unconditionally `Send`.
Adding an `R: Send` bound to the function definition satisfies it, but is more restrictive for callers.

For library crate authors that want to make sure that their async functions are compatible
with both single-threaded and multi-threaded executors, the default behavior of allowing `!Send` futures if type parameters
are `!Send` makes sense.

**Default Value:** `false`

---
**Affected lints:**
* [`future_not_send`](https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send)


## `unnecessary-box-size`
The byte size a `T` in `Box<T>` can have, below which it triggers the `clippy::unnecessary_box` lint

Expand Down
20 changes: 20 additions & 0 deletions clippy_config/src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,26 @@ define_Conf! {
/// The maximum complexity a type can have
#[lints(type_complexity)]
type_complexity_threshold: u64 = 250,
/// Whether `future_not_send` should require futures to implement `Send` unconditionally.
/// Enabling this makes the lint more strict and requires generic functions have `Send` bounds
/// on type parameters.
///
/// ### Example
/// ```
/// async fn foo<R>(r: R) {
/// std::future::ready(()).await;
/// r;
/// }
/// ```
/// The returned future by this async function may or may not be `Send` - it depends on the particular type
/// that `R` is instantiated with at call site, so it is **not** unconditionally `Send`.
/// Adding an `R: Send` bound to the function definition satisfies it, but is more restrictive for callers.
///
/// For library crate authors that want to make sure that their async functions are compatible
/// with both single-threaded and multi-threaded executors, the default behavior of allowing `!Send` futures
/// if type parameters are `!Send` makes sense.
#[lints(future_not_send)]
unconditional_future_not_send: bool = false,
/// The byte size a `T` in `Box<T>` can have, below which it triggers the `clippy::unnecessary_box` lint
#[lints(unnecessary_box_returns)]
unnecessary_box_size: u64 = 128,
Expand Down
44 changes: 36 additions & 8 deletions clippy_lints/src/future_not_send.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use clippy_config::Conf;
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, ClauseKind, PredicateKind};
use rustc_session::declare_lint_pass;
use rustc_middle::ty::{self, AliasTy, Binder, ClauseKind, PredicateKind, TypeVisitableExt};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, sym};
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
Expand Down Expand Up @@ -48,7 +49,19 @@ declare_clippy_lint! {
"public Futures must be Send"
}

declare_lint_pass!(FutureNotSend => [FUTURE_NOT_SEND]);
pub struct FutureNotSend {
unconditionally_not_send: bool,
}

impl FutureNotSend {
pub fn new(conf: &'static Conf) -> Self {
Self {
unconditionally_not_send: conf.unconditional_future_not_send,
}
}
}

impl_lint_pass!(FutureNotSend => [FUTURE_NOT_SEND]);

impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
fn check_fn(
Expand All @@ -64,12 +77,13 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
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() {
if let ty::Alias(ty::Opaque, AliasTy { def_id, args, .. }) = *ret_ty.kind()
&& let Some(future_trait) = cx.tcx.lang_items().future_trait()
{
let preds = cx.tcx.explicit_item_super_predicates(def_id);
let is_future = preds.iter_instantiated_copied(cx.tcx, args).any(|(p, _)| {
p.as_trait_clause().is_some_and(|trait_pred| {
Some(trait_pred.skip_binder().trait_ref.def_id) == cx.tcx.lang_items().future_trait()
})
p.as_trait_clause()
.is_some_and(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait)
});
if is_future {
let send_trait = cx.tcx.get_diagnostic_item(sym::Send).unwrap();
Expand All @@ -79,7 +93,21 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
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();
if !send_errors.is_empty() {

let emit_warning = if self.unconditionally_not_send {
!send_errors.is_empty()
} else {
// Only allow errors that try to prove `Send` for a type that mentions a generic parameter.
send_errors.iter().any(|err| {
!err.obligation
.predicate
.as_trait_clause()
.map(Binder::skip_binder)
.is_some_and(|pred| pred.self_ty().has_param() && pred.def_id() == future_trait)
})
};

if emit_warning {
span_lint_and_then(
cx,
FUTURE_NOT_SEND,
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
store.register_late_pass(move |_| Box::new(future_not_send::FutureNotSend::new(conf)));
store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(conf)));
store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
store.register_late_pass(|_| Box::new(if_not_else::IfNotElse));
Expand Down
3 changes: 3 additions & 0 deletions tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
too-many-lines-threshold
trivial-copy-size-limit
type-complexity-threshold
unconditional-future-not-send
unnecessary-box-size
unreadable-literal-lint-fractions
upper-case-acronyms-aggressive
Expand Down Expand Up @@ -154,6 +155,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
too-many-lines-threshold
trivial-copy-size-limit
type-complexity-threshold
unconditional-future-not-send
unnecessary-box-size
unreadable-literal-lint-fractions
upper-case-acronyms-aggressive
Expand Down Expand Up @@ -238,6 +240,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
too-many-lines-threshold
trivial-copy-size-limit
type-complexity-threshold
unconditional-future-not-send
unnecessary-box-size
unreadable-literal-lint-fractions
upper-case-acronyms-aggressive
Expand Down

0 comments on commit 58fe1b1

Please sign in to comment.