Skip to content

Commit 64c58a1

Browse files
authored
Rollup merge of #94639 - compiler-errors:rval-mutref, r=wesleywiser
Suggest dereferencing non-lval mutable reference on assignment 1. Adds deref suggestions for LHS of assignment (or assign-binop) when it implements `DerefMut` 2. Fixes missing deref suggestions for LHS when it isn't a place expr Fixes #46276 Fixes #93980
2 parents 77972d2 + d50d3fc commit 64c58a1

13 files changed

+290
-41
lines changed

compiler/rustc_typeck/src/check/coercion.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ use rustc_session::parse::feature_err;
5858
use rustc_span::symbol::sym;
5959
use rustc_span::{self, BytePos, DesugaringKind, Span};
6060
use rustc_target::spec::abi::Abi;
61-
use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
61+
use rustc_trait_selection::infer::InferCtxtExt as _;
62+
use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
6263
use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode};
6364

6465
use smallvec::{smallvec, SmallVec};
@@ -962,6 +963,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
962963
.find_map(|(ty, steps)| self.probe(|_| coerce.unify(ty, target)).ok().map(|_| steps))
963964
}
964965

966+
/// Given a type, this function will calculate and return the type given
967+
/// for `<Ty as Deref>::Target` only if `Ty` also implements `DerefMut`.
968+
///
969+
/// This function is for diagnostics only, since it does not register
970+
/// trait or region sub-obligations. (presumably we could, but it's not
971+
/// particularly important for diagnostics...)
972+
pub fn deref_once_mutably_for_diagnostic(&self, expr_ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
973+
self.autoderef(rustc_span::DUMMY_SP, expr_ty).nth(1).and_then(|(deref_ty, _)| {
974+
self.infcx
975+
.type_implements_trait(
976+
self.infcx.tcx.lang_items().deref_mut_trait()?,
977+
expr_ty,
978+
ty::List::empty(),
979+
self.param_env,
980+
)
981+
.may_apply()
982+
.then(|| deref_ty)
983+
})
984+
}
985+
965986
/// Given some expressions, their known unified type and another expression,
966987
/// tries to unify the types, potentially inserting coercions on any of the
967988
/// provided expressions and returns their LUB (aka "common supertype").

compiler/rustc_typeck/src/check/demand.rs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -696,28 +696,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
696696
};
697697

698698
if let Some(hir::Node::Expr(hir::Expr {
699-
kind: hir::ExprKind::Assign(left_expr, ..),
699+
kind: hir::ExprKind::Assign(..),
700700
..
701701
})) = self.tcx.hir().find(self.tcx.hir().get_parent_node(expr.hir_id))
702702
{
703703
if mutability == hir::Mutability::Mut {
704-
// Found the following case:
705-
// fn foo(opt: &mut Option<String>){ opt = None }
706-
// --- ^^^^
707-
// | |
708-
// consider dereferencing here: `*opt` |
709-
// expected mutable reference, found enum `Option`
710-
if sm.span_to_snippet(left_expr.span).is_ok() {
711-
return Some((
712-
left_expr.span.shrink_to_lo(),
713-
"consider dereferencing here to assign to the mutable \
714-
borrowed piece of memory"
715-
.to_string(),
716-
"*".to_string(),
717-
Applicability::MachineApplicable,
718-
true,
719-
));
720-
}
704+
// Suppressing this diagnostic, we'll properly print it in `check_expr_assign`
705+
return None;
721706
}
722707
}
723708

compiler/rustc_typeck/src/check/expr.rs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ use rustc_span::lev_distance::find_best_match_for_name;
5151
use rustc_span::source_map::Span;
5252
use rustc_span::symbol::{kw, sym, Ident, Symbol};
5353
use rustc_span::{BytePos, Pos};
54+
use rustc_trait_selection::infer::InferCtxtExt;
5455
use rustc_trait_selection::traits::{self, ObligationCauseCode};
5556

5657
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
@@ -836,6 +837,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
836837
lhs: &'tcx hir::Expr<'tcx>,
837838
err_code: &'static str,
838839
op_span: Span,
840+
adjust_err: impl FnOnce(&mut DiagnosticBuilder<'tcx, ErrorGuaranteed>),
839841
) {
840842
if lhs.is_syntactic_place_expr() {
841843
return;
@@ -858,6 +860,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
858860
);
859861
});
860862

863+
adjust_err(&mut err);
864+
861865
err.emit();
862866
}
863867

@@ -1050,10 +1054,47 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10501054
return self.tcx.ty_error();
10511055
}
10521056

1053-
self.check_lhs_assignable(lhs, "E0070", span);
1054-
10551057
let lhs_ty = self.check_expr_with_needs(&lhs, Needs::MutPlace);
1056-
let rhs_ty = self.check_expr_coercable_to_type(&rhs, lhs_ty, Some(lhs));
1058+
1059+
let suggest_deref_binop = |err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
1060+
rhs_ty: Ty<'tcx>| {
1061+
if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
1062+
// Can only assign if the type is sized, so if `DerefMut` yields a type that is
1063+
// unsized, do not suggest dereferencing it.
1064+
let lhs_deref_ty_is_sized = self
1065+
.infcx
1066+
.type_implements_trait(
1067+
self.tcx.lang_items().sized_trait().unwrap(),
1068+
lhs_deref_ty,
1069+
ty::List::empty(),
1070+
self.param_env,
1071+
)
1072+
.may_apply();
1073+
if lhs_deref_ty_is_sized && self.can_coerce(rhs_ty, lhs_deref_ty) {
1074+
err.span_suggestion_verbose(
1075+
lhs.span.shrink_to_lo(),
1076+
"consider dereferencing here to assign to the mutably borrowed value",
1077+
"*".to_string(),
1078+
Applicability::MachineApplicable,
1079+
);
1080+
}
1081+
}
1082+
};
1083+
1084+
self.check_lhs_assignable(lhs, "E0070", span, |err| {
1085+
let rhs_ty = self.check_expr(&rhs);
1086+
suggest_deref_binop(err, rhs_ty);
1087+
});
1088+
1089+
// This is (basically) inlined `check_expr_coercable_to_type`, but we want
1090+
// to suggest an additional fixup here in `suggest_deref_binop`.
1091+
let rhs_ty = self.check_expr_with_hint(&rhs, lhs_ty);
1092+
if let (_, Some(mut diag)) =
1093+
self.demand_coerce_diag(rhs, rhs_ty, lhs_ty, Some(lhs), AllowTwoPhase::No)
1094+
{
1095+
suggest_deref_binop(&mut diag, rhs_ty);
1096+
diag.emit();
1097+
}
10571098

10581099
self.require_type_is_sized(lhs_ty, lhs.span, traits::AssignmentLhsSized);
10591100

compiler/rustc_typeck/src/check/op.rs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
4141
return_ty
4242
};
4343

44-
self.check_lhs_assignable(lhs, "E0067", op.span);
44+
self.check_lhs_assignable(lhs, "E0067", op.span, |err| {
45+
if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
46+
if self
47+
.lookup_op_method(
48+
lhs_deref_ty,
49+
Some(rhs_ty),
50+
Some(rhs),
51+
Op::Binary(op, IsAssign::Yes),
52+
)
53+
.is_ok()
54+
{
55+
// Suppress this error, since we already emitted
56+
// a deref suggestion in check_overloaded_binop
57+
err.delay_as_bug();
58+
}
59+
}
60+
});
4561

4662
ty
4763
}
@@ -404,16 +420,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
404420
(err, missing_trait, use_output)
405421
}
406422
};
407-
if let Ref(_, rty, _) = lhs_ty.kind() {
408-
if self.infcx.type_is_copy_modulo_regions(self.param_env, *rty, lhs_expr.span)
409-
&& self
410-
.lookup_op_method(
411-
*rty,
412-
Some(rhs_ty),
413-
Some(rhs_expr),
414-
Op::Binary(op, is_assign),
415-
)
416-
.is_ok()
423+
424+
let mut suggest_deref_binop = |lhs_deref_ty: Ty<'tcx>| {
425+
if self
426+
.lookup_op_method(
427+
lhs_deref_ty,
428+
Some(rhs_ty),
429+
Some(rhs_expr),
430+
Op::Binary(op, is_assign),
431+
)
432+
.is_ok()
417433
{
418434
if let Ok(lstring) = source_map.span_to_snippet(lhs_expr.span) {
419435
let msg = &format!(
@@ -423,7 +439,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
423439
IsAssign::Yes => "=",
424440
IsAssign::No => "",
425441
},
426-
rty.peel_refs(),
442+
lhs_deref_ty.peel_refs(),
427443
lstring,
428444
);
429445
err.span_suggestion_verbose(
@@ -434,6 +450,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
434450
);
435451
}
436452
}
453+
};
454+
455+
// We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
456+
// `a += b` => `*a += b` if a is a mut ref.
457+
if is_assign == IsAssign::Yes
458+
&& let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
459+
suggest_deref_binop(lhs_deref_ty);
460+
} else if is_assign == IsAssign::No
461+
&& let Ref(_, lhs_deref_ty, _) = lhs_ty.kind() {
462+
if self.infcx.type_is_copy_modulo_regions(self.param_env, *lhs_deref_ty, lhs_expr.span) {
463+
suggest_deref_binop(*lhs_deref_ty);
464+
}
437465
}
438466
if let Some(missing_trait) = missing_trait {
439467
let mut visitor = TypeParamVisitor(vec![]);

src/test/ui/issues/issue-5239-1.stderr

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ LL | let x = |ref x: isize| { x += 1; };
55
| -^^^^^
66
| |
77
| cannot use `+=` on type `&isize`
8-
|
9-
help: `+=` can be used on `isize`, you can dereference `x`
10-
|
11-
LL | let x = |ref x: isize| { *x += 1; };
12-
| +
138

149
error: aborting due to previous error
1510

src/test/ui/suggestions/mut-ref-reassignment.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ LL | opt = None;
88
|
99
= note: expected mutable reference `&mut Option<String>`
1010
found enum `Option<_>`
11-
help: consider dereferencing here to assign to the mutable borrowed piece of memory
11+
help: consider dereferencing here to assign to the mutably borrowed value
1212
|
1313
LL | *opt = None;
1414
| +
@@ -34,7 +34,7 @@ LL | opt = Some(String::new())
3434
|
3535
= note: expected mutable reference `&mut Option<String>`
3636
found enum `Option<String>`
37-
help: consider dereferencing here to assign to the mutable borrowed piece of memory
37+
help: consider dereferencing here to assign to the mutably borrowed value
3838
|
3939
LL | *opt = Some(String::new())
4040
| +
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-rustfix
2+
3+
fn main() {
4+
let x = std::sync::Mutex::new(1usize);
5+
*x.lock().unwrap() = 2;
6+
//~^ ERROR invalid left-hand side of assignment
7+
*x.lock().unwrap() += 1;
8+
//~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
9+
10+
let mut y = x.lock().unwrap();
11+
*y = 2;
12+
//~^ ERROR mismatched types
13+
*y += 1;
14+
//~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-rustfix
2+
3+
fn main() {
4+
let x = std::sync::Mutex::new(1usize);
5+
x.lock().unwrap() = 2;
6+
//~^ ERROR invalid left-hand side of assignment
7+
x.lock().unwrap() += 1;
8+
//~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
9+
10+
let mut y = x.lock().unwrap();
11+
y = 2;
12+
//~^ ERROR mismatched types
13+
y += 1;
14+
//~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
15+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
error[E0070]: invalid left-hand side of assignment
2+
--> $DIR/assign-non-lval-derefmut.rs:5:23
3+
|
4+
LL | x.lock().unwrap() = 2;
5+
| ----------------- ^
6+
| |
7+
| cannot assign to this expression
8+
|
9+
help: consider dereferencing here to assign to the mutably borrowed value
10+
|
11+
LL | *x.lock().unwrap() = 2;
12+
| +
13+
14+
error[E0368]: binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
15+
--> $DIR/assign-non-lval-derefmut.rs:7:5
16+
|
17+
LL | x.lock().unwrap() += 1;
18+
| -----------------^^^^^
19+
| |
20+
| cannot use `+=` on type `MutexGuard<'_, usize>`
21+
|
22+
help: `+=` can be used on `usize`, you can dereference `x.lock().unwrap()`
23+
|
24+
LL | *x.lock().unwrap() += 1;
25+
| +
26+
27+
error[E0308]: mismatched types
28+
--> $DIR/assign-non-lval-derefmut.rs:11:9
29+
|
30+
LL | let mut y = x.lock().unwrap();
31+
| ----------------- expected due to this value
32+
LL | y = 2;
33+
| ^ expected struct `MutexGuard`, found integer
34+
|
35+
= note: expected struct `MutexGuard<'_, usize>`
36+
found type `{integer}`
37+
help: consider dereferencing here to assign to the mutably borrowed value
38+
|
39+
LL | *y = 2;
40+
| +
41+
42+
error[E0368]: binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
43+
--> $DIR/assign-non-lval-derefmut.rs:13:5
44+
|
45+
LL | y += 1;
46+
| -^^^^^
47+
| |
48+
| cannot use `+=` on type `MutexGuard<'_, usize>`
49+
|
50+
help: `+=` can be used on `usize`, you can dereference `y`
51+
|
52+
LL | *y += 1;
53+
| +
54+
55+
error: aborting due to 4 previous errors
56+
57+
Some errors have detailed explanations: E0070, E0308, E0368.
58+
For more information about an error, try `rustc --explain E0070`.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-rustfix
2+
3+
fn main() {
4+
let mut x = vec![1usize];
5+
*x.last_mut().unwrap() = 2;
6+
//~^ ERROR invalid left-hand side of assignment
7+
*x.last_mut().unwrap() += 1;
8+
//~^ ERROR binary assignment operation `+=` cannot be applied to type `&mut usize`
9+
10+
let y = x.last_mut().unwrap();
11+
*y = 2;
12+
//~^ ERROR mismatched types
13+
*y += 1;
14+
//~^ ERROR binary assignment operation `+=` cannot be applied to type `&mut usize`
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-rustfix
2+
3+
fn main() {
4+
let mut x = vec![1usize];
5+
x.last_mut().unwrap() = 2;
6+
//~^ ERROR invalid left-hand side of assignment
7+
x.last_mut().unwrap() += 1;
8+
//~^ ERROR binary assignment operation `+=` cannot be applied to type `&mut usize`
9+
10+
let y = x.last_mut().unwrap();
11+
y = 2;
12+
//~^ ERROR mismatched types
13+
y += 1;
14+
//~^ ERROR binary assignment operation `+=` cannot be applied to type `&mut usize`
15+
}

0 commit comments

Comments
 (0)