Suggest borrowing on fn argument that is `impl AsRef` · rust-lang/rust@2df4f7d
@@ -449,31 +449,81 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
449449} else {
450450(None, &[][..], 0)
451451};
452+let mut can_suggest_clone = true;
452453if let Some(def_id) = def_id
453454&& let node = self.infcx.tcx.hir_node_by_def_id(def_id)
454455&& let Some(fn_sig) = node.fn_sig()
455456&& let Some(ident) = node.ident()
456457&& let Some(pos) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
457458&& let Some(arg) = fn_sig.decl.inputs.get(pos + offset)
458459{
459-let mut span: MultiSpan = arg.span.into();
460- span.push_span_label(
461- arg.span,
462-"this parameter takes ownership of the value".to_string(),
463-);
464-let descr = match node.fn_kind() {
465-Some(hir::intravisit::FnKind::ItemFn(..)) | None => "function",
466-Some(hir::intravisit::FnKind::Method(..)) => "method",
467-Some(hir::intravisit::FnKind::Closure) => "closure",
468-};
469- span.push_span_label(ident.span, format!("in this {descr}"));
470- err.span_note(
471- span,
472-format!(
473-"consider changing this parameter type in {descr} `{ident}` to borrow \
474- instead if owning the value isn't necessary",
475-),
476-);
460+let mut is_mut = false;
461+if let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = arg.kind
462+&& let Res::Def(DefKind::TyParam, param_def_id) = path.res
463+&& self
464+.infcx
465+.tcx
466+.predicates_of(def_id)
467+.instantiate_identity(self.infcx.tcx)
468+.predicates
469+.into_iter()
470+.any(|pred| {
471+if let ty::ClauseKind::Trait(predicate) = pred.kind().skip_binder()
472+&& [
473+self.infcx.tcx.get_diagnostic_item(sym::AsRef),
474+self.infcx.tcx.get_diagnostic_item(sym::AsMut),
475+self.infcx.tcx.get_diagnostic_item(sym::Borrow),
476+self.infcx.tcx.get_diagnostic_item(sym::BorrowMut),
477+]
478+.contains(&Some(predicate.def_id()))
479+&& let ty::Param(param) = predicate.self_ty().kind()
480+&& let generics = self.infcx.tcx.generics_of(def_id)
481+&& let param = generics.type_param(*param, self.infcx.tcx)
482+&& param.def_id == param_def_id
483+{
484+if [
485+self.infcx.tcx.get_diagnostic_item(sym::AsMut),
486+self.infcx.tcx.get_diagnostic_item(sym::BorrowMut),
487+]
488+.contains(&Some(predicate.def_id()))
489+{
490+ is_mut = true;
491+}
492+true
493+} else {
494+false
495+}
496+})
497+{
498+// The type of the argument corresponding to the expression that got moved
499+// is a type parameter `T`, which is has a `T: AsRef` obligation.
500+ err.span_suggestion_verbose(
501+ expr.span.shrink_to_lo(),
502+"borrow the value to avoid moving it",
503+format!("&{}", if is_mut { "mut " } else { "" }),
504+Applicability::MachineApplicable,
505+);
506+ can_suggest_clone = is_mut;
507+} else {
508+let mut span: MultiSpan = arg.span.into();
509+ span.push_span_label(
510+ arg.span,
511+"this parameter takes ownership of the value".to_string(),
512+);
513+let descr = match node.fn_kind() {
514+Some(hir::intravisit::FnKind::ItemFn(..)) | None => "function",
515+Some(hir::intravisit::FnKind::Method(..)) => "method",
516+Some(hir::intravisit::FnKind::Closure) => "closure",
517+};
518+ span.push_span_label(ident.span, format!("in this {descr}"));
519+ err.span_note(
520+ span,
521+format!(
522+"consider changing this parameter type in {descr} `{ident}` to \
523+ borrow instead if owning the value isn't necessary",
524+),
525+);
526+}
477527}
478528let place = &self.move_data.move_paths[mpi].place;
479529let ty = place.ty(self.body, self.infcx.tcx).ty;
@@ -491,9 +541,10 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
491541ClosureKind::Coroutine(CoroutineKind::Desugared(_, CoroutineSource::Block)),
492542 ..
493543} = move_spans
544+&& can_suggest_clone
494545{
495546self.suggest_cloning(err, ty, expr, None, Some(move_spans));
496-} else if self.suggest_hoisting_call_outside_loop(err, expr) {
547+} else if self.suggest_hoisting_call_outside_loop(err, expr) && can_suggest_clone {
497548// The place where the type moves would be misleading to suggest clone.
498549// #121466
499550self.suggest_cloning(err, ty, expr, None, Some(move_spans));