1
Fork 0

Make suggestion to change Fn to FnMut work with methods well

This commit is contained in:
Gurinder Singh 2024-06-10 16:28:05 +05:30
parent d2fb97fcec
commit e47ea38da8
3 changed files with 126 additions and 44 deletions

View file

@ -937,24 +937,50 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
let node = self.infcx.tcx.hir_node(fn_call_id); let node = self.infcx.tcx.hir_node(fn_call_id);
let def_id = hir.enclosing_body_owner(fn_call_id); let def_id = hir.enclosing_body_owner(fn_call_id);
let mut look_at_return = true; let mut look_at_return = true;
// If we can detect the expression to be an `fn` call where the closure was an argument,
// we point at the `fn` definition argument... // If the HIR node is a function or method call gets the def ID
if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(func, args), .. }) = node { // of the called function or method and the span and args of the call expr
let arg_pos = args let get_call_details = || {
let hir::Node::Expr(hir::Expr { hir_id, kind, .. }) = node else {
return None;
};
let typeck_results = self.infcx.tcx.typeck(def_id);
match kind {
hir::ExprKind::Call(expr, args) => {
if let Some(ty::FnDef(def_id, _)) =
typeck_results.node_type_opt(expr.hir_id).as_ref().map(|ty| ty.kind())
{
Some((*def_id, expr.span, *args))
} else {
None
}
}
hir::ExprKind::MethodCall(_, _, args, span) => {
if let Some(def_id) = typeck_results.type_dependent_def_id(*hir_id) {
Some((def_id, *span, *args))
} else {
None
}
}
_ => None,
}
};
// If we can detect the expression to be an function or method call where the closure was an argument,
// we point at the function or method definition argument...
if let Some((callee_def_id, call_span, call_args)) = get_call_details() {
let arg_pos = call_args
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, arg)| arg.hir_id == closure_id) .filter(|(_, arg)| arg.hir_id == closure_id)
.map(|(pos, _)| pos) .map(|(pos, _)| pos)
.next(); .next();
let tables = self.infcx.tcx.typeck(def_id);
if let Some(ty::FnDef(def_id, _)) = let arg = match hir.get_if_local(callee_def_id) {
tables.node_type_opt(func.hir_id).as_ref().map(|ty| ty.kind())
{
let arg = match hir.get_if_local(*def_id) {
Some( Some(
hir::Node::Item(hir::Item { hir::Node::Item(hir::Item { ident, kind: hir::ItemKind::Fn(sig, ..), .. })
ident, kind: hir::ItemKind::Fn(sig, ..), ..
})
| hir::Node::TraitItem(hir::TraitItem { | hir::Node::TraitItem(hir::TraitItem {
ident, ident,
kind: hir::TraitItemKind::Fn(sig, _), kind: hir::TraitItemKind::Fn(sig, _),
@ -983,12 +1009,11 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
}; };
if let Some(span) = arg { if let Some(span) = arg {
err.span_label(span, "change this to accept `FnMut` instead of `Fn`"); err.span_label(span, "change this to accept `FnMut` instead of `Fn`");
err.span_label(func.span, "expects `Fn` instead of `FnMut`"); err.span_label(call_span, "expects `Fn` instead of `FnMut`");
err.span_label(closure_span, "in this closure"); err.span_label(closure_span, "in this closure");
look_at_return = false; look_at_return = false;
} }
} }
}
if look_at_return && hir.get_fn_id_for_return_block(closure_id).is_some() { if look_at_return && hir.get_fn_id_for_return_block(closure_id).is_some() {
// ...otherwise we are probably in the tail expression of the function, point at the // ...otherwise we are probably in the tail expression of the function, point at the

View file

@ -0,0 +1,29 @@
// Regression test for #125325
// Tests that we suggest changing an `impl Fn` param
// to `impl FnMut` when the provided closure arg
// is trying to mutate the closure env.
// Ensures that it works that way for both
// functions and methods
struct S;
impl S {
fn assoc_func(&self, _f: impl Fn()) -> usize {
0
}
}
fn func(_f: impl Fn()) -> usize {
0
}
fn test_func(s: &S) -> usize {
let mut x = ();
s.assoc_func(|| x = ());
//~^ cannot assign to `x`, as it is a captured variable in a `Fn` closure
func(|| x = ())
//~^ cannot assign to `x`, as it is a captured variable in a `Fn` closure
}
fn main() {}

View file

@ -0,0 +1,28 @@
error[E0594]: cannot assign to `x`, as it is a captured variable in a `Fn` closure
--> $DIR/wrong-closure-arg-suggestion-125325.rs:23:21
|
LL | fn assoc_func(&self, _f: impl Fn()) -> usize {
| --------- change this to accept `FnMut` instead of `Fn`
...
LL | s.assoc_func(|| x = ());
| --------------^^^^^^-
| | | |
| | | cannot assign
| | in this closure
| expects `Fn` instead of `FnMut`
error[E0594]: cannot assign to `x`, as it is a captured variable in a `Fn` closure
--> $DIR/wrong-closure-arg-suggestion-125325.rs:25:13
|
LL | fn func(_f: impl Fn()) -> usize {
| --------- change this to accept `FnMut` instead of `Fn`
...
LL | func(|| x = ())
| ---- -- ^^^^^^ cannot assign
| | |
| | in this closure
| expects `Fn` instead of `FnMut`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0594`.