Skip to content

Commit 766f6a4

Browse files
committed
Fix deref field pattern suggestions and improve error messages
1 parent ec2d669 commit 766f6a4

15 files changed

Lines changed: 509 additions & 99 deletions

compiler/rustc_borrowck/src/diagnostics/move_errors.rs

Lines changed: 135 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ enum GroupedMoveError<'tcx> {
9494
},
9595
}
9696

97+
struct PatternBindingInfo {
98+
pat_span: Span,
99+
binding_spans: Vec<Span>,
100+
has_mutable_by_value_binding: bool,
101+
}
102+
97103
impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
98104
pub(crate) fn report_move_errors(&mut self) {
99105
let grouped_errors = self.group_move_errors();
@@ -678,8 +684,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
678684
fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diag<'_>, span: Span) {
679685
match error {
680686
GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {
681-
self.add_borrow_suggestions(err, span, !binds_to.is_empty());
687+
binds_to.sort();
688+
binds_to.dedup();
689+
682690
if binds_to.is_empty() {
691+
self.add_borrow_suggestions(err, span, false);
683692
let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;
684693
let place_desc = match self.describe_place(move_from.as_ref()) {
685694
Some(desc) => format!("`{desc}`"),
@@ -697,16 +706,30 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
697706
span,
698707
});
699708
} else {
700-
binds_to.sort();
701-
binds_to.dedup();
702-
703-
self.add_move_error_details(err, &binds_to, &[]);
709+
let binding_info = self.pattern_binding_info(&binds_to);
710+
let suggest_pattern_binding = binding_info.as_ref().is_some_and(|info| {
711+
self.should_suggest_pattern_binding_instead(span, info)
712+
});
713+
let desugar_spans = if suggest_pattern_binding {
714+
self.add_move_error_suggestions(err, &binds_to)
715+
} else {
716+
if self.should_suggest_borrow_instead(span, binding_info.as_ref()) {
717+
self.add_borrow_suggestions(err, span, true);
718+
}
719+
None
720+
};
721+
self.add_move_error_details(
722+
err,
723+
&binds_to,
724+
desugar_spans.as_deref().unwrap_or_default(),
725+
);
704726
}
705727
}
706728
GroupedMoveError::MovesFromValue { mut binds_to, .. } => {
707729
binds_to.sort();
708730
binds_to.dedup();
709-
let desugar_spans = self.add_move_error_suggestions(err, &binds_to);
731+
let desugar_spans =
732+
self.add_move_error_suggestions(err, &binds_to).unwrap_or_default();
710733
self.add_move_error_details(err, &binds_to, &desugar_spans);
711734
}
712735
// No binding. Nothing to suggest.
@@ -861,7 +884,102 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
861884
}
862885
}
863886

864-
fn add_move_error_suggestions(&self, err: &mut Diag<'_>, binds_to: &[Local]) -> Vec<Span> {
887+
fn should_suggest_pattern_binding_instead(
888+
&self,
889+
span: Span,
890+
binding_info: &PatternBindingInfo,
891+
) -> bool {
892+
let Some(expr) = self.find_expr(span) else {
893+
return false;
894+
};
895+
896+
let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());
897+
let projection_qualifies = match expr.kind {
898+
hir::ExprKind::Field(base, ..) => {
899+
!typeck_results.node_type_opt(base.hir_id).is_some_and(|base_ty| {
900+
binding_info.has_mutable_by_value_binding
901+
&& matches!(base_ty.kind(), ty::Ref(_, _, hir::Mutability::Not))
902+
})
903+
}
904+
hir::ExprKind::Index(base, ..) => typeck_results
905+
.node_type_opt(base.hir_id)
906+
.is_some_and(|base_ty| match base_ty.kind() {
907+
ty::Ref(_, _, hir::Mutability::Not) | ty::RawPtr(..) => false,
908+
ty::Ref(_, _, hir::Mutability::Mut) => {
909+
binding_info.has_mutable_by_value_binding
910+
}
911+
_ => true,
912+
}),
913+
_ => false,
914+
};
915+
if !projection_qualifies {
916+
return false;
917+
}
918+
919+
let is_single_binding = binding_info.binding_spans.len() == 1
920+
&& binding_info.binding_spans[0] == binding_info.pat_span;
921+
!is_single_binding
922+
}
923+
924+
fn should_suggest_borrow_instead(
925+
&self,
926+
span: Span,
927+
binding_info: Option<&PatternBindingInfo>,
928+
) -> bool {
929+
if !binding_info.is_some_and(|info| info.has_mutable_by_value_binding) {
930+
return true;
931+
}
932+
933+
let Some(expr) = self.find_expr(span) else {
934+
return true;
935+
};
936+
937+
let Some(base) = (match expr.kind {
938+
hir::ExprKind::Field(base, _) | hir::ExprKind::Index(base, ..) => Some(base),
939+
_ => None,
940+
}) else {
941+
return true;
942+
};
943+
944+
!self
945+
.infcx
946+
.tcx
947+
.typeck(self.mir_def_id())
948+
.node_type_opt(base.hir_id)
949+
.is_some_and(|base_ty| matches!(base_ty.kind(), ty::Ref(_, _, hir::Mutability::Not)))
950+
}
951+
952+
fn pattern_binding_info(&self, binds_to: &[Local]) -> Option<PatternBindingInfo> {
953+
let mut pat_span = None;
954+
let mut binding_spans = Vec::new();
955+
let mut has_mutable_by_value_binding = false;
956+
for local in binds_to {
957+
let bind_to = &self.body.local_decls[*local];
958+
if let LocalInfo::User(BindingForm::Var(VarBindingForm {
959+
pat_span: pat_sp,
960+
binding_mode,
961+
..
962+
})) = *bind_to.local_info()
963+
{
964+
pat_span = Some(pat_sp);
965+
binding_spans.push(bind_to.source_info.span);
966+
has_mutable_by_value_binding |=
967+
matches!(binding_mode, hir::BindingMode(hir::ByRef::No, hir::Mutability::Mut));
968+
}
969+
}
970+
971+
Some(PatternBindingInfo {
972+
pat_span: pat_span?,
973+
binding_spans,
974+
has_mutable_by_value_binding,
975+
})
976+
}
977+
978+
fn add_move_error_suggestions(
979+
&self,
980+
err: &mut Diag<'_>,
981+
binds_to: &[Local],
982+
) -> Option<Vec<Span>> {
865983
/// A HIR visitor to associate each binding with a `&` or `&mut` that could be removed to
866984
/// make it bind by reference instead (if possible)
867985
struct BindingFinder<'tcx> {
@@ -964,27 +1082,20 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
9641082
self.has_adjustments = parent_has_adjustments;
9651083
}
9661084
}
967-
let mut pat_span = None;
968-
let mut binding_spans = Vec::new();
969-
for local in binds_to {
970-
let bind_to = &self.body.local_decls[*local];
971-
if let LocalInfo::User(BindingForm::Var(VarBindingForm { pat_span: pat_sp, .. })) =
972-
*bind_to.local_info()
973-
{
974-
pat_span = Some(pat_sp);
975-
binding_spans.push(bind_to.source_info.span);
976-
}
977-
}
978-
let Some(pat_span) = pat_span else { return Vec::new() };
1085+
let Some(binding_info) = self.pattern_binding_info(binds_to) else {
1086+
return None;
1087+
};
9791088

9801089
let tcx = self.infcx.tcx;
981-
let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) else { return Vec::new() };
1090+
let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) else {
1091+
return None;
1092+
};
9821093
let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());
9831094
let mut finder = BindingFinder {
9841095
typeck_results,
9851096
tcx,
986-
pat_span,
987-
binding_spans,
1097+
pat_span: binding_info.pat_span,
1098+
binding_spans: binding_info.binding_spans,
9881099
found_pat: false,
9891100
ref_pat: None,
9901101
has_adjustments: false,
@@ -1015,7 +1126,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
10151126
for (span, msg, suggestion) in suggestions {
10161127
err.span_suggestion_verbose(span, msg, suggestion, Applicability::MachineApplicable);
10171128
}
1018-
finder.desugar_binding_spans
1129+
1130+
Some(finder.desugar_binding_spans)
10191131
}
10201132

10211133
fn add_move_error_details(

tests/ui/binding/issue-40402-2.stderr

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ LL | let (a, b) = x[0];
88
| data moved here
99
|
1010
= note: move occurs because these variables have types that don't implement the `Copy` trait
11-
help: consider borrowing here
11+
help: consider borrowing the pattern binding
1212
|
13-
LL | let (a, b) = &x[0];
14-
| +
13+
LL | let (ref a, b) = x[0];
14+
| +++
15+
help: consider borrowing the pattern binding
16+
|
17+
LL | let (a, ref b) = x[0];
18+
| +++
1519

1620
error: aborting due to 1 previous error
1721

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@ run-rustfix
2+
#![allow(dead_code)]
3+
4+
use std::ops::{Deref, DerefMut};
5+
6+
fn take(mut wrap: Wrap<Struct>) {
7+
if let Some(ref mut val) = wrap.field {
8+
//~^ ERROR cannot move out of dereference of `Wrap<Struct>`
9+
val.0 = ();
10+
}
11+
}
12+
13+
fn take_mut_ref_base(mut wrap: Wrap<Struct>) {
14+
if let Some(ref mut val) = (&mut wrap).field {
15+
//~^ ERROR cannot move out of dereference of `Wrap<Struct>`
16+
val.0 = ();
17+
}
18+
}
19+
20+
struct Wrap<T>(T);
21+
struct Struct {
22+
field: Option<NonCopy>,
23+
}
24+
struct NonCopy(());
25+
26+
impl<T> Deref for Wrap<T> {
27+
type Target = T;
28+
29+
fn deref(&self) -> &Self::Target {
30+
&self.0
31+
}
32+
}
33+
34+
impl<T> DerefMut for Wrap<T> {
35+
fn deref_mut(&mut self) -> &mut Self::Target {
36+
&mut self.0
37+
}
38+
}
39+
40+
fn main() {}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@ run-rustfix
2+
#![allow(dead_code)]
3+
4+
use std::ops::{Deref, DerefMut};
5+
6+
fn take(mut wrap: Wrap<Struct>) {
7+
if let Some(mut val) = wrap.field {
8+
//~^ ERROR cannot move out of dereference of `Wrap<Struct>`
9+
val.0 = ();
10+
}
11+
}
12+
13+
fn take_mut_ref_base(mut wrap: Wrap<Struct>) {
14+
if let Some(mut val) = (&mut wrap).field {
15+
//~^ ERROR cannot move out of dereference of `Wrap<Struct>`
16+
val.0 = ();
17+
}
18+
}
19+
20+
struct Wrap<T>(T);
21+
struct Struct {
22+
field: Option<NonCopy>,
23+
}
24+
struct NonCopy(());
25+
26+
impl<T> Deref for Wrap<T> {
27+
type Target = T;
28+
29+
fn deref(&self) -> &Self::Target {
30+
&self.0
31+
}
32+
}
33+
34+
impl<T> DerefMut for Wrap<T> {
35+
fn deref_mut(&mut self) -> &mut Self::Target {
36+
&mut self.0
37+
}
38+
}
39+
40+
fn main() {}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
error[E0507]: cannot move out of dereference of `Wrap<Struct>`
2+
--> $DIR/deref-field-pattern-ref-suggestion-issue-146995.rs:7:28
3+
|
4+
LL | if let Some(mut val) = wrap.field {
5+
| ------- ^^^^^^^^^^
6+
| |
7+
| data moved here because `val` has type `NonCopy`, which does not implement the `Copy` trait
8+
|
9+
help: consider borrowing the pattern binding
10+
|
11+
LL | if let Some(ref mut val) = wrap.field {
12+
| +++
13+
14+
error[E0507]: cannot move out of dereference of `Wrap<Struct>`
15+
--> $DIR/deref-field-pattern-ref-suggestion-issue-146995.rs:14:28
16+
|
17+
LL | if let Some(mut val) = (&mut wrap).field {
18+
| ------- ^^^^^^^^^^^^^^^^^
19+
| |
20+
| data moved here because `val` has type `NonCopy`, which does not implement the `Copy` trait
21+
|
22+
help: consider borrowing the pattern binding
23+
|
24+
LL | if let Some(ref mut val) = (&mut wrap).field {
25+
| +++
26+
27+
error: aborting due to 2 previous errors
28+
29+
For more information about this error, try `rustc --explain E0507`.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//@ run-rustfix
2+
#![allow(dead_code)]
3+
4+
use std::ops::{Deref, DerefMut};
5+
6+
fn take(mut wrap: Wrap<[Option<NonCopy>; 1]>) {
7+
if let Some(ref mut val) = wrap[0] {
8+
//~^ ERROR cannot move out of type `[Option<NonCopy>; 1]`, a non-copy array
9+
val.0 = ();
10+
}
11+
}
12+
13+
fn take_mut_ref_base(mut wrap: Wrap<[Option<NonCopy>; 1]>) {
14+
if let Some(ref mut val) = (&mut wrap)[0] {
15+
//~^ ERROR cannot move out of type `[Option<NonCopy>; 1]`, a non-copy array
16+
val.0 = ();
17+
}
18+
}
19+
20+
struct Wrap<T>(T);
21+
struct NonCopy(());
22+
23+
impl<T> Deref for Wrap<T> {
24+
type Target = T;
25+
26+
fn deref(&self) -> &Self::Target {
27+
&self.0
28+
}
29+
}
30+
31+
impl<T> DerefMut for Wrap<T> {
32+
fn deref_mut(&mut self) -> &mut Self::Target {
33+
&mut self.0
34+
}
35+
}
36+
37+
fn main() {}

0 commit comments

Comments
 (0)