Skip to content

Commit cb1d8dd

Browse files
authored
Unrolled build for rust-lang#134977
Rollup merge of rust-lang#134977 - estebank:issue-112357, r=BoxyUwU Detect `mut arg: &Ty` meant to be `arg: &mut Ty` and provide structured suggestion When a newcomer attempts to use an "out parameter" using borrows, they sometimes get confused and instead of mutating the borrow they try to mutate the function-local binding instead. This leads to either type errors (due to assigning an owned value to a mutable binding of reference type) or a multitude of lifetime errors and unused binding warnings. This change adds a suggestion to the type error ``` error[E0308]: mismatched types --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:6:14 | LL | fn change_object(mut object: &Object) { | ------- expected due to this parameter type LL | let object2 = Object; LL | object = object2; | ^^^^^^^ expected `&Object`, found `Object` | help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding | LL ~ fn change_object(object: &mut Object) { LL | let object2 = Object; LL ~ *object = object2; | ``` and to the unused assignment lint ``` error: value assigned to `object` is never read --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:11:5 | LL | object = &object2; | ^^^^^^ | note: the lint level is defined here --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:1:9 | LL | #![deny(unused_assignments, unused_variables)] | ^^^^^^^^^^^^^^^^^^ help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding | LL ~ fn change_object2(object: &mut Object) { LL | let object2 = Object; LL ~ *object = object2; | ``` Fix rust-lang#112357.
2 parents 1ab85fb + 4438b32 commit cb1d8dd

7 files changed

+306
-5
lines changed

compiler/rustc_hir_typeck/src/demand.rs

+95
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8585

8686
self.annotate_expected_due_to_let_ty(err, expr, error);
8787
self.annotate_loop_expected_due_to_inference(err, expr, error);
88+
if self.annotate_mut_binding_to_immutable_binding(err, expr, error) {
89+
return;
90+
}
8891

8992
// FIXME(#73154): For now, we do leak check when coercing function
9093
// pointers in typeck, instead of only during borrowck. This can lead
@@ -795,6 +798,98 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
795798
}
796799
}
797800

801+
/// Detect the following case
802+
///
803+
/// ```text
804+
/// fn change_object(mut a: &Ty) {
805+
/// let a = Ty::new();
806+
/// b = a;
807+
/// }
808+
/// ```
809+
///
810+
/// where the user likely meant to modify the value behind there reference, use `a` as an out
811+
/// parameter, instead of mutating the local binding. When encountering this we suggest:
812+
///
813+
/// ```text
814+
/// fn change_object(a: &'_ mut Ty) {
815+
/// let a = Ty::new();
816+
/// *b = a;
817+
/// }
818+
/// ```
819+
fn annotate_mut_binding_to_immutable_binding(
820+
&self,
821+
err: &mut Diag<'_>,
822+
expr: &hir::Expr<'_>,
823+
error: Option<TypeError<'tcx>>,
824+
) -> bool {
825+
if let Some(TypeError::Sorts(ExpectedFound { expected, found })) = error
826+
&& let ty::Ref(_, inner, hir::Mutability::Not) = expected.kind()
827+
828+
// The difference between the expected and found values is one level of borrowing.
829+
&& self.can_eq(self.param_env, *inner, found)
830+
831+
// We have an `ident = expr;` assignment.
832+
&& let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Assign(lhs, rhs, _), .. }) =
833+
self.tcx.parent_hir_node(expr.hir_id)
834+
&& rhs.hir_id == expr.hir_id
835+
836+
// We are assigning to some binding.
837+
&& let hir::ExprKind::Path(hir::QPath::Resolved(
838+
None,
839+
hir::Path { res: hir::def::Res::Local(hir_id), .. },
840+
)) = lhs.kind
841+
&& let hir::Node::Pat(pat) = self.tcx.hir_node(*hir_id)
842+
843+
// The pattern we have is an fn argument.
844+
&& let hir::Node::Param(hir::Param { ty_span, .. }) =
845+
self.tcx.parent_hir_node(pat.hir_id)
846+
&& let item = self.tcx.hir().get_parent_item(pat.hir_id)
847+
&& let item = self.tcx.hir_owner_node(item)
848+
&& let Some(fn_decl) = item.fn_decl()
849+
850+
// We have a mutable binding in the argument.
851+
&& let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = pat.kind
852+
853+
// Look for the type corresponding to the argument pattern we have in the argument list.
854+
&& let Some(ty_sugg) = fn_decl
855+
.inputs
856+
.iter()
857+
.filter_map(|ty| {
858+
if ty.span == *ty_span
859+
&& let hir::TyKind::Ref(lt, x) = ty.kind
860+
{
861+
// `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
862+
Some((
863+
x.ty.span.shrink_to_lo(),
864+
format!(
865+
"{}mut ",
866+
if lt.ident.span.lo() == lt.ident.span.hi() { "" } else { " " }
867+
),
868+
))
869+
} else {
870+
None
871+
}
872+
})
873+
.next()
874+
{
875+
let sugg = vec![
876+
ty_sugg,
877+
(pat.span.until(ident.span), String::new()),
878+
(lhs.span.shrink_to_lo(), "*".to_string()),
879+
];
880+
// We suggest changing the argument from `mut ident: &Ty` to `ident: &'_ mut Ty` and the
881+
// assignment from `ident = val;` to `*ident = val;`.
882+
err.multipart_suggestion_verbose(
883+
"you might have meant to mutate the pointed at value being passed in, instead of \
884+
changing the reference in the local binding",
885+
sugg,
886+
Applicability::MaybeIncorrect,
887+
);
888+
return true;
889+
}
890+
false
891+
}
892+
798893
fn annotate_alternative_method_deref(
799894
&self,
800895
err: &mut Diag<'_>,

compiler/rustc_passes/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,9 @@ passes_unused_assign = value assigned to `{$name}` is never read
799799
passes_unused_assign_passed = value passed to `{$name}` is never read
800800
.help = maybe it is overwritten before being read?
801801
802+
passes_unused_assign_suggestion =
803+
you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding
804+
802805
passes_unused_capture_maybe_capture_ref = value captured by `{$name}` is never read
803806
.help = did you mean to capture by reference instead?
804807

compiler/rustc_passes/src/errors.rs

+18-1
Original file line numberDiff line numberDiff line change
@@ -1787,9 +1787,26 @@ pub(crate) struct IneffectiveUnstableImpl;
17871787

17881788
#[derive(LintDiagnostic)]
17891789
#[diag(passes_unused_assign)]
1790-
#[help]
17911790
pub(crate) struct UnusedAssign {
17921791
pub name: String,
1792+
#[subdiagnostic]
1793+
pub suggestion: Option<UnusedAssignSuggestion>,
1794+
#[help]
1795+
pub help: bool,
1796+
}
1797+
1798+
#[derive(Subdiagnostic)]
1799+
#[multipart_suggestion(passes_unused_assign_suggestion, applicability = "maybe-incorrect")]
1800+
pub(crate) struct UnusedAssignSuggestion {
1801+
pub pre: &'static str,
1802+
#[suggestion_part(code = "{pre}mut ")]
1803+
pub ty_span: Span,
1804+
#[suggestion_part(code = "")]
1805+
pub ty_ref_span: Span,
1806+
#[suggestion_part(code = "*")]
1807+
pub ident_span: Span,
1808+
#[suggestion_part(code = "")]
1809+
pub expr_ref_span: Span,
17931810
}
17941811

17951812
#[derive(LintDiagnostic)]

compiler/rustc_passes/src/liveness.rs

+77-4
Original file line numberDiff line numberDiff line change
@@ -1360,7 +1360,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
13601360
fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
13611361
self.check_unused_vars_in_pat(local.pat, None, None, |spans, hir_id, ln, var| {
13621362
if local.init.is_some() {
1363-
self.warn_about_dead_assign(spans, hir_id, ln, var);
1363+
self.warn_about_dead_assign(spans, hir_id, ln, var, None);
13641364
}
13651365
});
13661366

@@ -1460,7 +1460,8 @@ impl<'tcx> Liveness<'_, 'tcx> {
14601460
// as being used.
14611461
let ln = self.live_node(expr.hir_id, expr.span);
14621462
let var = self.variable(var_hid, expr.span);
1463-
self.warn_about_dead_assign(vec![expr.span], expr.hir_id, ln, var);
1463+
let sugg = self.annotate_mut_binding_to_immutable_binding(var_hid, expr);
1464+
self.warn_about_dead_assign(vec![expr.span], expr.hir_id, ln, var, sugg);
14641465
}
14651466
}
14661467
_ => {
@@ -1585,6 +1586,70 @@ impl<'tcx> Liveness<'_, 'tcx> {
15851586
}
15861587
}
15871588

1589+
/// Detect the following case
1590+
///
1591+
/// ```text
1592+
/// fn change_object(mut a: &Ty) {
1593+
/// let a = Ty::new();
1594+
/// b = &a;
1595+
/// }
1596+
/// ```
1597+
///
1598+
/// where the user likely meant to modify the value behind there reference, use `a` as an out
1599+
/// parameter, instead of mutating the local binding. When encountering this we suggest:
1600+
///
1601+
/// ```text
1602+
/// fn change_object(a: &'_ mut Ty) {
1603+
/// let a = Ty::new();
1604+
/// *b = a;
1605+
/// }
1606+
/// ```
1607+
fn annotate_mut_binding_to_immutable_binding(
1608+
&self,
1609+
var_hid: HirId,
1610+
expr: &'tcx Expr<'tcx>,
1611+
) -> Option<errors::UnusedAssignSuggestion> {
1612+
if let hir::Node::Expr(parent) = self.ir.tcx.parent_hir_node(expr.hir_id)
1613+
&& let hir::ExprKind::Assign(_, rhs, _) = parent.kind
1614+
&& let hir::ExprKind::AddrOf(borrow_kind, _mut, inner) = rhs.kind
1615+
&& let hir::BorrowKind::Ref = borrow_kind
1616+
&& let hir::Node::Pat(pat) = self.ir.tcx.hir_node(var_hid)
1617+
&& let hir::Node::Param(hir::Param { ty_span, .. }) =
1618+
self.ir.tcx.parent_hir_node(pat.hir_id)
1619+
&& let item_id = self.ir.tcx.hir().get_parent_item(pat.hir_id)
1620+
&& let item = self.ir.tcx.hir_owner_node(item_id)
1621+
&& let Some(fn_decl) = item.fn_decl()
1622+
&& let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = pat.kind
1623+
&& let Some((ty_span, pre)) = fn_decl
1624+
.inputs
1625+
.iter()
1626+
.filter_map(|ty| {
1627+
if ty.span == *ty_span
1628+
&& let hir::TyKind::Ref(lt, mut_ty) = ty.kind
1629+
{
1630+
// `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
1631+
Some((
1632+
mut_ty.ty.span.shrink_to_lo(),
1633+
if lt.ident.span.lo() == lt.ident.span.hi() { "" } else { " " },
1634+
))
1635+
} else {
1636+
None
1637+
}
1638+
})
1639+
.next()
1640+
{
1641+
Some(errors::UnusedAssignSuggestion {
1642+
ty_span,
1643+
pre,
1644+
ty_ref_span: pat.span.until(ident.span),
1645+
ident_span: expr.span.shrink_to_lo(),
1646+
expr_ref_span: rhs.span.until(inner.span),
1647+
})
1648+
} else {
1649+
None
1650+
}
1651+
}
1652+
15881653
#[instrument(skip(self), level = "INFO")]
15891654
fn report_unused(
15901655
&self,
@@ -1738,15 +1803,23 @@ impl<'tcx> Liveness<'_, 'tcx> {
17381803
suggs
17391804
}
17401805

1741-
fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) {
1806+
fn warn_about_dead_assign(
1807+
&self,
1808+
spans: Vec<Span>,
1809+
hir_id: HirId,
1810+
ln: LiveNode,
1811+
var: Variable,
1812+
suggestion: Option<errors::UnusedAssignSuggestion>,
1813+
) {
17421814
if !self.live_on_exit(ln, var)
17431815
&& let Some(name) = self.should_warn(var)
17441816
{
1817+
let help = suggestion.is_none();
17451818
self.ir.tcx.emit_node_span_lint(
17461819
lint::builtin::UNUSED_ASSIGNMENTS,
17471820
hir_id,
17481821
spans,
1749-
errors::UnusedAssign { name },
1822+
errors::UnusedAssign { name, suggestion, help },
17501823
);
17511824
}
17521825
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ run-rustfix
2+
#![deny(unused_assignments, unused_variables)]
3+
struct Object;
4+
5+
fn change_object(object: &mut Object) { //~ HELP you might have meant to mutate
6+
let object2 = Object;
7+
*object = object2; //~ ERROR mismatched types
8+
}
9+
10+
fn change_object2(object: &mut Object) { //~ ERROR variable `object` is assigned to, but never used
11+
//~^ HELP you might have meant to mutate
12+
let object2 = Object;
13+
*object = object2;
14+
//~^ ERROR `object2` does not live long enough
15+
//~| ERROR value assigned to `object` is never read
16+
}
17+
18+
fn main() {
19+
let mut object = Object;
20+
change_object(&mut object);
21+
change_object2(&mut object);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ run-rustfix
2+
#![deny(unused_assignments, unused_variables)]
3+
struct Object;
4+
5+
fn change_object(mut object: &Object) { //~ HELP you might have meant to mutate
6+
let object2 = Object;
7+
object = object2; //~ ERROR mismatched types
8+
}
9+
10+
fn change_object2(mut object: &Object) { //~ ERROR variable `object` is assigned to, but never used
11+
//~^ HELP you might have meant to mutate
12+
let object2 = Object;
13+
object = &object2;
14+
//~^ ERROR `object2` does not live long enough
15+
//~| ERROR value assigned to `object` is never read
16+
}
17+
18+
fn main() {
19+
let mut object = Object;
20+
change_object(&mut object);
21+
change_object2(&mut object);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:7:14
3+
|
4+
LL | fn change_object(mut object: &Object) {
5+
| ------- expected due to this parameter type
6+
LL | let object2 = Object;
7+
LL | object = object2;
8+
| ^^^^^^^ expected `&Object`, found `Object`
9+
|
10+
help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding
11+
|
12+
LL ~ fn change_object(object: &mut Object) {
13+
LL | let object2 = Object;
14+
LL ~ *object = object2;
15+
|
16+
17+
error: value assigned to `object` is never read
18+
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:13:5
19+
|
20+
LL | object = &object2;
21+
| ^^^^^^
22+
|
23+
note: the lint level is defined here
24+
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:2:9
25+
|
26+
LL | #![deny(unused_assignments, unused_variables)]
27+
| ^^^^^^^^^^^^^^^^^^
28+
help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding
29+
|
30+
LL ~ fn change_object2(object: &mut Object) {
31+
LL |
32+
LL | let object2 = Object;
33+
LL ~ *object = object2;
34+
|
35+
36+
error: variable `object` is assigned to, but never used
37+
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:10:23
38+
|
39+
LL | fn change_object2(mut object: &Object) {
40+
| ^^^^^^
41+
|
42+
= note: consider using `_object` instead
43+
note: the lint level is defined here
44+
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:2:29
45+
|
46+
LL | #![deny(unused_assignments, unused_variables)]
47+
| ^^^^^^^^^^^^^^^^
48+
49+
error[E0597]: `object2` does not live long enough
50+
--> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:13:14
51+
|
52+
LL | fn change_object2(mut object: &Object) {
53+
| - let's call the lifetime of this reference `'1`
54+
LL |
55+
LL | let object2 = Object;
56+
| ------- binding `object2` declared here
57+
LL | object = &object2;
58+
| ---------^^^^^^^^
59+
| | |
60+
| | borrowed value does not live long enough
61+
| assignment requires that `object2` is borrowed for `'1`
62+
...
63+
LL | }
64+
| - `object2` dropped here while still borrowed
65+
66+
error: aborting due to 4 previous errors
67+
68+
Some errors have detailed explanations: E0308, E0597.
69+
For more information about an error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)