diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 082c312c7c75e..d2bb518fac492 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -1046,6 +1046,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { expected_kind, trait_prefix, ); + self.suggest_change_mut_ref_for_closure(&mut err, &obligation); self.note_obligation_cause(&mut err, &obligation); return Some(err.emit()); } @@ -3019,6 +3020,25 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }) } + fn suggest_change_mut_ref_for_closure( + &self, + err: &mut Diag<'_>, + obligation: &PredicateObligation<'tcx>, + ) { + if let ObligationCauseCode::FunctionArg { arg_hir_id, .. } = obligation.cause.code() + && let (_, Some(root_trait_pred)) = + obligation.cause.code().peel_derives_with_predicate() + && let Node::Expr(arg) = self.tcx.hir_node(*arg_hir_id) + && let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, _) = arg.kind + { + let mut obligation = obligation.clone(); + // Error reporting may narrow the cause span to the borrow's operand. + // Use the whole argument so `suggest_change_mut` can replace the shared borrow. + obligation.cause.span = arg.span; + self.suggest_change_mut(&obligation, err, root_trait_pred); + } + } + pub fn note_obligation_cause( &self, err: &mut Diag<'_>, @@ -3557,7 +3577,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { _ => {} } } - self.dcx().create_err(err) } diff --git a/tests/ui/closures/fnmut-shared-reference-requires-fn.rs b/tests/ui/closures/fnmut-shared-reference-requires-fn.rs new file mode 100644 index 0000000000000..1207bc4cf1d52 --- /dev/null +++ b/tests/ui/closures/fnmut-shared-reference-requires-fn.rs @@ -0,0 +1,11 @@ +//! Do not suggest a mutable reference when the root obligation genuinely requires `Fn`. + +fn requires_fn(_: F) {} + +fn main() { + let mut value = 0; + let mut func = || value += 1; + //~^ ERROR expected a closure that implements the `Fn` trait + + requires_fn(&func); +} diff --git a/tests/ui/closures/fnmut-shared-reference-requires-fn.stderr b/tests/ui/closures/fnmut-shared-reference-requires-fn.stderr new file mode 100644 index 0000000000000..6a62f4d1a74c1 --- /dev/null +++ b/tests/ui/closures/fnmut-shared-reference-requires-fn.stderr @@ -0,0 +1,23 @@ +error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnMut` + --> $DIR/fnmut-shared-reference-requires-fn.rs:7:20 + | +LL | let mut func = || value += 1; + | ^^ ----- closure is `FnMut` because it mutates the variable `value` here + | | + | this closure implements `FnMut`, not `Fn` +... +LL | requires_fn(&func); + | ----------- ---- the requirement to implement `Fn` derives from here + | | + | required by a bound introduced by this call + | + = note: required for `&{closure@$DIR/fnmut-shared-reference-requires-fn.rs:7:20: 7:22}` to implement `Fn()` +note: required by a bound in `requires_fn` + --> $DIR/fnmut-shared-reference-requires-fn.rs:3:19 + | +LL | fn requires_fn(_: F) {} + | ^^^^ required by this bound in `requires_fn` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0525`. diff --git a/tests/ui/closures/fnmut-shared-reference-suggestion-issue-118843.fixed b/tests/ui/closures/fnmut-shared-reference-suggestion-issue-118843.fixed new file mode 100644 index 0000000000000..1918d26ac2d90 --- /dev/null +++ b/tests/ui/closures/fnmut-shared-reference-suggestion-issue-118843.fixed @@ -0,0 +1,13 @@ +//@ run-rustfix + +//! Regression test for https://github.com/rust-lang/rust/issues/118843. +//! A shared reference to an `FnMut` closure should suggest a mutable reference. + +fn main() { + let mut value = 0; + let mut func = |increment: usize| value += increment; + //~^ ERROR expected a closure that implements the `Fn` trait + + (0..100).for_each(&mut func); + //~^ HELP consider changing this borrow's mutability +} diff --git a/tests/ui/closures/fnmut-shared-reference-suggestion-issue-118843.rs b/tests/ui/closures/fnmut-shared-reference-suggestion-issue-118843.rs new file mode 100644 index 0000000000000..d7bd2fb60398a --- /dev/null +++ b/tests/ui/closures/fnmut-shared-reference-suggestion-issue-118843.rs @@ -0,0 +1,13 @@ +//@ run-rustfix + +//! Regression test for https://github.com/rust-lang/rust/issues/118843. +//! A shared reference to an `FnMut` closure should suggest a mutable reference. + +fn main() { + let mut value = 0; + let mut func = |increment: usize| value += increment; + //~^ ERROR expected a closure that implements the `Fn` trait + + (0..100).for_each(&func); + //~^ HELP consider changing this borrow's mutability +} diff --git a/tests/ui/closures/fnmut-shared-reference-suggestion-issue-118843.stderr b/tests/ui/closures/fnmut-shared-reference-suggestion-issue-118843.stderr new file mode 100644 index 0000000000000..817c073e92aa8 --- /dev/null +++ b/tests/ui/closures/fnmut-shared-reference-suggestion-issue-118843.stderr @@ -0,0 +1,24 @@ +error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnMut` + --> $DIR/fnmut-shared-reference-suggestion-issue-118843.rs:8:20 + | +LL | let mut func = |increment: usize| value += increment; + | ^^^^^^^^^^^^^^^^^^ ----- closure is `FnMut` because it mutates the variable `value` here + | | + | this closure implements `FnMut`, not `Fn` +... +LL | (0..100).for_each(&func); + | -------- ---- the requirement to implement `Fn` derives from here + | | + | required by a bound introduced by this call + | + = note: required for `&{closure@$DIR/fnmut-shared-reference-suggestion-issue-118843.rs:8:20: 8:38}` to implement `FnMut(usize)` +note: required by a bound in `for_each` + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL +help: consider changing this borrow's mutability + | +LL | (0..100).for_each(&mut func); + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0525`.