diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 5baa108ed3c..a230ba0e439 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -1,3 +1,5 @@ +// ignore-tidy-filelength + use either::Either; use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxIndexSet; @@ -24,6 +26,7 @@ use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::{BytePos, Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::error_reporting::FindExprBySpan; use rustc_trait_selection::traits::ObligationCtxt; use std::iter; @@ -1295,14 +1298,96 @@ fn suggest_slice_method_if_applicable( place: Place<'tcx>, borrowed_place: Place<'tcx>, ) { - if let ([ProjectionElem::Index(_)], [ProjectionElem::Index(_)]) = - (&place.projection[..], &borrowed_place.projection[..]) + let tcx = self.infcx.tcx; + let hir = tcx.hir(); + + if let ([ProjectionElem::Index(index1)], [ProjectionElem::Index(index2)]) + | ( + [ProjectionElem::Deref, ProjectionElem::Index(index1)], + [ProjectionElem::Deref, ProjectionElem::Index(index2)], + ) = (&place.projection[..], &borrowed_place.projection[..]) { - err.help( - "consider using `.split_at_mut(position)` or similar method to obtain \ - two mutable non-overlapping sub-slices", - ) - .help("consider using `.swap(index_1, index_2)` to swap elements at the specified indices"); + let mut note_default_suggestion = || { + err.help( + "consider using `.split_at_mut(position)` or similar method to obtain \ + two mutable non-overlapping sub-slices", + ) + .help("consider using `.swap(index_1, index_2)` to swap elements at the specified indices"); + }; + + let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { + note_default_suggestion(); + return; + }; + + let mut expr_finder = + FindExprBySpan::new(self.body.local_decls[*index1].source_info.span); + expr_finder.visit_expr(hir.body(body_id).value); + let Some(index1) = expr_finder.result else { + note_default_suggestion(); + return; + }; + + expr_finder = FindExprBySpan::new(self.body.local_decls[*index2].source_info.span); + expr_finder.visit_expr(hir.body(body_id).value); + let Some(index2) = expr_finder.result else { + note_default_suggestion(); + return; + }; + + let sm = tcx.sess.source_map(); + + let Ok(index1_str) = sm.span_to_snippet(index1.span) else { + note_default_suggestion(); + return; + }; + + let Ok(index2_str) = sm.span_to_snippet(index2.span) else { + note_default_suggestion(); + return; + }; + + let Some(object) = hir.parent_id_iter(index1.hir_id).find_map(|id| { + if let hir::Node::Expr(expr) = tcx.hir_node(id) + && let hir::ExprKind::Index(obj, ..) = expr.kind + { + Some(obj) + } else { + None + } + }) else { + note_default_suggestion(); + return; + }; + + let Ok(obj_str) = sm.span_to_snippet(object.span) else { + note_default_suggestion(); + return; + }; + + let Some(swap_call) = hir.parent_id_iter(object.hir_id).find_map(|id| { + if let hir::Node::Expr(call) = tcx.hir_node(id) + && let hir::ExprKind::Call(callee, ..) = call.kind + && let hir::ExprKind::Path(qpath) = callee.kind + && let hir::QPath::Resolved(None, res) = qpath + && let hir::def::Res::Def(_, did) = res.res + && tcx.is_diagnostic_item(sym::mem_swap, did) + { + Some(call) + } else { + None + } + }) else { + note_default_suggestion(); + return; + }; + + err.span_suggestion( + swap_call.span, + "use `.swap()` to swap elements at the specified indices instead", + format!("{obj_str}.swap({index1_str}, {index2_str})"), + Applicability::MachineApplicable, + ); } } diff --git a/tests/ui/suggestions/suggest-slice-swap.fixed b/tests/ui/suggestions/suggest-slice-swap.fixed new file mode 100644 index 00000000000..05b7ec26379 --- /dev/null +++ b/tests/ui/suggestions/suggest-slice-swap.fixed @@ -0,0 +1,9 @@ +// run-rustfix +#![allow(dead_code)] + +fn swap(arr: &mut [u32; 2]) { + arr.swap(1, 0); + //~^ ERROR cannot borrow `arr[_]` as mutable more than once at a time +} + +fn main() {} diff --git a/tests/ui/suggestions/suggest-slice-swap.rs b/tests/ui/suggestions/suggest-slice-swap.rs new file mode 100644 index 00000000000..9f3659aac16 --- /dev/null +++ b/tests/ui/suggestions/suggest-slice-swap.rs @@ -0,0 +1,9 @@ +// run-rustfix +#![allow(dead_code)] + +fn swap(arr: &mut [u32; 2]) { + std::mem::swap(&mut arr[0], &mut arr[1]); + //~^ ERROR cannot borrow `arr[_]` as mutable more than once at a time +} + +fn main() {} diff --git a/tests/ui/suggestions/suggest-slice-swap.stderr b/tests/ui/suggestions/suggest-slice-swap.stderr new file mode 100644 index 00000000000..2840fc0a761 --- /dev/null +++ b/tests/ui/suggestions/suggest-slice-swap.stderr @@ -0,0 +1,17 @@ +error[E0499]: cannot borrow `arr[_]` as mutable more than once at a time + --> $DIR/suggest-slice-swap.rs:5:33 + | +LL | std::mem::swap(&mut arr[0], &mut arr[1]); + | -------------- ----------- ^^^^^^^^^^^ second mutable borrow occurs here + | | | + | | first mutable borrow occurs here + | first borrow later used by call + | +help: use `.swap()` to swap elements at the specified indices instead + | +LL | arr.swap(1, 0); + | ~~~~~~~~~~~~~~ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`.