Skip to content
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
60 changes: 60 additions & 0 deletions compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,12 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
);
return;
}

// Do not suggest changing type if that is not under user control.
if self.is_closure_arg_with_non_locally_decided_type(local) {
return;
}

let decl_span = local_decl.source_info.span;

let (amp_mut_sugg, local_var_ty_info) = match *local_decl.local_info() {
Expand Down Expand Up @@ -1500,6 +1506,60 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
Applicability::HasPlaceholders,
);
}

/// Returns `true` if `local` is an argument in a closure passed to a
/// function defined in another crate.
///
/// For example, in the following code this function returns `true` for `x`
/// since `Option::inspect()` is not defined in the current crate:
///
/// ```text
/// some_option.as_mut().inspect(|x| {
/// ```
fn is_closure_arg_with_non_locally_decided_type(&self, local: Local) -> bool {
// We don't care about regular local variables, only args.
if self.body.local_kind(local) != LocalKind::Arg {
return false;
}

// Make sure we are inside a closure.
let InstanceKind::Item(body_def_id) = self.body.source.instance else {
return false;
};
let Some(Node::Expr(hir::Expr { hir_id: body_hir_id, kind, .. })) =
self.infcx.tcx.hir_get_if_local(body_def_id)
else {
return false;
};
let ExprKind::Closure(hir::Closure { kind: hir::ClosureKind::Closure, .. }) = kind else {
return false;
};

// Check if the method/function that our closure is passed to is defined
// in another crate.
let Node::Expr(closure_parent) = self.infcx.tcx.parent_hir_node(*body_hir_id) else {
return false;
};
match closure_parent.kind {
ExprKind::MethodCall(method, _, _, _) => self
.infcx
.tcx
.typeck(method.hir_id.owner.def_id)
.type_dependent_def_id(closure_parent.hir_id)
.is_some_and(|def_id| !def_id.is_local()),
ExprKind::Call(func, _) => self
.infcx
.tcx
.typeck(func.hir_id.owner.def_id)
.node_type_opt(func.hir_id)
.and_then(|ty| match ty.kind() {
ty::FnDef(def_id, _) => Some(def_id),
_ => None,
})
.is_some_and(|def_id| !def_id.is_local()),
_ => false,
}
}
}

struct BindingFinder {
Expand Down
4 changes: 1 addition & 3 deletions tests/ui/borrowck/issue-115259-suggest-iter-mut.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ error[E0596]: cannot borrow `**layer` as mutable, as it is behind a `&` referenc
--> $DIR/issue-115259-suggest-iter-mut.rs:15:65
|
LL | self.layers.iter().fold(0, |result, mut layer| result + layer.process())
| --------- ^^^^^ `layer` is a `&` reference, so it cannot be borrowed as mutable
| |
| consider changing this binding's type to be: `&mut Box<dyn Layer>`
| ^^^^^ `layer` is a `&` reference, so it cannot be borrowed as mutable
|
help: you may want to use `iter_mut` here
|
Expand Down
4 changes: 1 addition & 3 deletions tests/ui/borrowck/issue-62387-suggest-iter-mut-2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ error[E0596]: cannot borrow `*container` as mutable, as it is behind a `&` refer
--> $DIR/issue-62387-suggest-iter-mut-2.rs:30:45
|
LL | vec.iter().flat_map(|container| container.things()).cloned().collect::<Vec<PathBuf>>();
| --------- ^^^^^^^^^ `container` is a `&` reference, so it cannot be borrowed as mutable
| |
| consider changing this binding's type to be: `&mut Container`
| ^^^^^^^^^ `container` is a `&` reference, so it cannot be borrowed as mutable
|
help: you may want to use `iter_mut` here
|
Expand Down
8 changes: 2 additions & 6 deletions tests/ui/borrowck/issue-62387-suggest-iter-mut.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ error[E0596]: cannot borrow `*a` as mutable, as it is behind a `&` reference
--> $DIR/issue-62387-suggest-iter-mut.rs:18:27
|
LL | v.iter().for_each(|a| a.double());
| - ^ `a` is a `&` reference, so it cannot be borrowed as mutable
| |
| consider changing this binding's type to be: `&mut A`
| ^ `a` is a `&` reference, so it cannot be borrowed as mutable
|
help: you may want to use `iter_mut` here
|
Expand All @@ -15,9 +13,7 @@ error[E0596]: cannot borrow `*a` as mutable, as it is behind a `&` reference
--> $DIR/issue-62387-suggest-iter-mut.rs:25:39
|
LL | v.iter().rev().rev().for_each(|a| a.double());
| - ^ `a` is a `&` reference, so it cannot be borrowed as mutable
| |
| consider changing this binding's type to be: `&mut A`
| ^ `a` is a `&` reference, so it cannot be borrowed as mutable
|
help: you may want to use `iter_mut` here
|
Expand Down
18 changes: 18 additions & 0 deletions tests/ui/borrowck/option-inspect-mutation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//! Regression test for <https://github.com/rust-lang/rust/issues/128381>.

struct Struct {
field: u32,
}

fn main() {
let mut some_struct = Some(Struct { field: 42 });
some_struct.as_mut().inspect(|some_struct| {
some_struct.field *= 10; //~ ERROR cannot assign to `some_struct.field`, which is behind a `&` reference
// Users can't change type of `some_struct` param, so above error must not suggest it.
});

// Same check as above but using `hir::ExprKind::Call` instead of `hir::ExprKind::MethodCall`.
Option::inspect(some_struct.as_mut(), |some_struct| {
some_struct.field *= 20; //~ ERROR cannot assign to `some_struct.field`, which is behind a `&` reference
});
}
15 changes: 15 additions & 0 deletions tests/ui/borrowck/option-inspect-mutation.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error[E0594]: cannot assign to `some_struct.field`, which is behind a `&` reference
--> $DIR/option-inspect-mutation.rs:10:9
|
LL | some_struct.field *= 10;
| ^^^^^^^^^^^^^^^^^^^^^^^ `some_struct` is a `&` reference, so it cannot be written to

error[E0594]: cannot assign to `some_struct.field`, which is behind a `&` reference
--> $DIR/option-inspect-mutation.rs:16:9
|
LL | some_struct.field *= 20;
| ^^^^^^^^^^^^^^^^^^^^^^^ `some_struct` is a `&` reference, so it cannot be written to

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0594`.
4 changes: 1 addition & 3 deletions tests/ui/borrowck/suggest-as-ref-on-mut-closure.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ error[E0596]: cannot borrow `*cb` as mutable, as it is behind a `&` reference
--> $DIR/suggest-as-ref-on-mut-closure.rs:12:26
|
LL | cb.as_ref().map(|cb| cb());
| -- ^^ `cb` is a `&` reference, so it cannot be borrowed as mutable
| |
| consider changing this binding's type to be: `&mut &mut dyn FnMut()`
| ^^ `cb` is a `&` reference, so it cannot be borrowed as mutable

error: aborting due to 2 previous errors

Expand Down
Loading