diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index c0332a48be3..9b7f8f80310 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -16,6 +16,7 @@ use rustc_hir as hir; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::Visitor; use rustc_hir::{ExprKind, Node, QPath}; use rustc_hir_analysis::astconv::AstConv; use rustc_hir_analysis::check::intrinsicck::InlineAsmCtxt; @@ -28,7 +29,7 @@ use rustc_infer::infer::{DefineOpaqueTypes, InferOk}; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::visit::TypeVisitableExt; -use rustc_middle::ty::{self, IsSuggestable, Ty}; +use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt}; use rustc_session::Session; use rustc_span::symbol::{kw, Ident}; use rustc_span::{self, sym, BytePos, Span}; @@ -722,6 +723,8 @@ fn has_error_or_infer<'tcx>(tys: impl IntoIterator>) -> bool { &mut err, fn_def_id, callee_ty, + call_expr, + None, Some(mismatch_idx), is_method, ); @@ -826,6 +829,8 @@ fn has_error_or_infer<'tcx>(tys: impl IntoIterator>) -> bool { &mut err, fn_def_id, callee_ty, + call_expr, + Some(expected_ty), Some(expected_idx.as_usize()), is_method, ); @@ -1208,7 +1213,7 @@ enum SuggestionText { } // Call out where the function is defined - self.label_fn_like(&mut err, fn_def_id, callee_ty, None, is_method); + self.label_fn_like(&mut err, fn_def_id, callee_ty, call_expr, None, None, is_method); // And add a suggestion block for all of the parameters let suggestion_text = match suggestion_text { @@ -1899,6 +1904,8 @@ fn label_fn_like( err: &mut Diagnostic, callable_def_id: Option, callee_ty: Option>, + call_expr: &'tcx hir::Expr<'tcx>, + expected_ty: Option>, // A specific argument should be labeled, instead of all of them expected_idx: Option, is_method: bool, @@ -2015,6 +2022,46 @@ fn label_fn_like( let param = expected_idx .and_then(|expected_idx| self.tcx.hir().body(*body).params.get(expected_idx)); let (kind, span) = if let Some(param) = param { + // Try to find earlier invocations of this closure to find if the type mismatch + // is because of inference. If we find one, point at them. + let mut call_finder = FindClosureArg { tcx: self.tcx, calls: vec![] }; + let node = self.tcx + .opt_local_def_id_to_hir_id(self.tcx.hir().get_parent_item(call_expr.hir_id)) + .and_then(|hir_id| self.tcx.hir().find(hir_id)); + match node { + Some(hir::Node::Item(item)) => call_finder.visit_item(item), + Some(hir::Node::TraitItem(item)) => call_finder.visit_trait_item(item), + Some(hir::Node::ImplItem(item)) => call_finder.visit_impl_item(item), + _ => {} + } + let typeck = self.typeck_results.borrow(); + for (rcvr, args) in call_finder.calls { + if let Some(rcvr_ty) = typeck.node_type_opt(rcvr.hir_id) + && let ty::Closure(call_def_id, _) = rcvr_ty.kind() + && def_id == *call_def_id + && let Some(idx) = expected_idx + && let Some(arg) = args.get(idx) + && let Some(arg_ty) = typeck.node_type_opt(arg.hir_id) + && let Some(expected_ty) = expected_ty + && self.can_eq(self.param_env, arg_ty, expected_ty) + { + let mut sp: MultiSpan = vec![arg.span].into(); + sp.push_span_label( + arg.span, + format!("expected because this argument is of type `{arg_ty}`"), + ); + sp.push_span_label(rcvr.span, "in this closure call"); + err.span_note( + sp, + format!( + "expected because the closure was earlier called with an \ + argument of type `{arg_ty}`", + ), + ); + break; + } + } + ("closure parameter", param.span) } else { ("closure", self.tcx.def_span(def_id)) @@ -2028,3 +2075,23 @@ fn label_fn_like( } } } + +struct FindClosureArg<'tcx> { + tcx: TyCtxt<'tcx>, + calls: Vec<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, +} + +impl<'tcx> Visitor<'tcx> for FindClosureArg<'tcx> { + type NestedFilter = rustc_middle::hir::nested_filter::All; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Call(rcvr, args) = ex.kind { + self.calls.push((rcvr, args)); + } + hir::intravisit::walk_expr(self, ex); + } +} diff --git a/tests/ui/unboxed-closures/unboxed-closures-type-mismatch.rs b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch.rs index 9f76849e5fb..0999f61b01a 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-type-mismatch.rs +++ b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch.rs @@ -1,7 +1,35 @@ use std::ops::FnMut; -pub fn main() { +fn main() { let mut f = |x: isize, y: isize| -> isize { x + y }; - let z = f(1_usize, 2); //~ ERROR mismatched types + let z = f(1_usize, 2); //~ ERROR mismatched types + println!("{}", z); + let mut g = |x, y| { x + y }; + let y = g(1_i32, 2); + let z = g(1_usize, 2); //~ ERROR mismatched types println!("{}", z); } + +trait T { + fn bar(&self) { + let identity = |x| x; + identity(1u8); + identity(1u16); //~ ERROR mismatched types + let identity = |x| x; + identity(&1u8); + identity(&1u16); //~ ERROR mismatched types + } +} + +struct S; + +impl T for S { + fn bar(&self) { + let identity = |x| x; + identity(1u8); + identity(1u16); //~ ERROR mismatched types + let identity = |x| x; + identity(&1u8); + identity(&1u16); //~ ERROR mismatched types + } +} diff --git a/tests/ui/unboxed-closures/unboxed-closures-type-mismatch.stderr b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch.stderr index 455f83f5721..327df50e645 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-type-mismatch.stderr +++ b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch.stderr @@ -16,6 +16,127 @@ help: change the type of the numeric literal from `usize` to `isize` LL | let z = f(1_isize, 2); | ~~~~~ -error: aborting due to previous error +error[E0308]: mismatched types + --> $DIR/unboxed-closures-type-mismatch.rs:9:15 + | +LL | let z = g(1_usize, 2); + | - ^^^^^^^ expected `i32`, found `usize` + | | + | arguments to this function are incorrect + | +note: expected because the closure was earlier called with an argument of type `i32` + --> $DIR/unboxed-closures-type-mismatch.rs:8:15 + | +LL | let y = g(1_i32, 2); + | - ^^^^^ expected because this argument is of type `i32` + | | + | in this closure call +note: closure parameter defined here + --> $DIR/unboxed-closures-type-mismatch.rs:7:18 + | +LL | let mut g = |x, y| { x + y }; + | ^ +help: change the type of the numeric literal from `usize` to `i32` + | +LL | let z = g(1_i32, 2); + | ~~~ + +error[E0308]: mismatched types + --> $DIR/unboxed-closures-type-mismatch.rs:17:18 + | +LL | identity(1u16); + | -------- ^^^^ expected `u8`, found `u16` + | | + | arguments to this function are incorrect + | +note: expected because the closure was earlier called with an argument of type `u8` + --> $DIR/unboxed-closures-type-mismatch.rs:16:18 + | +LL | identity(1u8); + | -------- ^^^ expected because this argument is of type `u8` + | | + | in this closure call +note: closure parameter defined here + --> $DIR/unboxed-closures-type-mismatch.rs:15:25 + | +LL | let identity = |x| x; + | ^ +help: change the type of the numeric literal from `u16` to `u8` + | +LL | identity(1u8); + | ~~ + +error[E0308]: mismatched types + --> $DIR/unboxed-closures-type-mismatch.rs:20:18 + | +LL | identity(&1u16); + | -------- ^^^^^ expected `&u8`, found `&u16` + | | + | arguments to this function are incorrect + | + = note: expected reference `&u8` + found reference `&u16` +note: expected because the closure was earlier called with an argument of type `&u8` + --> $DIR/unboxed-closures-type-mismatch.rs:19:18 + | +LL | identity(&1u8); + | -------- ^^^^ expected because this argument is of type `&u8` + | | + | in this closure call +note: closure parameter defined here + --> $DIR/unboxed-closures-type-mismatch.rs:18:25 + | +LL | let identity = |x| x; + | ^ + +error[E0308]: mismatched types + --> $DIR/unboxed-closures-type-mismatch.rs:30:18 + | +LL | identity(1u16); + | -------- ^^^^ expected `u8`, found `u16` + | | + | arguments to this function are incorrect + | +note: expected because the closure was earlier called with an argument of type `u8` + --> $DIR/unboxed-closures-type-mismatch.rs:29:18 + | +LL | identity(1u8); + | -------- ^^^ expected because this argument is of type `u8` + | | + | in this closure call +note: closure parameter defined here + --> $DIR/unboxed-closures-type-mismatch.rs:28:25 + | +LL | let identity = |x| x; + | ^ +help: change the type of the numeric literal from `u16` to `u8` + | +LL | identity(1u8); + | ~~ + +error[E0308]: mismatched types + --> $DIR/unboxed-closures-type-mismatch.rs:33:18 + | +LL | identity(&1u16); + | -------- ^^^^^ expected `&u8`, found `&u16` + | | + | arguments to this function are incorrect + | + = note: expected reference `&u8` + found reference `&u16` +note: expected because the closure was earlier called with an argument of type `&u8` + --> $DIR/unboxed-closures-type-mismatch.rs:32:18 + | +LL | identity(&1u8); + | -------- ^^^^ expected because this argument is of type `&u8` + | | + | in this closure call +note: closure parameter defined here + --> $DIR/unboxed-closures-type-mismatch.rs:31:25 + | +LL | let identity = |x| x; + | ^ + +error: aborting due to 6 previous errors For more information about this error, try `rustc --explain E0308`.