Skip to content

Commit e5d6e11

Browse files
committed
On Fn arg mismatch for a fn path, suggest a closure
When encountering a fn call that has a path to another fn being passed in, where an `Fn` impl is expected, and the arguments differ, suggest wrapping the argument with a closure with the appropriate arguments.
1 parent d8dbf7c commit e5d6e11

15 files changed

+249
-54
lines changed

compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

Lines changed: 169 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,15 @@ pub trait TypeErrCtxtExt<'tcx> {
222222
param_env: ty::ParamEnv<'tcx>,
223223
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>;
224224

225+
fn note_conflicting_fn_args(
226+
&self,
227+
err: &mut Diagnostic,
228+
cause: &ObligationCauseCode<'tcx>,
229+
expected: Ty<'tcx>,
230+
found: Ty<'tcx>,
231+
param_env: ty::ParamEnv<'tcx>,
232+
);
233+
225234
fn note_conflicting_closure_bounds(
226235
&self,
227236
cause: &ObligationCauseCode<'tcx>,
@@ -2006,6 +2015,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
20062015
let signature_kind = format!("{argument_kind} signature");
20072016
err.note_expected_found(&signature_kind, expected_str, &signature_kind, found_str);
20082017

2018+
self.note_conflicting_fn_args(&mut err, cause, expected, found, param_env);
20092019
self.note_conflicting_closure_bounds(cause, &mut err);
20102020

20112021
if let Some(found_node) = found_node {
@@ -2015,6 +2025,154 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
20152025
err
20162026
}
20172027

2028+
fn note_conflicting_fn_args(
2029+
&self,
2030+
err: &mut Diagnostic,
2031+
cause: &ObligationCauseCode<'tcx>,
2032+
expected: Ty<'tcx>,
2033+
found: Ty<'tcx>,
2034+
param_env: ty::ParamEnv<'tcx>,
2035+
) {
2036+
let ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, .. } = cause else {
2037+
return;
2038+
};
2039+
let ty::FnPtr(expected) = expected.kind() else {
2040+
return;
2041+
};
2042+
let ty::FnPtr(found) = found.kind() else {
2043+
return;
2044+
};
2045+
let Some(Node::Expr(arg)) = self.tcx.hir().find(*arg_hir_id) else {
2046+
return;
2047+
};
2048+
let hir::ExprKind::Path(path) = arg.kind else {
2049+
return;
2050+
};
2051+
let expected_inputs = self.tcx.erase_late_bound_regions(*expected).inputs();
2052+
let found_inputs = self.tcx.erase_late_bound_regions(*found).inputs();
2053+
let both_tys = expected_inputs.iter().cloned().zip(found_inputs.iter().cloned());
2054+
2055+
let arg_expr = |infcx: &InferCtxt<'tcx>, name, expected: Ty<'tcx>, found: Ty<'tcx>| {
2056+
let (expected_ty, expected_refs) = get_deref_type_and_refs(expected);
2057+
let (found_ty, found_refs) = get_deref_type_and_refs(found);
2058+
2059+
if infcx.can_eq(param_env, found_ty, expected_ty) {
2060+
if found_refs.len() == expected_refs.len()
2061+
&& found_refs.iter().zip(expected_refs.iter()).all(|(e, f)| e == f)
2062+
{
2063+
name
2064+
} else if found_refs.len() > expected_refs.len() {
2065+
if found_refs[..found_refs.len() - expected_refs.len()]
2066+
.iter()
2067+
.zip(expected_refs.iter())
2068+
.any(|(e, f)| e != f)
2069+
{
2070+
// The refs have different mutability.
2071+
format!(
2072+
"{}*{name}",
2073+
found_refs[..found_refs.len() - expected_refs.len()]
2074+
.iter()
2075+
.map(|mutbl| format!("&{}", mutbl.prefix_str()))
2076+
.collect::<Vec<_>>()
2077+
.join(""),
2078+
)
2079+
} else {
2080+
format!(
2081+
"{}{name}",
2082+
found_refs[..found_refs.len() - expected_refs.len()]
2083+
.iter()
2084+
.map(|mutbl| format!("&{}", mutbl.prefix_str()))
2085+
.collect::<Vec<_>>()
2086+
.join(""),
2087+
)
2088+
}
2089+
} else if expected_refs.len() > found_refs.len() {
2090+
format!(
2091+
"{}{name}",
2092+
(0..(expected_refs.len() - found_refs.len()))
2093+
.map(|_| "*")
2094+
.collect::<Vec<_>>()
2095+
.join(""),
2096+
)
2097+
} else {
2098+
format!(
2099+
"{}{name}",
2100+
found_refs
2101+
.iter()
2102+
.map(|mutbl| format!("&{}", mutbl.prefix_str()))
2103+
.chain(found_refs.iter().map(|_| "*".to_string()))
2104+
.collect::<Vec<_>>()
2105+
.join(""),
2106+
)
2107+
}
2108+
} else {
2109+
format!("/* {found} */")
2110+
}
2111+
};
2112+
let (closure_names, call_names): (Vec<_>, Vec<_>) = if both_tys
2113+
.clone()
2114+
.all(|(expected, found)| {
2115+
let (expected_ty, _) = get_deref_type_and_refs(expected);
2116+
let (found_ty, _) = get_deref_type_and_refs(found);
2117+
self.can_eq(param_env, found_ty, expected_ty)
2118+
})
2119+
&& !expected_inputs.is_empty()
2120+
&& expected_inputs.len() == found_inputs.len()
2121+
&& let hir::QPath::Resolved(_, path) = path
2122+
&& let hir::def::Res::Def(_, fn_def_id) = path.res
2123+
&& let Some(node) = self.tcx.hir().get_if_local(fn_def_id)
2124+
&& let Some(body_id) = node.body_id()
2125+
{
2126+
let closure = self
2127+
.tcx
2128+
.hir()
2129+
.body_param_names(body_id)
2130+
.map(|name| format!("{name}"))
2131+
.collect();
2132+
let args = self
2133+
.tcx
2134+
.hir()
2135+
.body_param_names(body_id)
2136+
.zip(both_tys)
2137+
.map(|(name, (expected, found))| {
2138+
arg_expr(self.infcx, format!("{name}"), expected, found)
2139+
})
2140+
.collect();
2141+
(closure, args)
2142+
} else {
2143+
let closure_args = expected_inputs
2144+
.iter()
2145+
.enumerate()
2146+
.map(|(i, _)| format!("arg{i}"))
2147+
.collect::<Vec<_>>();
2148+
let call_args = both_tys
2149+
.enumerate()
2150+
.map(|(i, (expected, found))| {
2151+
arg_expr(self.infcx, format!("arg{i}"), expected, found)
2152+
})
2153+
.collect::<Vec<_>>();
2154+
(closure_args, call_args)
2155+
};
2156+
let closure_names: Vec<_> = closure_names
2157+
.into_iter()
2158+
.zip(expected_inputs.iter())
2159+
.map(|(name, ty)| {
2160+
format!(
2161+
"{name}{}",
2162+
if ty.has_infer_types() { String::new() } else { format!(": {ty}") }
2163+
)
2164+
})
2165+
.collect();
2166+
err.multipart_suggestion(
2167+
format!("consider wrapping the function in a closure"),
2168+
vec![
2169+
(arg.span.shrink_to_lo(), format!("|{}| ", closure_names.join(", "))),
2170+
(arg.span.shrink_to_hi(), format!("({})", call_names.join(", "))),
2171+
],
2172+
Applicability::MaybeIncorrect,
2173+
);
2174+
}
2175+
20182176
// Add a note if there are two `Fn`-family bounds that have conflicting argument
20192177
// requirements, which will always cause a closure to have a type error.
20202178
fn note_conflicting_closure_bounds(
@@ -4342,17 +4500,6 @@ fn hint_missing_borrow<'tcx>(
43424500

43434501
let args = fn_decl.inputs.iter();
43444502

4345-
fn get_deref_type_and_refs(mut ty: Ty<'_>) -> (Ty<'_>, Vec<hir::Mutability>) {
4346-
let mut refs = vec![];
4347-
4348-
while let ty::Ref(_, new_ty, mutbl) = ty.kind() {
4349-
ty = *new_ty;
4350-
refs.push(*mutbl);
4351-
}
4352-
4353-
(ty, refs)
4354-
}
4355-
43564503
let mut to_borrow = Vec::new();
43574504
let mut remove_borrow = Vec::new();
43584505

@@ -4645,3 +4792,14 @@ pub fn suggest_desugaring_async_fn_to_impl_future_in_trait<'tcx>(
46454792

46464793
Some(sugg)
46474794
}
4795+
4796+
fn get_deref_type_and_refs(mut ty: Ty<'_>) -> (Ty<'_>, Vec<hir::Mutability>) {
4797+
let mut refs = vec![];
4798+
4799+
while let ty::Ref(_, new_ty, mutbl) = ty.kind() {
4800+
ty = *new_ty;
4801+
refs.push(*mutbl);
4802+
}
4803+
4804+
(ty, refs)
4805+
}

tests/ui/generic-associated-types/bugs/issue-88382.stderr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ note: required by a bound in `do_something`
1616
|
1717
LL | fn do_something<I: Iterable>(i: I, mut f: impl for<'a> Fn(&mut I::Iterator<'a>)) {
1818
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `do_something`
19+
help: consider wrapping the function in a closure
20+
|
21+
LL | do_something(SomeImplementation(), |arg0: &mut std::iter::Empty<usize>| test(/* &mut <_ as Iterable>::Iterator<'_> */));
22+
| ++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++
1923

2024
error: aborting due to previous error
2125

tests/ui/intrinsics/const-eval-select-bad.stderr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ LL | const_eval_select((true,), foo, baz);
8686
found function signature `fn(i32) -> _`
8787
note: required by a bound in `const_eval_select`
8888
--> $SRC_DIR/core/src/intrinsics.rs:LL:COL
89+
help: consider wrapping the function in a closure
90+
|
91+
LL | const_eval_select((true,), |arg0: bool| foo(/* i32 */), baz);
92+
| ++++++++++++ +++++++++++
8993

9094
error: this argument must be a `const fn`
9195
--> $DIR/const-eval-select-bad.rs:42:29

tests/ui/mismatched_types/E0631.stderr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ note: required by a bound in `foo`
4848
|
4949
LL | fn foo<F: Fn(usize)>(_: F) {}
5050
| ^^^^^^^^^ required by this bound in `foo`
51+
help: consider wrapping the function in a closure
52+
|
53+
LL | foo(|arg0: usize| f(/* u64 */));
54+
| +++++++++++++ +++++++++++
5155

5256
error[E0631]: type mismatch in function arguments
5357
--> $DIR/E0631.rs:10:9
@@ -67,6 +71,10 @@ note: required by a bound in `bar`
6771
|
6872
LL | fn bar<F: Fn<(usize,)>>(_: F) {}
6973
| ^^^^^^^^^^^^ required by this bound in `bar`
74+
help: consider wrapping the function in a closure
75+
|
76+
LL | bar(|arg0: usize| f(/* u64 */));
77+
| +++++++++++++ +++++++++++
7078

7179
error: aborting due to 4 previous errors
7280

tests/ui/mismatched_types/closure-ref-114180.stderr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ LL | v.sort_by(compare);
1212
found closure signature `fn((_,), (_,)) -> _`
1313
note: required by a bound in `slice::<impl [T]>::sort_by`
1414
--> $SRC_DIR/alloc/src/slice.rs:LL:COL
15+
help: consider wrapping the function in a closure
16+
|
17+
LL | v.sort_by(|arg0, arg1| compare(*arg0, *arg1));
18+
| ++++++++++++ ++++++++++++++
1519
help: consider adjusting the signature so it borrows its arguments
1620
|
1721
LL | let compare = |&(a,), &(e,)| todo!();

tests/ui/mismatched_types/fn-variance-1.stderr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ note: required by a bound in `apply`
1616
|
1717
LL | fn apply<T, F>(t: T, f: F) where F: FnOnce(T) {
1818
| ^^^^^^^^^ required by this bound in `apply`
19+
help: consider wrapping the function in a closure
20+
|
21+
LL | apply(&3, |x| takes_mut(&mut *x));
22+
| +++ +++++++++
1923

2024
error[E0631]: type mismatch in function arguments
2125
--> $DIR/fn-variance-1.rs:15:19
@@ -35,6 +39,10 @@ note: required by a bound in `apply`
3539
|
3640
LL | fn apply<T, F>(t: T, f: F) where F: FnOnce(T) {
3741
| ^^^^^^^^^ required by this bound in `apply`
42+
help: consider wrapping the function in a closure
43+
|
44+
LL | apply(&mut 3, |x| takes_imm(&*x));
45+
| +++ +++++
3846

3947
error: aborting due to 2 previous errors
4048

tests/ui/mismatched_types/suggest-option-asderef-inference-var.stderr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ LL | let _has_inference_vars: Option<i32> = Some(0).map(deref_int);
1313
found function signature `for<'a> fn(&'a i32) -> _`
1414
note: required by a bound in `Option::<T>::map`
1515
--> $SRC_DIR/core/src/option.rs:LL:COL
16+
help: consider wrapping the function in a closure
17+
|
18+
LL | let _has_inference_vars: Option<i32> = Some(0).map(|a| deref_int(&a));
19+
| +++ ++++
1620
help: consider adjusting the signature so it does not borrow its argument
1721
|
1822
LL - fn deref_int(a: &i32) -> i32 {

tests/ui/mismatched_types/suggest-option-asderef-unfixable.stderr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ LL | let _ = produces_string().and_then(takes_str_but_too_many_refs);
1313
found function signature `for<'a, 'b> fn(&'a &'b str) -> _`
1414
note: required by a bound in `Option::<T>::and_then`
1515
--> $SRC_DIR/core/src/option.rs:LL:COL
16+
help: consider wrapping the function in a closure
17+
|
18+
LL | let _ = produces_string().and_then(|arg0: String| takes_str_but_too_many_refs(/* &&str */));
19+
| ++++++++++++++ +++++++++++++
1620

1721
error[E0277]: expected a `FnOnce(String)` closure, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}`
1822
--> $DIR/suggest-option-asderef-unfixable.rs:26:40
@@ -68,6 +72,10 @@ LL | let _ = Some(TypeWithoutDeref).and_then(takes_str_but_too_many_refs);
6872
found function signature `for<'a, 'b> fn(&'a &'b str) -> _`
6973
note: required by a bound in `Option::<T>::and_then`
7074
--> $SRC_DIR/core/src/option.rs:LL:COL
75+
help: consider wrapping the function in a closure
76+
|
77+
LL | let _ = Some(TypeWithoutDeref).and_then(|arg0: TypeWithoutDeref| takes_str_but_too_many_refs(/* &&str */));
78+
| ++++++++++++++++++++++++ +++++++++++++
7179

7280
error: aborting due to 5 previous errors
7381

tests/ui/mismatched_types/suggest-option-asderef.fixed

Lines changed: 0 additions & 39 deletions
This file was deleted.

tests/ui/mismatched_types/suggest-option-asderef.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// run-rustfix
1+
// this isn't auto-fixable now because we produce two similar suggestions
22

33
fn produces_string() -> Option<String> {
44
Some("my cool string".to_owned())
@@ -25,15 +25,19 @@ fn main() {
2525
let _: Option<()> = produces_string().and_then(takes_str);
2626
//~^ ERROR type mismatch in function arguments
2727
//~| HELP call `Option::as_deref()` first
28+
//~| HELP consider wrapping the function in a closure
2829
let _: Option<Option<()>> = produces_string().map(takes_str);
2930
//~^ ERROR type mismatch in function arguments
3031
//~| HELP call `Option::as_deref()` first
32+
//~| HELP consider wrapping the function in a closure
3133
let _: Option<Option<()>> = produces_string().map(takes_str_mut);
3234
//~^ ERROR type mismatch in function arguments
3335
//~| HELP call `Option::as_deref_mut()` first
36+
//~| HELP consider wrapping the function in a closure
3437
let _ = produces_string().and_then(generic);
3538

3639
let _ = produces_string().and_then(generic_ref);
3740
//~^ ERROR type mismatch in function arguments
3841
//~| HELP call `Option::as_deref()` first
42+
//~| HELP consider wrapping the function in a closure
3943
}

0 commit comments

Comments
 (0)