More accurate argument blames, add some comments

This commit is contained in:
Michael Goulet 2023-03-03 01:35:51 +00:00
parent 5a71029dd3
commit 5cc4757421
4 changed files with 69 additions and 25 deletions

View File

@ -221,7 +221,7 @@ pub fn demand_coerce_diag(
}
/// Notes the point at which a variable is constrained to some type incompatible
/// with `expected_ty`.
/// with some expectation given by `source`.
pub fn note_source_of_type_mismatch_constraint(
&self,
err: &mut Diagnostic,
@ -265,7 +265,7 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
use rustc_infer::infer::type_variable::*;
use rustc_middle::infer::unify_key::*;
// Replaces all of the variables in the given type with a fresh inference variable.
let mut fudger = BottomUpFolder {
tcx: self.tcx,
ty_op: |ty| {
@ -301,7 +301,13 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
let expected_ty = match source {
TypeMismatchSource::Ty(expected_ty) => expected_ty,
TypeMismatchSource::Arg(call_expr, idx) => {
// Try to deduce what the possible value of `expr` would be if the
// incompatible arg were compatible. For example, given `Vec<i32>`
// and `vec.push(1u32)`, we ideally want to deduce that the type of
// `vec` *should* have been `Vec<u32>`. This will allow us to then
// run the subsequent code with this expectation, finding out exactly
// when this type diverged from our expectation.
TypeMismatchSource::Arg { call_expr, incompatible_arg: idx } => {
let hir::ExprKind::MethodCall(segment, _, args, _) = call_expr.kind else {
return false;
};
@ -310,6 +316,7 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
};
let possible_rcvr_ty = expr_finder.uses.iter().find_map(|binding| {
let possible_rcvr_ty = self.node_ty_opt(binding.hir_id)?;
// Fudge the receiver, so we can do new inference on it.
let possible_rcvr_ty = possible_rcvr_ty.fold_with(&mut fudger);
let method = self
.lookup_method(
@ -321,6 +328,9 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
args,
)
.ok()?;
// Unify the method signature with our incompatible arg, to
// do inference in the *opposite* direction and to find out
// what our ideal rcvr ty would look like.
let _ = self
.at(&ObligationCause::dummy(), self.param_env)
.eq(DefineOpaqueTypes::No, method.sig.inputs()[idx + 1], arg_ty)
@ -339,11 +349,16 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
}
};
// If our expected_ty does not equal init_ty, then it *began* as incompatible.
// No need to note in this case...
if !self.can_eq(self.param_env, expected_ty, init_ty.fold_with(&mut fudger)) {
return false;
}
for window in expr_finder.uses.windows(2) {
// Bindings always update their recorded type after the fact, so we
// need to look at the *following* usage's type to see when the
// binding became incompatible.
let [binding, next_usage] = *window else { continue; };
// Don't go past the binding (always gonna be a nonsense label if so)
@ -363,6 +378,9 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
&& let hir::ExprKind::MethodCall(segment, rcvr, args, _) = parent_expr.kind
&& rcvr.hir_id == binding.hir_id
{
// If our binding became incompatible while it was a receiver
// to a method call, we may be able to make a better guess to
// the source of a type mismatch.
let Some(rcvr_ty) = self.node_ty_opt(rcvr.hir_id) else { continue; };
let rcvr_ty = rcvr_ty.fold_with(&mut fudger);
let Ok(method) =
@ -371,14 +389,19 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
continue;
};
// NOTE: For future removers of `fudge_inference_if_ok`, you
// can replace this with another call to `lookup_method` but
// using `expected_ty` as the rcvr.
let ideal_method_sig: Result<_, TypeError<'tcx>> = self.fudge_inference_if_ok(|| {
let _ = self.at(&ObligationCause::dummy(), self.param_env).eq(rcvr_ty, expected_ty)?;
Ok(method.sig)
let ideal_rcvr_ty = rcvr_ty.fold_with(&mut fudger);
let ideal_method = self
.lookup_method(ideal_rcvr_ty, segment, DUMMY_SP, parent_expr, rcvr, args)
.ok()
.and_then(|method| {
let _ = self.at(&ObligationCause::dummy(), self.param_env)
.eq(DefineOpaqueTypes::No, ideal_rcvr_ty, expected_ty)
.ok()?;
Some(method)
});
// Find what argument caused our rcvr to become incompatible
// with the expected ty.
for (idx, (expected_arg_ty, arg_expr)) in
std::iter::zip(&method.sig.inputs()[1..], args).enumerate()
{
@ -391,27 +414,33 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
AllowTwoPhase::No,
None,
);
self.select_obligations_where_possible(|errs| {
// Yeet the errors, we're already reporting errors.
errs.clear();
});
// If our rcvr, after inference due to unifying the signature
// with the expected argument type, is still compatible with
// the rcvr, then it must've not been the source of blame.
if self.can_eq(self.param_env, rcvr_ty, expected_ty) {
continue;
}
err.span_label(
arg_expr.span,
format!("this argument has type `{arg_ty}`..."),
);
err.span_label(arg_expr.span, format!("this argument has type `{arg_ty}`..."));
err.span_label(
binding.span,
format!(
"... which constrains `{ident}` to have type `{next_use_ty}`"
),
format!("... which causes `{ident}` to have type `{next_use_ty}`"),
);
// Using our "ideal" method signature, suggest a fix to this
// blame arg, if possible. Don't do this if we're coming from
// arg mismatch code, because we'll possibly suggest a mutually
// incompatible fix at the original mismatch site.
if matches!(source, TypeMismatchSource::Ty(_))
&& let Ok(ideal_method_sig) = ideal_method_sig
&& let Some(ideal_method) = ideal_method
{
self.emit_type_mismatch_suggestions(
err,
arg_expr,
arg_ty,
ideal_method_sig.inputs()[idx + 1],
self.resolve_vars_if_possible(ideal_method.sig.inputs()[idx + 1]),
None,
None,
);
@ -419,7 +448,6 @@ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
return true;
}
}
err.span_label(
binding.span,
format!("here the type of `{ident}` is inferred to be `{next_use_ty}`"),
@ -2092,6 +2120,11 @@ enum CallableKind {
}
pub enum TypeMismatchSource<'tcx> {
/// Expected the binding to have the given type, but it was found to have
/// a different type. Find out when that type first became incompatible.
Ty(Ty<'tcx>),
Arg(&'tcx hir::Expr<'tcx>, usize),
/// When we fail during method argument checking, try to find out if a previous
/// expression has constrained the method's receiver in a way that makes the
/// argument's type incompatible.
Arg { call_expr: &'tcx hir::Expr<'tcx>, incompatible_arg: usize },
}

View File

@ -814,7 +814,10 @@ fn has_error_or_infer<'tcx>(tys: impl IntoIterator<Item = Ty<'tcx>>) -> bool {
self.note_source_of_type_mismatch_constraint(
&mut err,
rcvr,
crate::demand::TypeMismatchSource::Arg(call_expr, provided_idx.as_usize()),
crate::demand::TypeMismatchSource::Arg {
call_expr,
incompatible_arg: provided_idx.as_usize(),
},
);
}

View File

@ -11,9 +11,11 @@ fn main() {
let s = S(None);
s.infer(0i32);
//~^ ERROR this method takes 2 arguments but 1 argument was supplied
//~| NOTE here the type of `s` is inferred to be `S<i32, _>`
//~| NOTE this argument has type `i32`...
//~| NOTE ... which causes `s` to have type `S<i32, _>`
//~| NOTE an argument is missing
//~| HELP provide the argument
//~| HELP change the type of the numeric literal from `i32` to `u32`
let t: S<u32, _> = s;
//~^ ERROR mismatched types
//~| NOTE expected `S<u32, _>`, found `S<i32, _>`

View File

@ -15,10 +15,12 @@ LL | s.infer(0i32, /* b */);
| ~~~~~~~~~~~~~~~
error[E0308]: mismatched types
--> $DIR/point-at-inference-4.rs:17:24
--> $DIR/point-at-inference-4.rs:19:24
|
LL | s.infer(0i32);
| - here the type of `s` is inferred to be `S<i32, _>`
| - ---- this argument has type `i32`...
| |
| ... which causes `s` to have type `S<i32, _>`
...
LL | let t: S<u32, _> = s;
| --------- ^ expected `S<u32, _>`, found `S<i32, _>`
@ -27,6 +29,10 @@ LL | let t: S<u32, _> = s;
|
= note: expected struct `S<u32, _>`
found struct `S<i32, _>`
help: change the type of the numeric literal from `i32` to `u32`
|
LL | s.infer(0u32);
| ~~~
error: aborting due to 2 previous errors