Skip to content

Commit

Permalink
Rollup merge of #106665 - JulianKnodt:better_fn_trait_note, r=cjgillot
Browse files Browse the repository at this point in the history
Add note when `FnPtr` vs. `FnDef` impl trait

I encountered an instance where an `FnPtr` implemented a trait, but I was passing an `FnDef`. I was confused for an hour and to examine the source code of the trait's crate's tests in order to understand how to cast it properly (it didn't help that it was behind a reference). To the end user, it might not be immediately obvious that they are different and how to convert from an `FnDef` to an `FnPtr`, but it is necessary to cast to the generic function in order to compile. It is thus useful to suggest `as` in the help note, (even if the `Fn` output implements the trait).
  • Loading branch information
matthiaskrgr authored Jan 14, 2023
2 parents bc4049d + 2de9d67 commit 3fa9be9
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 17 deletions.
54 changes: 38 additions & 16 deletions compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
})
}
}

impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
fn report_fulfillment_errors(
&self,
Expand Down Expand Up @@ -852,6 +853,29 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
let mut suggested =
self.suggest_dereferences(&obligation, &mut err, trait_predicate);
suggested |= self.suggest_fn_call(&obligation, &mut err, trait_predicate);
let impl_candidates = self.find_similar_impl_candidates(trait_predicate);
suggested = if let &[cand] = &impl_candidates[..] {
let cand = cand.trait_ref;
if let (ty::FnPtr(_), ty::FnDef(..)) =
(cand.self_ty().kind(), trait_ref.self_ty().skip_binder().kind())
{
err.span_suggestion(
span.shrink_to_hi(),
format!(
"the trait `{}` is implemented for fn pointer `{}`, try casting using `as`",
cand.print_only_trait_path(),
cand.self_ty(),
),
format!(" as {}", cand.self_ty()),
Applicability::MaybeIncorrect,
);
true
} else {
false
}
} else {
false
} || suggested;
suggested |=
self.suggest_remove_reference(&obligation, &mut err, trait_predicate);
suggested |= self.suggest_semicolon_removal(
Expand Down Expand Up @@ -1968,27 +1992,25 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
candidates.sort();
candidates.dedup();
let len = candidates.len();
if candidates.len() == 0 {
if candidates.is_empty() {
return false;
}
if candidates.len() == 1 {
let ty_desc = match candidates[0].self_ty().kind() {
ty::FnPtr(_) => Some("fn pointer"),
_ => None,
};
let the_desc = match ty_desc {
Some(desc) => format!(" implemented for {} `", desc),
None => " implemented for `".to_string(),
};
if let &[cand] = &candidates[..] {
let (desc, mention_castable) =
match (cand.self_ty().kind(), trait_ref.self_ty().skip_binder().kind()) {
(ty::FnPtr(_), ty::FnDef(..)) => {
(" implemented for fn pointer `", ", cast using `as`")
}
(ty::FnPtr(_), _) => (" implemented for fn pointer `", ""),
_ => (" implemented for `", ""),
};
err.highlighted_help(vec![
(
format!("the trait `{}` ", candidates[0].print_only_trait_path()),
Style::NoStyle,
),
(format!("the trait `{}` ", cand.print_only_trait_path()), Style::NoStyle),
("is".to_string(), Style::Highlight),
(the_desc, Style::NoStyle),
(candidates[0].self_ty().to_string(), Style::Highlight),
(desc.to_string(), Style::NoStyle),
(cand.self_ty().to_string(), Style::Highlight),
("`".to_string(), Style::NoStyle),
(mention_castable.to_string(), Style::NoStyle),
]);
return true;
}
Expand Down
26 changes: 26 additions & 0 deletions tests/ui/traits/fn-trait-cast-diagnostic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// There are two different instances to check that even if
// the trait is implemented for the output of a function,
// it will still be displayed if the function itself implements a trait.
trait Foo {}

impl Foo for fn() -> bool {}
impl Foo for bool {}

fn example() -> bool {
true
}

trait NoOtherFoo {}

impl NoOtherFoo for fn() -> bool {}

fn do_on_foo(v: impl Foo) {}
fn do_on_single_foo(v: impl NoOtherFoo) {}

fn main() {
do_on_foo(example);
//~^ ERROR the trait bound

do_on_single_foo(example);
//~^ ERROR the trait bound
}
43 changes: 43 additions & 0 deletions tests/ui/traits/fn-trait-cast-diagnostic.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
error[E0277]: the trait bound `fn() -> bool {example}: Foo` is not satisfied
--> $DIR/fn-trait-cast-diagnostic.rs:21:15
|
LL | do_on_foo(example);
| --------- ^^^^^^^ the trait `Foo` is not implemented for fn item `fn() -> bool {example}`
| |
| required by a bound introduced by this call
|
note: required by a bound in `do_on_foo`
--> $DIR/fn-trait-cast-diagnostic.rs:17:22
|
LL | fn do_on_foo(v: impl Foo) {}
| ^^^ required by this bound in `do_on_foo`
help: use parentheses to call this function
|
LL | do_on_foo(example());
| ++
help: the trait `Foo` is implemented for fn pointer `fn() -> bool`, try casting using `as`
|
LL | do_on_foo(example as fn() -> bool);
| +++++++++++++++

error[E0277]: the trait bound `fn() -> bool {example}: NoOtherFoo` is not satisfied
--> $DIR/fn-trait-cast-diagnostic.rs:24:22
|
LL | do_on_single_foo(example);
| ---------------- ^^^^^^^ the trait `NoOtherFoo` is not implemented for fn item `fn() -> bool {example}`
| |
| required by a bound introduced by this call
|
note: required by a bound in `do_on_single_foo`
--> $DIR/fn-trait-cast-diagnostic.rs:18:29
|
LL | fn do_on_single_foo(v: impl NoOtherFoo) {}
| ^^^^^^^^^^ required by this bound in `do_on_single_foo`
help: the trait `NoOtherFoo` is implemented for fn pointer `fn() -> bool`, try casting using `as`
|
LL | do_on_single_foo(example as fn() -> bool);
| +++++++++++++++

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
5 changes: 4 additions & 1 deletion tests/ui/traits/issue-99875.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ LL | takes(function);
| |
| required by a bound introduced by this call
|
= help: the trait `Trait` is implemented for fn pointer `fn(Argument) -> Return`
note: required by a bound in `takes`
--> $DIR/issue-99875.rs:9:18
|
LL | fn takes(_: impl Trait) {}
| ^^^^^ required by this bound in `takes`
help: the trait `Trait` is implemented for fn pointer `fn(Argument) -> Return`, try casting using `as`
|
LL | takes(function as fn(Argument) -> Return);
| +++++++++++++++++++++++++

error[E0277]: the trait bound `[closure@$DIR/issue-99875.rs:14:11: 14:34]: Trait` is not satisfied
--> $DIR/issue-99875.rs:14:11
Expand Down

0 comments on commit 3fa9be9

Please sign in to comment.