Rollup merge of #116250 - estebank:closure-arg-inference-span, r=petrochenkov

On type error of closure call argument, point at earlier calls that affected inference

Mitigate part of  https://github.com/rust-lang/rust/issues/71209.

When we encounter a type error on a specific argument of a closure call argument, where the closure's definition doesn't have a type specified, look for other calls of the closure to try and find the specific call that cased that argument to be inferred of the expected type.

```
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);
   |                   ~~
```
This commit is contained in:
Guillaume Gomez 2023-10-10 18:44:44 +02:00 committed by GitHub
commit 0e5e04b89a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 221 additions and 5 deletions

View File

@ -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<Item = Ty<'tcx>>) -> 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<Item = Ty<'tcx>>) -> 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<DefId>,
callee_ty: Option<Ty<'tcx>>,
call_expr: &'tcx hir::Expr<'tcx>,
expected_ty: Option<Ty<'tcx>>,
// A specific argument should be labeled, instead of all of them
expected_idx: Option<usize>,
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);
}
}

View File

@ -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
}
}

View File

@ -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`.