Suggest borrowing arguments in generic positions when trait bounds are satisfied

This subsumes the suggestions to borrow arguments with `AsRef`/`Borrow` bounds and those to borrow
arguments with `Fn` and `FnMut` bounds. It works for other traits implemented on references as well,
such as `std::io::Read`, `std::io::Write`, and `core::fmt::Write`.

Incidentally, by making the logic for suggesting borrowing closures general, this removes some
spurious suggestions to mutably borrow `FnMut` closures in assignments, as well as an unhelpful
suggestion to add a `Clone` constraint to an `impl Fn` argument.
This commit is contained in:
dianne 2024-10-23 01:32:12 -07:00
parent ea37000b56
commit 2ab8480605
13 changed files with 327 additions and 300 deletions

View File

@ -27,7 +27,7 @@ use rustc_middle::mir::{
}; };
use rustc_middle::ty::print::PrintTraitRefExt as _; use rustc_middle::ty::print::PrintTraitRefExt as _;
use rustc_middle::ty::{ use rustc_middle::ty::{
self, PredicateKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor, Upcast, self, ClauseKind, PredicateKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor, Upcast,
suggest_constraining_type_params, suggest_constraining_type_params,
}; };
use rustc_middle::util::CallKind; use rustc_middle::util::CallKind;
@ -39,6 +39,7 @@ use rustc_span::{BytePos, Span, Symbol};
use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
use rustc_trait_selection::error_reporting::traits::FindExprBySpan; use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt}; use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
use tracing::{debug, instrument}; use tracing::{debug, instrument};
@ -201,16 +202,15 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
let mut has_suggest_reborrow = false; let mut has_suggest_reborrow = false;
if !seen_spans.contains(&move_span) { if !seen_spans.contains(&move_span) {
if !closure { self.suggest_ref_or_clone(
self.suggest_ref_or_clone( mpi,
mpi, &mut err,
&mut err, &mut in_pattern,
&mut in_pattern, move_spans,
move_spans, moved_place.as_ref(),
moved_place.as_ref(), &mut has_suggest_reborrow,
&mut has_suggest_reborrow, closure,
); );
}
let msg_opt = CapturedMessageOpt { let msg_opt = CapturedMessageOpt {
is_partial_move, is_partial_move,
@ -266,27 +266,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
} }
} }
let opt_name = self.describe_place_with_options(place.as_ref(), DescribePlaceOpt { if self.param_env.caller_bounds().iter().any(|c| {
including_downcast: true, c.as_trait_clause().is_some_and(|pred| {
including_tuple_field: true, pred.skip_binder().self_ty() == ty && self.infcx.tcx.is_fn_trait(pred.def_id())
}); })
let note_msg = match opt_name { }) {
Some(name) => format!("`{name}`"),
None => "value".to_owned(),
};
if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, &note_msg)
|| if let UseSpans::FnSelfUse { kind, .. } = use_spans
&& let CallKind::FnCall { fn_trait_id, self_ty } = kind
&& let ty::Param(_) = self_ty.kind()
&& ty == self_ty
&& self.infcx.tcx.is_lang_item(fn_trait_id, LangItem::FnOnce)
{
// this is a type parameter `T: FnOnce()`, don't suggest `T: FnOnce() + Clone`.
true
} else {
false
}
{
// Suppress the next suggestion since we don't want to put more bounds onto // Suppress the next suggestion since we don't want to put more bounds onto
// something that already has `Fn`-like bounds (or is a closure), so we can't // something that already has `Fn`-like bounds (or is a closure), so we can't
// restrict anyways. // restrict anyways.
@ -295,6 +279,14 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
self.suggest_adding_bounds(&mut err, ty, copy_did, span); self.suggest_adding_bounds(&mut err, ty, copy_did, span);
} }
let opt_name = self.describe_place_with_options(place.as_ref(), DescribePlaceOpt {
including_downcast: true,
including_tuple_field: true,
});
let note_msg = match opt_name {
Some(name) => format!("`{name}`"),
None => "value".to_owned(),
};
if needs_note { if needs_note {
if let Some(local) = place.as_local() { if let Some(local) = place.as_local() {
let span = self.body.local_decls[local].source_info.span; let span = self.body.local_decls[local].source_info.span;
@ -341,6 +333,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
move_spans: UseSpans<'tcx>, move_spans: UseSpans<'tcx>,
moved_place: PlaceRef<'tcx>, moved_place: PlaceRef<'tcx>,
has_suggest_reborrow: &mut bool, has_suggest_reborrow: &mut bool,
moved_or_invoked_closure: bool,
) { ) {
let move_span = match move_spans { let move_span = match move_spans {
UseSpans::ClosureUse { capture_kind_span, .. } => capture_kind_span, UseSpans::ClosureUse { capture_kind_span, .. } => capture_kind_span,
@ -428,17 +421,18 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
} }
let typeck = self.infcx.tcx.typeck(self.mir_def_id()); let typeck = self.infcx.tcx.typeck(self.mir_def_id());
let parent = self.infcx.tcx.parent_hir_node(expr.hir_id); let parent = self.infcx.tcx.parent_hir_node(expr.hir_id);
let (def_id, args, offset) = if let hir::Node::Expr(parent_expr) = parent let (def_id, call_id, args, offset) = if let hir::Node::Expr(parent_expr) = parent
&& let hir::ExprKind::MethodCall(_, _, args, _) = parent_expr.kind && let hir::ExprKind::MethodCall(_, _, args, _) = parent_expr.kind
{ {
(typeck.type_dependent_def_id(parent_expr.hir_id), args, 1) let def_id = typeck.type_dependent_def_id(parent_expr.hir_id);
(def_id, Some(parent_expr.hir_id), args, 1)
} else if let hir::Node::Expr(parent_expr) = parent } else if let hir::Node::Expr(parent_expr) = parent
&& let hir::ExprKind::Call(call, args) = parent_expr.kind && let hir::ExprKind::Call(call, args) = parent_expr.kind
&& let ty::FnDef(def_id, _) = typeck.node_type(call.hir_id).kind() && let ty::FnDef(def_id, _) = typeck.node_type(call.hir_id).kind()
{ {
(Some(*def_id), args, 0) (Some(*def_id), Some(call.hir_id), args, 0)
} else { } else {
(None, &[][..], 0) (None, None, &[][..], 0)
}; };
let ty = place.ty(self.body, self.infcx.tcx).ty; let ty = place.ty(self.body, self.infcx.tcx).ty;
@ -449,14 +443,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
// The move occurred as one of the arguments to a function call. Is that // The move occurred as one of the arguments to a function call. Is that
// argument generic? `def_id` can't be a closure here, so using `fn_sig` is fine // argument generic? `def_id` can't be a closure here, so using `fn_sig` is fine
let arg_param = if self.infcx.tcx.def_kind(def_id).is_fn_like() let arg_param = if self.infcx.tcx.def_kind(def_id).is_fn_like()
&& let Some(arg_ty) = self && let sig =
.infcx self.infcx.tcx.fn_sig(def_id).instantiate_identity().skip_binder()
.tcx && let Some(arg_ty) = sig.inputs().get(pos + offset)
.fn_sig(def_id)
.instantiate_identity()
.skip_binder()
.inputs()
.get(pos + offset)
&& let ty::Param(arg_param) = arg_ty.kind() && let ty::Param(arg_param) = arg_ty.kind()
{ {
Some(arg_param) Some(arg_param)
@ -478,54 +467,22 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
return; return;
} }
let mut mutbl = ty::Mutability::Not; // If the moved place is used generically by the callee and a reference to it
if let Some(param) = arg_param // would still satisfy any bounds on its type, suggest borrowing.
&& self if let Some(&param) = arg_param
.infcx && let Some(generic_args) = call_id.and_then(|id| typeck.node_args_opt(id))
.tcx && let Some(ref_mutability) = self.suggest_borrow_generic_arg(
.predicates_of(def_id) err,
.instantiate_identity(self.infcx.tcx) def_id,
.predicates generic_args,
.into_iter() param,
.any(|pred| { moved_place,
if let ty::ClauseKind::Trait(predicate) = pred.kind().skip_binder() pos + offset,
&& [ ty,
self.infcx.tcx.get_diagnostic_item(sym::AsRef), expr.span,
self.infcx.tcx.get_diagnostic_item(sym::AsMut), )
self.infcx.tcx.get_diagnostic_item(sym::Borrow),
self.infcx.tcx.get_diagnostic_item(sym::BorrowMut),
]
.contains(&Some(predicate.def_id()))
&& *predicate.self_ty().kind() == ty::Param(*param)
{
if [
self.infcx.tcx.get_diagnostic_item(sym::AsMut),
self.infcx.tcx.get_diagnostic_item(sym::BorrowMut),
]
.contains(&Some(predicate.def_id()))
{
mutbl = ty::Mutability::Mut;
}
true
} else {
false
}
})
{ {
// The type of the argument corresponding to the expression that got moved can_suggest_clone = ref_mutability.is_mut();
// is a type parameter `T`, which is has a `T: AsRef` obligation.
let place_desc = if let Some(desc) = self.describe_place(moved_place) {
format!("`{desc}`")
} else {
"here".to_owned()
};
err.span_suggestion_verbose(
expr.span.shrink_to_lo(),
format!("consider {}borrowing {place_desc}", mutbl.mutably_str()),
mutbl.ref_prefix_str(),
Applicability::MaybeIncorrect,
);
can_suggest_clone = mutbl.is_mut();
} else if let Some(local_def_id) = def_id.as_local() } else if let Some(local_def_id) = def_id.as_local()
&& let node = self.infcx.tcx.hir_node_by_def_id(local_def_id) && let node = self.infcx.tcx.hir_node_by_def_id(local_def_id)
&& let Some(fn_decl) = node.fn_decl() && let Some(fn_decl) = node.fn_decl()
@ -563,6 +520,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
} else if let UseSpans::FnSelfUse { kind: CallKind::Normal { .. }, .. } = move_spans } else if let UseSpans::FnSelfUse { kind: CallKind::Normal { .. }, .. } = move_spans
{ {
// We already suggest cloning for these cases in `explain_captures`. // We already suggest cloning for these cases in `explain_captures`.
} else if moved_or_invoked_closure {
// Do not suggest `closure.clone()()`.
} else if let UseSpans::ClosureUse { } else if let UseSpans::ClosureUse {
closure_kind: closure_kind:
ClosureKind::Coroutine(CoroutineKind::Desugared(_, CoroutineSource::Block)), ClosureKind::Coroutine(CoroutineKind::Desugared(_, CoroutineSource::Block)),
@ -671,6 +630,113 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
); );
} }
/// If a place is used after being moved as an argument to a function, the function is generic
/// in that argument, and a reference to the argument's type would still satisfy the function's
/// bounds, suggest borrowing. This covers, e.g., borrowing an `impl Fn()` argument being passed
/// in an `impl FnOnce()` position.
/// Returns `Some(mutability)` when suggesting to borrow with mutability `mutability`, or `None`
/// if no suggestion is made.
fn suggest_borrow_generic_arg(
&self,
err: &mut Diag<'_>,
callee_did: DefId,
generic_args: ty::GenericArgsRef<'tcx>,
param: ty::ParamTy,
moved_place: PlaceRef<'tcx>,
moved_arg_pos: usize,
moved_arg_ty: Ty<'tcx>,
place_span: Span,
) -> Option<ty::Mutability> {
let tcx = self.infcx.tcx;
let sig = tcx.fn_sig(callee_did).instantiate_identity().skip_binder();
let clauses = tcx.predicates_of(callee_did).instantiate_identity(self.infcx.tcx).predicates;
// First, is there at least one method on one of `param`'s trait bounds?
// This keeps us from suggesting borrowing the argument to `mem::drop`, e.g.
if !clauses.iter().any(|clause| {
clause.as_trait_clause().is_some_and(|tc| {
tc.self_ty().skip_binder().is_param(param.index)
&& tc.polarity() == ty::PredicatePolarity::Positive
&& tcx
.supertrait_def_ids(tc.def_id())
.flat_map(|trait_did| tcx.associated_items(trait_did).in_definition_order())
.any(|item| item.fn_has_self_parameter)
})
}) {
return None;
}
// Try borrowing a shared reference first, then mutably.
if let Some(mutbl) = [ty::Mutability::Not, ty::Mutability::Mut].into_iter().find(|&mutbl| {
let re = self.infcx.tcx.lifetimes.re_erased;
let ref_ty = Ty::new_ref(self.infcx.tcx, re, moved_arg_ty, mutbl);
// Ensure that substituting `ref_ty` in the callee's signature doesn't break
// other inputs or the return type.
let new_args = tcx.mk_args_from_iter(generic_args.iter().enumerate().map(
|(i, arg)| {
if i == param.index as usize { ref_ty.into() } else { arg }
},
));
let can_subst = |ty: Ty<'tcx>| {
// Normalize before comparing to see through type aliases and projections.
let old_ty = ty::EarlyBinder::bind(ty).instantiate(tcx, generic_args);
let new_ty = ty::EarlyBinder::bind(ty).instantiate(tcx, new_args);
if let Ok(old_ty) = tcx.try_normalize_erasing_regions(self.param_env, old_ty)
&& let Ok(new_ty) = tcx.try_normalize_erasing_regions(self.param_env, new_ty)
{
old_ty == new_ty
} else {
false
}
};
if !can_subst(sig.output())
|| sig
.inputs()
.iter()
.enumerate()
.any(|(i, &input_ty)| i != moved_arg_pos && !can_subst(input_ty))
{
return false;
}
// Test the callee's predicates, substituting a reference in for the self ty
// in bounds on `param`.
clauses.iter().all(|&clause| {
let clause_for_ref = clause.kind().map_bound(|kind| match kind {
ClauseKind::Trait(c) if c.self_ty().is_param(param.index) => {
ClauseKind::Trait(c.with_self_ty(tcx, ref_ty))
}
ClauseKind::Projection(c) if c.self_ty().is_param(param.index) => {
ClauseKind::Projection(c.with_self_ty(tcx, ref_ty))
}
_ => kind,
});
self.infcx.predicate_must_hold_modulo_regions(&Obligation::new(
tcx,
ObligationCause::dummy(),
self.param_env,
ty::EarlyBinder::bind(clause_for_ref).instantiate(tcx, generic_args),
))
})
}) {
let place_desc = if let Some(desc) = self.describe_place(moved_place) {
format!("`{desc}`")
} else {
"here".to_owned()
};
err.span_suggestion_verbose(
place_span.shrink_to_lo(),
format!("consider {}borrowing {place_desc}", mutbl.mutably_str()),
mutbl.ref_prefix_str(),
Applicability::MaybeIncorrect,
);
Some(mutbl)
} else {
None
}
}
fn report_use_of_uninitialized( fn report_use_of_uninitialized(
&self, &self,
mpi: MovePathIndex, mpi: MovePathIndex,
@ -851,74 +917,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
); );
} }
fn suggest_borrow_fn_like(
&self,
err: &mut Diag<'_>,
ty: Ty<'tcx>,
move_sites: &[MoveSite],
value_name: &str,
) -> bool {
let tcx = self.infcx.tcx;
// Find out if the predicates show that the type is a Fn or FnMut
let find_fn_kind_from_did = |(pred, _): (ty::Clause<'tcx>, _)| {
if let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder()
&& pred.self_ty() == ty
{
if tcx.is_lang_item(pred.def_id(), LangItem::Fn) {
return Some(hir::Mutability::Not);
} else if tcx.is_lang_item(pred.def_id(), LangItem::FnMut) {
return Some(hir::Mutability::Mut);
}
}
None
};
// If the type is opaque/param/closure, and it is Fn or FnMut, let's suggest (mutably)
// borrowing the type, since `&mut F: FnMut` iff `F: FnMut` and similarly for `Fn`.
// These types seem reasonably opaque enough that they could be instantiated with their
// borrowed variants in a function body when we see a move error.
let borrow_level = match *ty.kind() {
ty::Param(_) => tcx
.explicit_predicates_of(self.mir_def_id().to_def_id())
.predicates
.iter()
.copied()
.find_map(find_fn_kind_from_did),
ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => tcx
.explicit_item_super_predicates(def_id)
.iter_instantiated_copied(tcx, args)
.find_map(|(clause, span)| find_fn_kind_from_did((clause, span))),
ty::Closure(_, args) => match args.as_closure().kind() {
ty::ClosureKind::Fn => Some(hir::Mutability::Not),
ty::ClosureKind::FnMut => Some(hir::Mutability::Mut),
_ => None,
},
_ => None,
};
let Some(borrow_level) = borrow_level else {
return false;
};
let sugg = move_sites
.iter()
.map(|move_site| {
let move_out = self.move_data.moves[(*move_site).moi];
let moved_place = &self.move_data.move_paths[move_out.path].place;
let move_spans = self.move_spans(moved_place.as_ref(), move_out.source);
let move_span = move_spans.args_or_use();
let suggestion = borrow_level.ref_prefix_str().to_owned();
(move_span.shrink_to_lo(), suggestion)
})
.collect();
err.multipart_suggestion_verbose(
format!("consider {}borrowing {value_name}", borrow_level.mutably_str()),
sugg,
Applicability::MaybeIncorrect,
);
true
}
/// In a move error that occurs on a call within a loop, we try to identify cases where cloning /// In a move error that occurs on a call within a loop, we try to identify cases where cloning
/// the value would lead to a logic error. We infer these cases by seeing if the moved value is /// the value would lead to a logic error. We infer these cases by seeing if the moved value is
/// part of the logic to break the loop, either through an explicit `break` or if the expression /// part of the logic to break the loop, either through an explicit `break` or if the expression

View File

@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
| |
LL | if let MultiVariant::Point(ref mut x, _) = point { LL | if let MultiVariant::Point(ref mut x, _) = point {
| ^^^^^ | ^^^^^
help: consider mutably borrowing `c`
|
LL | let a = &mut c;
| ++++
error: aborting due to 1 previous error error: aborting due to 1 previous error

View File

@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
| |
LL | let SingleVariant::Point(ref mut x, _) = point; LL | let SingleVariant::Point(ref mut x, _) = point;
| ^^^^^ | ^^^^^
help: consider mutably borrowing `c`
|
LL | let b = &mut c;
| ++++
error: aborting due to 1 previous error error: aborting due to 1 previous error

View File

@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
| |
LL | x.y.a += 1; LL | x.y.a += 1;
| ^^^^^ | ^^^^^
help: consider mutably borrowing `hello`
|
LL | let b = &mut hello;
| ++++
error: aborting due to 1 previous error error: aborting due to 1 previous error

View File

@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
| |
LL | x.0 += 1; LL | x.0 += 1;
| ^^^ | ^^^
help: consider mutably borrowing `hello`
|
LL | let b = &mut hello;
| ++++
error: aborting due to 1 previous error error: aborting due to 1 previous error

View File

@ -1,23 +1,20 @@
//! auxiliary definitons for suggest-borrow-for-generic-arg.rs, to ensure the suggestion works on //! auxiliary definitons for suggest-borrow-for-generic-arg.rs, to ensure the suggestion works on
//! functions defined in other crates. //! functions defined in other crates.
use std::borrow::{Borrow, BorrowMut}; use std::io::{self, Read, Write};
use std::convert::{AsMut, AsRef}; use std::iter::Sum;
pub struct Bar;
impl AsRef<Bar> for Bar { pub fn write_stuff<W: Write>(mut writer: W) -> io::Result<()> {
fn as_ref(&self) -> &Bar { writeln!(writer, "stuff")
self
}
} }
impl AsMut<Bar> for Bar { pub fn read_and_discard<R: Read>(mut reader: R) -> io::Result<()> {
fn as_mut(&mut self) -> &mut Bar { let mut buf = Vec::new();
self reader.read_to_end(&mut buf).map(|_| ())
}
} }
pub fn foo<T: AsRef<Bar>>(_: T) {} pub fn sum_three<I: IntoIterator>(iter: I) -> <I as IntoIterator>::Item
pub fn qux<T: AsMut<Bar>>(_: T) {} where <I as IntoIterator>::Item: Sum
pub fn bat<T: Borrow<T>>(_: T) {} {
pub fn baz<T: BorrowMut<T>>(_: T) {} iter.into_iter().take(3).sum()
}

View File

@ -1,4 +1,4 @@
fn takes_fn(f: impl Fn()) { //~ HELP if `impl Fn()` implemented `Clone` fn takes_fn(f: impl Fn()) {
loop { loop {
takes_fnonce(f); takes_fnonce(f);
//~^ ERROR use of moved value //~^ ERROR use of moved value

View File

@ -8,21 +8,6 @@ LL | loop {
LL | takes_fnonce(f); LL | takes_fnonce(f);
| ^ value moved here, in previous iteration of loop | ^ value moved here, in previous iteration of loop
| |
note: consider changing this parameter type in function `takes_fnonce` to borrow instead if owning the value isn't necessary
--> $DIR/borrow-closures-instead-of-move.rs:34:20
|
LL | fn takes_fnonce(_: impl FnOnce()) {}
| ------------ ^^^^^^^^^^^^^ this parameter takes ownership of the value
| |
| in this function
help: if `impl Fn()` implemented `Clone`, you could clone the value
--> $DIR/borrow-closures-instead-of-move.rs:1:16
|
LL | fn takes_fn(f: impl Fn()) {
| ^^^^^^^^^ consider constraining this type parameter with `Clone`
LL | loop {
LL | takes_fnonce(f);
| - you could clone this value
help: consider borrowing `f` help: consider borrowing `f`
| |
LL | takes_fnonce(&f); LL | takes_fnonce(&f);
@ -40,13 +25,6 @@ LL | takes_fnonce(m);
LL | takes_fnonce(m); LL | takes_fnonce(m);
| ^ value used here after move | ^ value used here after move
| |
note: consider changing this parameter type in function `takes_fnonce` to borrow instead if owning the value isn't necessary
--> $DIR/borrow-closures-instead-of-move.rs:34:20
|
LL | fn takes_fnonce(_: impl FnOnce()) {}
| ------------ ^^^^^^^^^^^^^ this parameter takes ownership of the value
| |
| in this function
help: if `impl FnMut()` implemented `Clone`, you could clone the value help: if `impl FnMut()` implemented `Clone`, you could clone the value
--> $DIR/borrow-closures-instead-of-move.rs:9:20 --> $DIR/borrow-closures-instead-of-move.rs:9:20
| |

View File

@ -24,10 +24,6 @@ LL | fn conspirator<F>(mut f: F) where F: FnMut(&mut R, bool) {
| ^ consider constraining this type parameter with `Clone` | ^ consider constraining this type parameter with `Clone`
LL | let mut r = R {c: Box::new(f)}; LL | let mut r = R {c: Box::new(f)};
| - you could clone this value | - you could clone this value
help: consider mutably borrowing `f`
|
LL | let mut r = R {c: Box::new(&mut f)};
| ++++
error: aborting due to 2 previous errors error: aborting due to 2 previous errors

View File

@ -1,22 +1,46 @@
//! Test suggetions to borrow generic arguments instead of moving when all predicates hold after //! Test suggetions to borrow generic arguments instead of moving. Tests for other instances of this
//! substituting in the reference type. //! can be found in `moved-value-on-as-ref-arg.rs` and `borrow-closures-instead-of-move.rs`
//@ run-rustfix //@ run-rustfix
//@ aux-build:suggest-borrow-for-generic-arg-aux.rs //@ aux-crate:aux=suggest-borrow-for-generic-arg-aux.rs
//@ edition: 2021
#![allow(unused_mut)] #![allow(unused_mut)]
extern crate suggest_borrow_for_generic_arg_aux as aux; use std::io::{self, Write};
pub fn main() { // test for `std::io::Write` (#131413)
let bar = aux::Bar; fn test_write() -> io::Result<()> {
aux::foo(&bar); //~ HELP consider borrowing `bar` let mut stdout = io::stdout();
let _baa = bar; //~ ERROR use of moved value aux::write_stuff(&stdout)?; //~ HELP consider borrowing `stdout`
let mut bar = aux::Bar; writeln!(stdout, "second line")?; //~ ERROR borrow of moved value: `stdout`
aux::qux(&mut bar); //~ HELP consider mutably borrowing `bar`
let _baa = bar; //~ ERROR use of moved value let mut buf = Vec::new();
let bar = aux::Bar; aux::write_stuff(&mut buf.clone())?; //~ HELP consider mutably borrowing `buf`
aux::bat(&bar); //~ HELP consider borrowing `bar` //~^ HELP consider cloning the value
let _baa = bar; //~ ERROR use of moved value writeln!(buf, "second_line") //~ ERROR borrow of moved value: `buf`
let mut bar = aux::Bar; }
aux::baz(&mut bar); //~ HELP consider mutably borrowing `bar`
let _baa = bar; //~ ERROR use of moved value /// test for `std::io::Read` (#131413)
fn test_read() -> io::Result<()> {
let stdin = io::stdin();
aux::read_and_discard(&stdin)?; //~ HELP consider borrowing `stdin`
aux::read_and_discard(stdin)?; //~ ERROR use of moved value: `stdin`
let mut bytes = std::collections::VecDeque::from([1, 2, 3, 4, 5, 6]);
aux::read_and_discard(&mut bytes.clone())?; //~ HELP consider mutably borrowing `bytes`
//~^ HELP consider cloning the value
aux::read_and_discard(bytes) //~ ERROR use of moved value: `bytes`
}
/// test that suggestions work with projection types in the callee's signature
fn test_projections() {
let mut iter = [1, 2, 3, 4, 5, 6].into_iter();
let _six: usize = aux::sum_three(&mut iter.clone()); //~ HELP consider mutably borrowing `iter`
//~^ HELP consider cloning the value
let _fifteen: usize = aux::sum_three(iter); //~ ERROR use of moved value: `iter`
}
fn main() {
test_write().unwrap();
test_read().unwrap();
test_projections();
} }

View File

@ -1,22 +1,46 @@
//! Test suggetions to borrow generic arguments instead of moving when all predicates hold after //! Test suggetions to borrow generic arguments instead of moving. Tests for other instances of this
//! substituting in the reference type. //! can be found in `moved-value-on-as-ref-arg.rs` and `borrow-closures-instead-of-move.rs`
//@ run-rustfix //@ run-rustfix
//@ aux-build:suggest-borrow-for-generic-arg-aux.rs //@ aux-crate:aux=suggest-borrow-for-generic-arg-aux.rs
//@ edition: 2021
#![allow(unused_mut)] #![allow(unused_mut)]
extern crate suggest_borrow_for_generic_arg_aux as aux; use std::io::{self, Write};
pub fn main() { // test for `std::io::Write` (#131413)
let bar = aux::Bar; fn test_write() -> io::Result<()> {
aux::foo(bar); //~ HELP consider borrowing `bar` let mut stdout = io::stdout();
let _baa = bar; //~ ERROR use of moved value aux::write_stuff(stdout)?; //~ HELP consider borrowing `stdout`
let mut bar = aux::Bar; writeln!(stdout, "second line")?; //~ ERROR borrow of moved value: `stdout`
aux::qux(bar); //~ HELP consider mutably borrowing `bar`
let _baa = bar; //~ ERROR use of moved value let mut buf = Vec::new();
let bar = aux::Bar; aux::write_stuff(buf)?; //~ HELP consider mutably borrowing `buf`
aux::bat(bar); //~ HELP consider borrowing `bar` //~^ HELP consider cloning the value
let _baa = bar; //~ ERROR use of moved value writeln!(buf, "second_line") //~ ERROR borrow of moved value: `buf`
let mut bar = aux::Bar; }
aux::baz(bar); //~ HELP consider mutably borrowing `bar`
let _baa = bar; //~ ERROR use of moved value /// test for `std::io::Read` (#131413)
fn test_read() -> io::Result<()> {
let stdin = io::stdin();
aux::read_and_discard(stdin)?; //~ HELP consider borrowing `stdin`
aux::read_and_discard(stdin)?; //~ ERROR use of moved value: `stdin`
let mut bytes = std::collections::VecDeque::from([1, 2, 3, 4, 5, 6]);
aux::read_and_discard(bytes)?; //~ HELP consider mutably borrowing `bytes`
//~^ HELP consider cloning the value
aux::read_and_discard(bytes) //~ ERROR use of moved value: `bytes`
}
/// test that suggestions work with projection types in the callee's signature
fn test_projections() {
let mut iter = [1, 2, 3, 4, 5, 6].into_iter();
let _six: usize = aux::sum_three(iter); //~ HELP consider mutably borrowing `iter`
//~^ HELP consider cloning the value
let _fifteen: usize = aux::sum_three(iter); //~ ERROR use of moved value: `iter`
}
fn main() {
test_write().unwrap();
test_read().unwrap();
test_projections();
} }

View File

@ -1,63 +1,93 @@
error[E0382]: use of moved value: `bar` error[E0382]: borrow of moved value: `stdout`
--> $DIR/suggest-borrow-for-generic-arg.rs:12:16 --> $DIR/suggest-borrow-for-generic-arg.rs:14:14
| |
LL | let bar = aux::Bar; LL | let mut stdout = io::stdout();
| --- move occurs because `bar` has type `Bar`, which does not implement the `Copy` trait | ---------- move occurs because `stdout` has type `Stdout`, which does not implement the `Copy` trait
LL | aux::foo(bar); LL | aux::write_stuff(stdout)?;
| --- value moved here | ------ value moved here
LL | let _baa = bar; LL | writeln!(stdout, "second line")?;
| ^^^ value used here after move | ^^^^^^ value borrowed here after move
| |
help: consider borrowing `bar` help: consider borrowing `stdout`
| |
LL | aux::foo(&bar); LL | aux::write_stuff(&stdout)?;
| + | +
error[E0382]: use of moved value: `bar` error[E0382]: borrow of moved value: `buf`
--> $DIR/suggest-borrow-for-generic-arg.rs:15:16 --> $DIR/suggest-borrow-for-generic-arg.rs:19:14
| |
LL | let mut bar = aux::Bar; LL | let mut buf = Vec::new();
| ------- move occurs because `bar` has type `Bar`, which does not implement the `Copy` trait | ------- move occurs because `buf` has type `Vec<u8>`, which does not implement the `Copy` trait
LL | aux::qux(bar); LL | aux::write_stuff(buf)?;
| --- value moved here | --- value moved here
LL | let _baa = bar; LL |
| ^^^ value used here after move LL | writeln!(buf, "second_line")
| ^^^ value borrowed here after move
| |
help: consider mutably borrowing `bar` help: consider mutably borrowing `buf`
| |
LL | aux::qux(&mut bar); LL | aux::write_stuff(&mut buf)?;
| ++++ | ++++
help: consider cloning the value if the performance cost is acceptable
|
LL | aux::write_stuff(buf.clone())?;
| ++++++++
error[E0382]: use of moved value: `bar` error[E0382]: use of moved value: `stdin`
--> $DIR/suggest-borrow-for-generic-arg.rs:18:16 --> $DIR/suggest-borrow-for-generic-arg.rs:26:27
| |
LL | let bar = aux::Bar; LL | let stdin = io::stdin();
| --- move occurs because `bar` has type `Bar`, which does not implement the `Copy` trait | ----- move occurs because `stdin` has type `Stdin`, which does not implement the `Copy` trait
LL | aux::bat(bar); LL | aux::read_and_discard(stdin)?;
| --- value moved here | ----- value moved here
LL | let _baa = bar; LL | aux::read_and_discard(stdin)?;
| ^^^ value used here after move | ^^^^^ value used here after move
| |
help: consider borrowing `bar` help: consider borrowing `stdin`
| |
LL | aux::bat(&bar); LL | aux::read_and_discard(&stdin)?;
| + | +
error[E0382]: use of moved value: `bar` error[E0382]: use of moved value: `bytes`
--> $DIR/suggest-borrow-for-generic-arg.rs:21:16 --> $DIR/suggest-borrow-for-generic-arg.rs:31:27
| |
LL | let mut bar = aux::Bar; LL | let mut bytes = std::collections::VecDeque::from([1, 2, 3, 4, 5, 6]);
| ------- move occurs because `bar` has type `Bar`, which does not implement the `Copy` trait | --------- move occurs because `bytes` has type `VecDeque<u8>`, which does not implement the `Copy` trait
LL | aux::baz(bar); LL | aux::read_and_discard(bytes)?;
| --- value moved here | ----- value moved here
LL | let _baa = bar; LL |
| ^^^ value used here after move LL | aux::read_and_discard(bytes)
| ^^^^^ value used here after move
| |
help: consider mutably borrowing `bar` help: consider mutably borrowing `bytes`
| |
LL | aux::baz(&mut bar); LL | aux::read_and_discard(&mut bytes)?;
| ++++ | ++++
help: consider cloning the value if the performance cost is acceptable
|
LL | aux::read_and_discard(bytes.clone())?;
| ++++++++
error: aborting due to 4 previous errors error[E0382]: use of moved value: `iter`
--> $DIR/suggest-borrow-for-generic-arg.rs:39:42
|
LL | let mut iter = [1, 2, 3, 4, 5, 6].into_iter();
| -------- move occurs because `iter` has type `std::array::IntoIter<usize, 6>`, which does not implement the `Copy` trait
LL | let _six: usize = aux::sum_three(iter);
| ---- value moved here
LL |
LL | let _fifteen: usize = aux::sum_three(iter);
| ^^^^ value used here after move
|
help: consider mutably borrowing `iter`
|
LL | let _six: usize = aux::sum_three(&mut iter);
| ++++
help: consider cloning the value if the performance cost is acceptable
|
LL | let _six: usize = aux::sum_three(iter.clone());
| ++++++++
error: aborting due to 5 previous errors
For more information about this error, try `rustc --explain E0382`. For more information about this error, try `rustc --explain E0382`.

View File

@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
| |
LL | a += 1; LL | a += 1;
| ^ | ^
help: consider mutably borrowing `hello`
|
LL | let b = &mut hello;
| ++++
error: aborting due to 1 previous error error: aborting due to 1 previous error