Skip to content

Commit

Permalink
Add suggestions for function pointers
Browse files Browse the repository at this point in the history
- On compiler-error's suggestion of moving this lower down the stack,
along the path of `report_mismatched_types()`, which is used
by `rustc_hir_analysis` and `rustc_hir_typeck`.
- update ui tests, add test
- add suggestions for references to fn pointers
- modify `TypeErrCtxt::same_type_modulo_infer` to take `T: relate::Relate` instead of `Ty`
  • Loading branch information
mattjperez committed Jan 24, 2023
1 parent c8e6a9e commit 1e22280
Show file tree
Hide file tree
Showing 14 changed files with 273 additions and 96 deletions.
2 changes: 2 additions & 0 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1613,12 +1613,14 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
if visitor.ret_exprs.len() > 0 && let Some(expr) = expression {
self.note_unreachable_loop_return(&mut err, &expr, &visitor.ret_exprs);
}

let reported = err.emit_unless(unsized_return);

self.final_ty = Some(fcx.tcx.ty_error_with_guaranteed(reported));
}
}
}

fn note_unreachable_loop_return(
&self,
err: &mut Diagnostic,
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_hir_typeck/src/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.annotate_expected_due_to_let_ty(err, expr, error);
self.emit_type_mismatch_suggestions(err, expr, expr_ty, expected, expected_ty_expr, error);
self.note_type_is_not_clone(err, expected, expr_ty, expr);
self.note_need_for_fn_pointer(err, expected, expr_ty);
self.note_internal_mutation_in_method(err, expr, expected, expr_ty);
self.check_for_range_as_method_call(err, expr, expr_ty, expected);
self.check_for_binding_assigned_block_without_tail_expression(err, expr, expr_ty, expected);
Expand Down
37 changes: 0 additions & 37 deletions compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -926,43 +926,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}

pub(in super::super) fn note_need_for_fn_pointer(
&self,
err: &mut Diagnostic,
expected: Ty<'tcx>,
found: Ty<'tcx>,
) {
let (sig, did, substs) = match (&expected.kind(), &found.kind()) {
(ty::FnDef(did1, substs1), ty::FnDef(did2, substs2)) => {
let sig1 = self.tcx.bound_fn_sig(*did1).subst(self.tcx, substs1);
let sig2 = self.tcx.bound_fn_sig(*did2).subst(self.tcx, substs2);
if sig1 != sig2 {
return;
}
err.note(
"different `fn` items always have unique types, even if their signatures are \
the same",
);
(sig1, *did1, substs1)
}
(ty::FnDef(did, substs), ty::FnPtr(sig2)) => {
let sig1 = self.tcx.bound_fn_sig(*did).subst(self.tcx, substs);
if sig1 != *sig2 {
return;
}
(sig1, *did, substs)
}
_ => return,
};
err.help(&format!("change the expected type to be function pointer `{}`", sig));
err.help(&format!(
"if the expected type is due to type inference, cast the expected `fn` to a function \
pointer: `{} as {}`",
self.tcx.def_path_str_with_substs(did, substs),
sig
));
}

// Instantiates the given path, which must refer to an item with the given
// number of type parameters and type.
#[instrument(skip(self, span), level = "debug")]
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_infer/src/infer/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1841,6 +1841,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
self.suggest_as_ref_where_appropriate(span, &exp_found, diag);
self.suggest_accessing_field_where_appropriate(cause, &exp_found, diag);
self.suggest_await_on_expect_found(cause, span, &exp_found, diag);
self.suggest_function_pointers(cause, span, &exp_found, diag);
}
}

Expand Down Expand Up @@ -2585,7 +2586,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
/// with the other type. A TyVar inference type is compatible with any type, and an IntVar or
/// FloatVar inference type are compatible with themselves or their concrete types (Int and
/// Float types, respectively). When comparing two ADTs, these rules apply recursively.
pub fn same_type_modulo_infer(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
pub fn same_type_modulo_infer<T: relate::Relate<'tcx>>(&self, a: T, b: T) -> bool {
let (a, b) = self.resolve_vars_if_possible((a, b));
SameTypeModuloInfer(self).relate(a, b).is_ok()
}
Expand Down
78 changes: 77 additions & 1 deletion compiler/rustc_infer/src/infer/error_reporting/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rustc_middle::traits::{
StatementAsExpression,
};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self as ty, Ty, TypeVisitable};
use rustc_middle::ty::{self as ty, IsSuggestable, Ty, TypeVisitable};
use rustc_span::{sym, BytePos, Span};

use crate::errors::SuggAddLetForLetChains;
Expand Down Expand Up @@ -351,6 +351,82 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
}
}

pub(super) fn suggest_function_pointers(
&self,
cause: &ObligationCause<'tcx>,
span: Span,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
debug!("suggest_function_pointers(cause={:?}, exp_found={:?})", cause, exp_found);
let ty::error::ExpectedFound { expected, found } = exp_found;
let expected_inner = expected.peel_refs();
let found_inner = found.peel_refs();
if !expected_inner.is_fn() || !found_inner.is_fn() {
return;
}
match (&expected_inner.kind(), &found_inner.kind()) {
(ty::FnPtr(sig), ty::FnDef(did, substs)) => {
let expected_sig = &(self.normalize_fn_sig)(*sig);
let found_sig =
&(self.normalize_fn_sig)(self.tcx.bound_fn_sig(*did).subst(self.tcx, substs));

let fn_name = self.tcx.def_path_str_with_substs(*did, substs);

if !self.same_type_modulo_infer(*found_sig, *expected_sig)
|| !sig.is_suggestable(self.tcx, true)
|| ty::util::is_intrinsic(self.tcx, *did)
{
return;
}

let (msg, sugg) = match (expected.is_ref(), found.is_ref()) {
(true, false) => {
let msg = "consider using a reference";
let sug = format!("&{fn_name}");
(msg, sug)
}
(false, true) => {
let msg = "consider removing the reference";
let sug = format!("{fn_name}");
(msg, sug)
}
(true, true) => {
diag.note("fn items are distinct from fn pointers");
let msg = "consider casting to a fn pointer";
let sug = format!("&({fn_name} as {sig})");
(msg, sug)
}
(false, false) => {
diag.note("fn items are distinct from fn pointers");
let msg = "consider casting to a fn pointer";
let sug = format!("{fn_name} as {sig}");
(msg, sug)
}
};
diag.span_suggestion(span, msg, &sugg, Applicability::MaybeIncorrect);
}
(ty::FnDef(did1, substs1), ty::FnDef(did2, substs2)) => {
let expected_sig =
&(self.normalize_fn_sig)(self.tcx.bound_fn_sig(*did1).subst(self.tcx, substs1));
let found_sig =
&(self.normalize_fn_sig)(self.tcx.bound_fn_sig(*did2).subst(self.tcx, substs2));

if self.same_type_modulo_infer(*found_sig, *expected_sig) {
diag.note(
"different fn items have unique types, even if their signatures are the same",
);
}
}
(ty::FnDef(_, _), ty::FnPtr(_)) => {
diag.note("fn items are distinct from fn pointers");
}
_ => {
return;
}
};
}

pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) =
(expected.kind(), found.kind())
Expand Down
1 change: 1 addition & 0 deletions tests/ui/fn/fn-compare-mismatch.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ LL | let x = f == g;
|
= note: expected fn item `fn() {f}`
found fn item `fn() {g}`
= note: different fn items have unique types, even if their signatures are the same

error: aborting due to 2 previous errors

Expand Down
37 changes: 18 additions & 19 deletions tests/ui/fn/fn-item-type.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,52 @@
// Test that the types of distinct fn items are not compatible by
// default. See also `run-pass/fn-item-type-*.rs`.

fn foo<T>(x: isize) -> isize { x * 2 }
fn bar<T>(x: isize) -> isize { x * 4 }
fn foo<T>(x: isize) -> isize {
x * 2
}
fn bar<T>(x: isize) -> isize {
x * 4
}

fn eq<T>(x: T, y: T) { }
fn eq<T>(x: T, y: T) {}

trait Foo { fn foo() { /* this is a default fn */ } }
impl<T> Foo for T { /* `foo` is still default here */ }
trait Foo {
fn foo() { /* this is a default fn */
}
}
impl<T> Foo for T {
/* `foo` is still default here */
}

fn main() {
eq(foo::<u8>, bar::<u8>);
//~^ ERROR mismatched types
//~| expected fn item `fn(_) -> _ {foo::<u8>}`
//~| found fn item `fn(_) -> _ {bar::<u8>}`
//~| expected fn item, found a different fn item
//~| different `fn` items always have unique types, even if their signatures are the same
//~| change the expected type to be function pointer
//~| if the expected type is due to type inference, cast the expected `fn` to a function pointer
//~| different fn items have unique types, even if their signatures are the same

eq(foo::<u8>, foo::<i8>);
//~^ ERROR mismatched types
//~| expected `u8`, found `i8`
//~| different `fn` items always have unique types, even if their signatures are the same
//~| change the expected type to be function pointer
//~| if the expected type is due to type inference, cast the expected `fn` to a function pointer
//~| different fn items have unique types, even if their signatures are the same

eq(bar::<String>, bar::<Vec<u8>>);
//~^ ERROR mismatched types
//~| found fn item `fn(_) -> _ {bar::<Vec<u8>>}`
//~| expected struct `String`, found struct `Vec`
//~| different `fn` items always have unique types, even if their signatures are the same
//~| change the expected type to be function pointer
//~| if the expected type is due to type inference, cast the expected `fn` to a function pointer
//~| different fn items have unique types, even if their signatures are the same

// Make sure we distinguish between trait methods correctly.
eq(<u8 as Foo>::foo, <u16 as Foo>::foo);
//~^ ERROR mismatched types
//~| expected `u8`, found `u16`
//~| different `fn` items always have unique types, even if their signatures are the same
//~| change the expected type to be function pointer
//~| if the expected type is due to type inference, cast the expected `fn` to a function pointer
//~| different fn items have unique types, even if their signatures are the same

eq(foo::<u8>, bar::<u8> as fn(isize) -> isize);
//~^ ERROR mismatched types
//~| found fn pointer `fn(_) -> _`
//~| expected fn item, found fn pointer
//~| change the expected type to be function pointer
//~| if the expected type is due to type inference, cast the expected `fn` to a function pointer

eq(foo::<u8> as fn(isize) -> isize, bar::<u8>); // ok!
}
49 changes: 20 additions & 29 deletions tests/ui/fn/fn-item-type.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0308]: mismatched types
--> $DIR/fn-item-type.rs:13:19
--> $DIR/fn-item-type.rs:22:19
|
LL | eq(foo::<u8>, bar::<u8>);
| -- ^^^^^^^^^ expected fn item, found a different fn item
Expand All @@ -8,17 +8,15 @@ LL | eq(foo::<u8>, bar::<u8>);
|
= note: expected fn item `fn(_) -> _ {foo::<u8>}`
found fn item `fn(_) -> _ {bar::<u8>}`
= note: different `fn` items always have unique types, even if their signatures are the same
= help: change the expected type to be function pointer `fn(isize) -> isize`
= help: if the expected type is due to type inference, cast the expected `fn` to a function pointer: `foo::<u8> as fn(isize) -> isize`
= note: different fn items have unique types, even if their signatures are the same
note: function defined here
--> $DIR/fn-item-type.rs:7:4
--> $DIR/fn-item-type.rs:11:4
|
LL | fn eq<T>(x: T, y: T) { }
LL | fn eq<T>(x: T, y: T) {}
| ^^ ----

error[E0308]: mismatched types
--> $DIR/fn-item-type.rs:22:19
--> $DIR/fn-item-type.rs:29:19
|
LL | eq(foo::<u8>, foo::<i8>);
| -- ^^^^^^^^^ expected `u8`, found `i8`
Expand All @@ -27,17 +25,15 @@ LL | eq(foo::<u8>, foo::<i8>);
|
= note: expected fn item `fn(_) -> _ {foo::<u8>}`
found fn item `fn(_) -> _ {foo::<i8>}`
= note: different `fn` items always have unique types, even if their signatures are the same
= help: change the expected type to be function pointer `fn(isize) -> isize`
= help: if the expected type is due to type inference, cast the expected `fn` to a function pointer: `foo::<u8> as fn(isize) -> isize`
= note: different fn items have unique types, even if their signatures are the same
note: function defined here
--> $DIR/fn-item-type.rs:7:4
--> $DIR/fn-item-type.rs:11:4
|
LL | fn eq<T>(x: T, y: T) { }
LL | fn eq<T>(x: T, y: T) {}
| ^^ ----

error[E0308]: mismatched types
--> $DIR/fn-item-type.rs:29:23
--> $DIR/fn-item-type.rs:34:23
|
LL | eq(bar::<String>, bar::<Vec<u8>>);
| -- ^^^^^^^^^^^^^^ expected struct `String`, found struct `Vec`
Expand All @@ -46,17 +42,15 @@ LL | eq(bar::<String>, bar::<Vec<u8>>);
|
= note: expected fn item `fn(_) -> _ {bar::<String>}`
found fn item `fn(_) -> _ {bar::<Vec<u8>>}`
= note: different `fn` items always have unique types, even if their signatures are the same
= help: change the expected type to be function pointer `fn(isize) -> isize`
= help: if the expected type is due to type inference, cast the expected `fn` to a function pointer: `bar::<String> as fn(isize) -> isize`
= note: different fn items have unique types, even if their signatures are the same
note: function defined here
--> $DIR/fn-item-type.rs:7:4
--> $DIR/fn-item-type.rs:11:4
|
LL | fn eq<T>(x: T, y: T) { }
LL | fn eq<T>(x: T, y: T) {}
| ^^ ----

error[E0308]: mismatched types
--> $DIR/fn-item-type.rs:38:26
--> $DIR/fn-item-type.rs:41:26
|
LL | eq(<u8 as Foo>::foo, <u16 as Foo>::foo);
| -- ^^^^^^^^^^^^^^^^^ expected `u8`, found `u16`
Expand All @@ -65,17 +59,15 @@ LL | eq(<u8 as Foo>::foo, <u16 as Foo>::foo);
|
= note: expected fn item `fn() {<u8 as Foo>::foo}`
found fn item `fn() {<u16 as Foo>::foo}`
= note: different `fn` items always have unique types, even if their signatures are the same
= help: change the expected type to be function pointer `fn()`
= help: if the expected type is due to type inference, cast the expected `fn` to a function pointer: `<u8 as Foo>::foo as fn()`
= note: different fn items have unique types, even if their signatures are the same
note: function defined here
--> $DIR/fn-item-type.rs:7:4
--> $DIR/fn-item-type.rs:11:4
|
LL | fn eq<T>(x: T, y: T) { }
LL | fn eq<T>(x: T, y: T) {}
| ^^ ----

error[E0308]: mismatched types
--> $DIR/fn-item-type.rs:45:19
--> $DIR/fn-item-type.rs:46:19
|
LL | eq(foo::<u8>, bar::<u8> as fn(isize) -> isize);
| -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn item, found fn pointer
Expand All @@ -84,12 +76,11 @@ LL | eq(foo::<u8>, bar::<u8> as fn(isize) -> isize);
|
= note: expected fn item `fn(_) -> _ {foo::<u8>}`
found fn pointer `fn(_) -> _`
= help: change the expected type to be function pointer `fn(isize) -> isize`
= help: if the expected type is due to type inference, cast the expected `fn` to a function pointer: `foo::<u8> as fn(isize) -> isize`
= note: fn items are distinct from fn pointers
note: function defined here
--> $DIR/fn-item-type.rs:7:4
--> $DIR/fn-item-type.rs:11:4
|
LL | fn eq<T>(x: T, y: T) { }
LL | fn eq<T>(x: T, y: T) {}
| ^^ ----

error: aborting due to 5 previous errors
Expand Down
Loading

0 comments on commit 1e22280

Please sign in to comment.