Skip to content

Suggest dereferencing non-lval mutable reference on assignment #94639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion compiler/rustc_typeck/src/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ use rustc_session::parse::feature_err;
use rustc_span::symbol::sym;
use rustc_span::{self, BytePos, DesugaringKind, Span};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
use rustc_trait_selection::infer::InferCtxtExt as _;
use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode};

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

/// Given a type, this function will calculate and return the type given
/// for `<Ty as Deref>::Target` only if `Ty` also implements `DerefMut`.
///
/// This function is for diagnostics only, since it does not register
/// trait or region sub-obligations. (presumably we could, but it's not
/// particularly important for diagnostics...)
pub fn deref_once_mutably_for_diagnostic(&self, expr_ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
self.autoderef(rustc_span::DUMMY_SP, expr_ty).nth(1).and_then(|(deref_ty, _)| {
self.infcx
.type_implements_trait(
self.infcx.tcx.lang_items().deref_mut_trait()?,
expr_ty,
ty::List::empty(),
self.param_env,
)
.may_apply()
.then(|| deref_ty)
})
}

/// Given some expressions, their known unified type and another expression,
/// tries to unify the types, potentially inserting coercions on any of the
/// provided expressions and returns their LUB (aka "common supertype").
Expand Down
21 changes: 3 additions & 18 deletions compiler/rustc_typeck/src/check/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,28 +696,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
};

if let Some(hir::Node::Expr(hir::Expr {
kind: hir::ExprKind::Assign(left_expr, ..),
kind: hir::ExprKind::Assign(..),
..
})) = self.tcx.hir().find(self.tcx.hir().get_parent_node(expr.hir_id))
{
if mutability == hir::Mutability::Mut {
// Found the following case:
// fn foo(opt: &mut Option<String>){ opt = None }
// --- ^^^^
// | |
// consider dereferencing here: `*opt` |
// expected mutable reference, found enum `Option`
if sm.span_to_snippet(left_expr.span).is_ok() {
return Some((
left_expr.span.shrink_to_lo(),
"consider dereferencing here to assign to the mutable \
borrowed piece of memory"
.to_string(),
"*".to_string(),
Applicability::MachineApplicable,
true,
));
}
// Suppressing this diagnostic, we'll properly print it in `check_expr_assign`
return None;
Comment on lines +704 to +705
Copy link
Contributor

@estebank estebank May 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@compiler-errors For future reference, adding a delay_span_bug here might be a good idea to protect from miscompilations in the face of refactors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shoot, yeah. i can add one in a follow-up.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this just suppresses a suggestion, not the error itself. And the other case I actually did suppress a real error, I downgraded it to a delayed bug. See: https://github.com/rust-lang/rust/pull/94639/files#diff-c46757032f463a1170b40e5c41d569ce058668cfe259774fb8a7936e3fdd82f4R57

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@compiler-errors perfect!

}
}

Expand Down
47 changes: 44 additions & 3 deletions compiler/rustc_typeck/src/check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use rustc_span::lev_distance::find_best_match_for_name;
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::{BytePos, Pos};
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::{self, ObligationCauseCode};

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Expand Down Expand Up @@ -836,6 +837,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
lhs: &'tcx hir::Expr<'tcx>,
err_code: &'static str,
op_span: Span,
adjust_err: impl FnOnce(&mut DiagnosticBuilder<'tcx, ErrorGuaranteed>),
) {
if lhs.is_syntactic_place_expr() {
return;
Expand All @@ -858,6 +860,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
});

adjust_err(&mut err);

err.emit();
}

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

self.check_lhs_assignable(lhs, "E0070", span);

let lhs_ty = self.check_expr_with_needs(&lhs, Needs::MutPlace);
let rhs_ty = self.check_expr_coercable_to_type(&rhs, lhs_ty, Some(lhs));

let suggest_deref_binop = |err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>,
rhs_ty: Ty<'tcx>| {
if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
// Can only assign if the type is sized, so if `DerefMut` yields a type that is
// unsized, do not suggest dereferencing it.
let lhs_deref_ty_is_sized = self
.infcx
.type_implements_trait(
self.tcx.lang_items().sized_trait().unwrap(),
lhs_deref_ty,
ty::List::empty(),
self.param_env,
)
.may_apply();
if lhs_deref_ty_is_sized && self.can_coerce(rhs_ty, lhs_deref_ty) {
err.span_suggestion_verbose(
lhs.span.shrink_to_lo(),
"consider dereferencing here to assign to the mutably borrowed value",
"*".to_string(),
Applicability::MachineApplicable,
);
}
}
};

self.check_lhs_assignable(lhs, "E0070", span, |err| {
let rhs_ty = self.check_expr(&rhs);
suggest_deref_binop(err, rhs_ty);
});

// This is (basically) inlined `check_expr_coercable_to_type`, but we want
// to suggest an additional fixup here in `suggest_deref_binop`.
let rhs_ty = self.check_expr_with_hint(&rhs, lhs_ty);
if let (_, Some(mut diag)) =
self.demand_coerce_diag(rhs, rhs_ty, lhs_ty, Some(lhs), AllowTwoPhase::No)
{
suggest_deref_binop(&mut diag, rhs_ty);
diag.emit();
}

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

Expand Down
52 changes: 40 additions & 12 deletions compiler/rustc_typeck/src/check/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return_ty
};

self.check_lhs_assignable(lhs, "E0067", op.span);
self.check_lhs_assignable(lhs, "E0067", op.span, |err| {
if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
if self
.lookup_op_method(
lhs_deref_ty,
Some(rhs_ty),
Some(rhs),
Op::Binary(op, IsAssign::Yes),
)
.is_ok()
{
// Suppress this error, since we already emitted
// a deref suggestion in check_overloaded_binop
err.delay_as_bug();
}
}
});

ty
}
Expand Down Expand Up @@ -404,16 +420,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
(err, missing_trait, use_output)
}
};
if let Ref(_, rty, _) = lhs_ty.kind() {
if self.infcx.type_is_copy_modulo_regions(self.param_env, *rty, lhs_expr.span)
&& self
.lookup_op_method(
*rty,
Some(rhs_ty),
Some(rhs_expr),
Op::Binary(op, is_assign),
)
.is_ok()

let mut suggest_deref_binop = |lhs_deref_ty: Ty<'tcx>| {
if self
.lookup_op_method(
lhs_deref_ty,
Some(rhs_ty),
Some(rhs_expr),
Op::Binary(op, is_assign),
)
.is_ok()
{
if let Ok(lstring) = source_map.span_to_snippet(lhs_expr.span) {
let msg = &format!(
Expand All @@ -423,7 +439,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
IsAssign::Yes => "=",
IsAssign::No => "",
},
rty.peel_refs(),
lhs_deref_ty.peel_refs(),
lstring,
);
err.span_suggestion_verbose(
Expand All @@ -434,6 +450,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
}
}
};

// We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
// `a += b` => `*a += b` if a is a mut ref.
if is_assign == IsAssign::Yes
&& let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
suggest_deref_binop(lhs_deref_ty);
} else if is_assign == IsAssign::No
&& let Ref(_, lhs_deref_ty, _) = lhs_ty.kind() {
if self.infcx.type_is_copy_modulo_regions(self.param_env, *lhs_deref_ty, lhs_expr.span) {
suggest_deref_binop(*lhs_deref_ty);
}
}
if let Some(missing_trait) = missing_trait {
let mut visitor = TypeParamVisitor(vec![]);
Expand Down
5 changes: 0 additions & 5 deletions src/test/ui/issues/issue-5239-1.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ LL | let x = |ref x: isize| { x += 1; };
| -^^^^^
| |
| cannot use `+=` on type `&isize`
|
help: `+=` can be used on `isize`, you can dereference `x`
|
LL | let x = |ref x: isize| { *x += 1; };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kinda a regression, but not actually... &isize doesn't have a +=. Perhaps we could suggest changing the ref to ref mut, but that seems out of scope for this PR.

| +

error: aborting due to previous error

Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/suggestions/mut-ref-reassignment.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ LL | opt = None;
|
= note: expected mutable reference `&mut Option<String>`
found enum `Option<_>`
help: consider dereferencing here to assign to the mutable borrowed piece of memory
help: consider dereferencing here to assign to the mutably borrowed value
|
LL | *opt = None;
| +
Expand All @@ -34,7 +34,7 @@ LL | opt = Some(String::new())
|
= note: expected mutable reference `&mut Option<String>`
found enum `Option<String>`
help: consider dereferencing here to assign to the mutable borrowed piece of memory
help: consider dereferencing here to assign to the mutably borrowed value
|
LL | *opt = Some(String::new())
| +
Expand Down
15 changes: 15 additions & 0 deletions src/test/ui/typeck/assign-non-lval-derefmut.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// run-rustfix

fn main() {
let x = std::sync::Mutex::new(1usize);
*x.lock().unwrap() = 2;
//~^ ERROR invalid left-hand side of assignment
*x.lock().unwrap() += 1;
//~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`

let mut y = x.lock().unwrap();
*y = 2;
//~^ ERROR mismatched types
*y += 1;
//~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
}
15 changes: 15 additions & 0 deletions src/test/ui/typeck/assign-non-lval-derefmut.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// run-rustfix

fn main() {
let x = std::sync::Mutex::new(1usize);
x.lock().unwrap() = 2;
//~^ ERROR invalid left-hand side of assignment
x.lock().unwrap() += 1;
//~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`

let mut y = x.lock().unwrap();
y = 2;
//~^ ERROR mismatched types
y += 1;
//~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
}
58 changes: 58 additions & 0 deletions src/test/ui/typeck/assign-non-lval-derefmut.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
error[E0070]: invalid left-hand side of assignment
--> $DIR/assign-non-lval-derefmut.rs:5:23
|
LL | x.lock().unwrap() = 2;
| ----------------- ^
| |
| cannot assign to this expression
|
help: consider dereferencing here to assign to the mutably borrowed value
|
LL | *x.lock().unwrap() = 2;
| +

error[E0368]: binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
--> $DIR/assign-non-lval-derefmut.rs:7:5
|
LL | x.lock().unwrap() += 1;
| -----------------^^^^^
| |
| cannot use `+=` on type `MutexGuard<'_, usize>`
|
help: `+=` can be used on `usize`, you can dereference `x.lock().unwrap()`
|
LL | *x.lock().unwrap() += 1;
| +

error[E0308]: mismatched types
--> $DIR/assign-non-lval-derefmut.rs:11:9
|
LL | let mut y = x.lock().unwrap();
| ----------------- expected due to this value
LL | y = 2;
| ^ expected struct `MutexGuard`, found integer
|
= note: expected struct `MutexGuard<'_, usize>`
found type `{integer}`
help: consider dereferencing here to assign to the mutably borrowed value
|
LL | *y = 2;
| +

error[E0368]: binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
--> $DIR/assign-non-lval-derefmut.rs:13:5
|
LL | y += 1;
| -^^^^^
| |
| cannot use `+=` on type `MutexGuard<'_, usize>`
|
help: `+=` can be used on `usize`, you can dereference `y`
|
LL | *y += 1;
| +

error: aborting due to 4 previous errors

Some errors have detailed explanations: E0070, E0308, E0368.
For more information about an error, try `rustc --explain E0070`.
15 changes: 15 additions & 0 deletions src/test/ui/typeck/assign-non-lval-mut-ref.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// run-rustfix

fn main() {
let mut x = vec![1usize];
*x.last_mut().unwrap() = 2;
//~^ ERROR invalid left-hand side of assignment
*x.last_mut().unwrap() += 1;
//~^ ERROR binary assignment operation `+=` cannot be applied to type `&mut usize`

let y = x.last_mut().unwrap();
*y = 2;
//~^ ERROR mismatched types
*y += 1;
//~^ ERROR binary assignment operation `+=` cannot be applied to type `&mut usize`
}
15 changes: 15 additions & 0 deletions src/test/ui/typeck/assign-non-lval-mut-ref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// run-rustfix

fn main() {
let mut x = vec![1usize];
x.last_mut().unwrap() = 2;
//~^ ERROR invalid left-hand side of assignment
x.last_mut().unwrap() += 1;
//~^ ERROR binary assignment operation `+=` cannot be applied to type `&mut usize`

let y = x.last_mut().unwrap();
y = 2;
//~^ ERROR mismatched types
y += 1;
//~^ ERROR binary assignment operation `+=` cannot be applied to type `&mut usize`
}
Loading