1
Fork 0

Suggest .clone() or ref binding on E0382

This commit is contained in:
Esteban Küber 2022-11-02 21:22:24 -07:00
parent d121aa3b55
commit 9e72e35ceb
86 changed files with 1092 additions and 49 deletions

View file

@ -191,6 +191,146 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
is_loop_move = true;
}
struct ExpressionFinder<'hir> {
expr_span: Span,
expr: Option<&'hir hir::Expr<'hir>>,
pat: Option<&'hir hir::Pat<'hir>>,
}
impl<'hir> Visitor<'hir> for ExpressionFinder<'hir> {
fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
if e.span == self.expr_span {
self.expr = Some(e);
}
hir::intravisit::walk_expr(self, e);
}
fn visit_pat(&mut self, p: &'hir hir::Pat<'hir>) {
if p.span == self.expr_span {
self.pat = Some(p);
}
if let hir::PatKind::Binding(hir::BindingAnnotation::NONE, _, i, _) = p.kind
&& i.span == self.expr_span
{
self.pat = Some(p);
}
hir::intravisit::walk_pat(self, p);
}
}
let hir = self.infcx.tcx.hir();
if let Some(hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn(_, _, body_id),
..
})) = hir.find(hir.local_def_id_to_hir_id(self.mir_def_id()))
&& let Some(hir::Node::Expr(expr)) = hir.find(body_id.hir_id)
{
let place = &self.move_data.move_paths[mpi].place;
let span = place.as_local()
.map(|local| self.body.local_decls[local].source_info.span);
let mut finder = ExpressionFinder {
expr_span: move_span,
expr: None,
pat: None,
};
finder.visit_expr(expr);
if let Some(span) = span && let Some(expr) = finder.expr {
for (_, expr) in hir.parent_iter(expr.hir_id) {
if let hir::Node::Expr(expr) = expr {
if expr.span.contains(span) {
// If the let binding occurs within the same loop, then that
// loop isn't relevant, like in the following, the outermost `loop`
// doesn't play into `x` being moved.
// ```
// loop {
// let x = String::new();
// loop {
// foo(x);
// }
// }
// ```
break;
}
if let hir::ExprKind::Loop(.., loop_span) = expr.kind {
err.span_label(loop_span, "inside of this loop");
}
}
}
let typeck = self.infcx.tcx.typeck(self.mir_def_id());
let hir_id = hir.get_parent_node(expr.hir_id);
if let Some(parent) = hir.find(hir_id) {
if let hir::Node::Expr(parent_expr) = parent
&& let hir::ExprKind::MethodCall(_, _, args, _) = parent_expr.kind
&& let Some(def_id) = typeck.type_dependent_def_id(parent_expr.hir_id)
&& let Some(def_id) = def_id.as_local()
&& let Some(node) = hir.find(hir.local_def_id_to_hir_id(def_id))
&& let Some(fn_sig) = node.fn_sig()
&& let Some(ident) = node.ident()
&& let Some(pos) = args.iter()
.position(|arg| arg.hir_id == expr.hir_id)
&& let Some(arg) = fn_sig.decl.inputs.get(pos + 1)
{
let mut span: MultiSpan = arg.span.into();
span.push_span_label(
arg.span,
"this type parameter takes ownership of the value".to_string(),
);
span.push_span_label(
ident.span,
"in this method".to_string(),
);
err.span_note(
span,
format!(
"consider changing this parameter type in `{}` to borrow \
instead if ownering the value isn't necessary",
ident,
),
);
}
if let hir::Node::Expr(parent_expr) = parent
&& let hir::ExprKind::Call(call, args) = parent_expr.kind
&& let ty::FnDef(def_id, _) = typeck.node_type(call.hir_id).kind()
&& let Some(def_id) = def_id.as_local()
&& let Some(node) = hir.find(hir.local_def_id_to_hir_id(def_id))
&& let Some(fn_sig) = node.fn_sig()
&& let Some(ident) = node.ident()
&& let Some(pos) = args.iter()
.position(|arg| arg.hir_id == expr.hir_id)
&& let Some(arg) = fn_sig.decl.inputs.get(pos)
{
let mut span: MultiSpan = arg.span.into();
span.push_span_label(
arg.span,
"this type parameter takes ownership of the value".to_string(),
);
span.push_span_label(
ident.span,
"in this function".to_string(),
);
err.span_note(
span,
format!(
"consider changing this parameter type in `{}` to borrow \
instead if ownering the value isn't necessary",
ident,
),
);
}
let place = &self.move_data.move_paths[mpi].place;
let ty = place.ty(self.body, self.infcx.tcx).ty;
self.suggest_cloning(&mut err, ty, move_span);
}
}
if let Some(pat) = finder.pat {
in_pattern = true;
err.span_suggestion_verbose(
pat.span.shrink_to_lo(),
"borrow this binding in the pattern to avoid moving the value",
"ref ".to_string(),
Applicability::MachineApplicable,
);
}
}
self.explain_captures(
&mut err,
span,
@ -203,25 +343,6 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
is_loop_move,
maybe_reinitialized_locations.is_empty(),
);
if let (UseSpans::PatUse(span), []) =
(move_spans, &maybe_reinitialized_locations[..])
{
if maybe_reinitialized_locations.is_empty() {
err.span_suggestion_verbose(
span.shrink_to_lo(),
&format!(
"borrow this field in the pattern to avoid moving {}",
self.describe_place(moved_place.as_ref())
.map(|n| format!("`{}`", n))
.unwrap_or_else(|| "the value".to_string())
),
"ref ",
Applicability::MachineApplicable,
);
in_pattern = true;
}
}
}
use_spans.var_path_only_subdiag(&mut err, desired_action);
@ -590,6 +711,39 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
true
}
fn suggest_cloning(&self, err: &mut Diagnostic, ty: Ty<'tcx>, span: Span) {
let tcx = self.infcx.tcx;
// Try to find predicates on *generic params* that would allow copying `ty`
let infcx = tcx.infer_ctxt().build();
let mut fulfill_cx = <dyn rustc_infer::traits::TraitEngine<'_>>::new(infcx.tcx);
let clone_did = infcx.tcx.lang_items().clone_trait().unwrap();
let cause = ObligationCause::new(
span,
self.mir_hir_id(),
rustc_infer::traits::ObligationCauseCode::MiscObligation,
);
fulfill_cx.register_bound(
&infcx,
self.param_env,
// Erase any region vids from the type, which may not be resolved
infcx.tcx.erase_regions(ty),
clone_did,
cause,
);
// Select all, including ambiguous predicates
let errors = fulfill_cx.select_all_or_error(&infcx);
if errors.is_empty() {
err.span_suggestion_verbose(
span.shrink_to_hi(),
"consider cloning the value if the performance cost is acceptable",
".clone()".to_string(),
Applicability::MachineApplicable,
);
}
}
fn suggest_adding_copy_bounds(&self, err: &mut Diagnostic, ty: Ty<'tcx>, span: Span) {
let tcx = self.infcx.tcx;
let generics = tcx.generics_of(self.mir_def_id());