Skip to content

Commit d50d3fc

Browse files
better lvalue errors for things implementing DerefMut
1 parent b26580f commit d50d3fc

12 files changed

+218
-55
lines changed

compiler/rustc_typeck/src/check/coercion.rs

+22-1
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

+3-18
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

+31-10
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> {
@@ -1055,25 +1056,45 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10551056

10561057
let lhs_ty = self.check_expr_with_needs(&lhs, Needs::MutPlace);
10571058

1058-
self.check_lhs_assignable(lhs, "E0070", span, |err| {
1059-
let rhs_ty = self.check_expr(&rhs);
1060-
1061-
// FIXME: This could be done any time lhs_ty is DerefMut into something that
1062-
// is compatible with rhs_ty, and not _just_ `&mut`
1063-
if let ty::Ref(_, lhs_inner_ty, hir::Mutability::Mut) = lhs_ty.kind() {
1064-
if self.can_coerce(rhs_ty, *lhs_inner_ty) {
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) {
10651074
err.span_suggestion_verbose(
10661075
lhs.span.shrink_to_lo(),
1067-
"consider dereferencing here to assign to the mutable \
1068-
borrowed piece of memory",
1076+
"consider dereferencing here to assign to the mutably borrowed value",
10691077
"*".to_string(),
10701078
Applicability::MachineApplicable,
10711079
);
10721080
}
10731081
}
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);
10741087
});
10751088

1076-
let rhs_ty = self.check_expr_coercable_to_type(&rhs, lhs_ty, Some(lhs));
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+
}
10771098

10781099
self.require_type_is_sized(lhs_ty, lhs.span, traits::AssignmentLhsSized);
10791100

compiler/rustc_typeck/src/check/op.rs

+30-20
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
4242
};
4343

4444
self.check_lhs_assignable(lhs, "E0067", op.span, |err| {
45-
if let Ref(_, rty, hir::Mutability::Mut) = lhs_ty.kind() {
45+
if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
4646
if self
47-
.lookup_op_method(*rty, Some(rhs_ty), Some(rhs), Op::Binary(op, IsAssign::Yes))
47+
.lookup_op_method(
48+
lhs_deref_ty,
49+
Some(rhs_ty),
50+
Some(rhs),
51+
Op::Binary(op, IsAssign::Yes),
52+
)
4853
.is_ok()
4954
{
5055
// Suppress this error, since we already emitted
@@ -415,23 +420,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
415420
(err, missing_trait, use_output)
416421
}
417422
};
418-
if let Ref(_, rty, mutability) = lhs_ty.kind() {
419-
let is_copy =
420-
self.infcx.type_is_copy_modulo_regions(self.param_env, *rty, lhs_expr.span);
421-
// We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
422-
// `a += b` => `*a += b` if a is a mut ref.
423-
// FIXME: This could be done any time lhs_ty is DerefMut into something that
424-
// is compatible with rhs_ty, and not _just_ `&mut` (for IsAssign::Yes).
425-
if ((is_assign == IsAssign::No && is_copy)
426-
|| (is_assign == IsAssign::Yes && *mutability == hir::Mutability::Mut))
427-
&& self
428-
.lookup_op_method(
429-
*rty,
430-
Some(rhs_ty),
431-
Some(rhs_expr),
432-
Op::Binary(op, is_assign),
433-
)
434-
.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()
435433
{
436434
if let Ok(lstring) = source_map.span_to_snippet(lhs_expr.span) {
437435
let msg = &format!(
@@ -441,7 +439,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
441439
IsAssign::Yes => "=",
442440
IsAssign::No => "",
443441
},
444-
rty.peel_refs(),
442+
lhs_deref_ty.peel_refs(),
445443
lstring,
446444
);
447445
err.span_suggestion_verbose(
@@ -452,6 +450,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
452450
);
453451
}
454452
}
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+
}
455465
}
456466
if let Some(missing_trait) = missing_trait {
457467
let mut visitor = TypeParamVisitor(vec![]);

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

+2-2
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
| +
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+
}
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+
}
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`.

src/test/ui/typeck/assign-non-lval-mut-ref.fixed

+6
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@ fn main() {
66
//~^ ERROR invalid left-hand side of assignment
77
*x.last_mut().unwrap() += 1;
88
//~^ 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`
915
}

src/test/ui/typeck/assign-non-lval-mut-ref.rs

+6
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@ fn main() {
66
//~^ ERROR invalid left-hand side of assignment
77
x.last_mut().unwrap() += 1;
88
//~^ 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`
915
}

src/test/ui/typeck/assign-non-lval-mut-ref.stderr

+29-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ LL | x.last_mut().unwrap() = 2;
66
| |
77
| cannot assign to this expression
88
|
9-
help: consider dereferencing here to assign to the mutable borrowed piece of memory
9+
help: consider dereferencing here to assign to the mutably borrowed value
1010
|
1111
LL | *x.last_mut().unwrap() = 2;
1212
| +
@@ -24,7 +24,33 @@ help: `+=` can be used on `usize`, you can dereference `x.last_mut().unwrap()`
2424
LL | *x.last_mut().unwrap() += 1;
2525
| +
2626

27-
error: aborting due to 2 previous errors
27+
error[E0308]: mismatched types
28+
--> $DIR/assign-non-lval-mut-ref.rs:11:9
29+
|
30+
LL | let y = x.last_mut().unwrap();
31+
| --------------------- expected due to this value
32+
LL | y = 2;
33+
| ^ expected `&mut usize`, found integer
34+
|
35+
help: consider dereferencing here to assign to the mutably borrowed value
36+
|
37+
LL | *y = 2;
38+
| +
39+
40+
error[E0368]: binary assignment operation `+=` cannot be applied to type `&mut usize`
41+
--> $DIR/assign-non-lval-mut-ref.rs:13:5
42+
|
43+
LL | y += 1;
44+
| -^^^^^
45+
| |
46+
| cannot use `+=` on type `&mut usize`
47+
|
48+
help: `+=` can be used on `usize`, you can dereference `y`
49+
|
50+
LL | *y += 1;
51+
| +
52+
53+
error: aborting due to 4 previous errors
2854

29-
Some errors have detailed explanations: E0070, E0368.
55+
Some errors have detailed explanations: E0070, E0308, E0368.
3056
For more information about an error, try `rustc --explain E0070`.

src/test/ui/typeck/issue-93486.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ LL | vec![].last_mut().unwrap() = 3_u8;
66
| |
77
| cannot assign to this expression
88
|
9-
help: consider dereferencing here to assign to the mutable borrowed piece of memory
9+
help: consider dereferencing here to assign to the mutably borrowed value
1010
|
1111
LL | *vec![].last_mut().unwrap() = 3_u8;
1212
| +

0 commit comments

Comments
 (0)