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 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} }
/// Notes the point at which a variable is constrained to some type incompatible /// 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( pub fn note_source_of_type_mismatch_constraint(
&self, &self,
err: &mut Diagnostic, err: &mut Diagnostic,
@ -265,7 +265,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
use rustc_infer::infer::type_variable::*; use rustc_infer::infer::type_variable::*;
use rustc_middle::infer::unify_key::*; use rustc_middle::infer::unify_key::*;
// Replaces all of the variables in the given type with a fresh inference variable.
let mut fudger = BottomUpFolder { let mut fudger = BottomUpFolder {
tcx: self.tcx, tcx: self.tcx,
ty_op: |ty| { ty_op: |ty| {
@ -301,7 +301,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let expected_ty = match source { let expected_ty = match source {
TypeMismatchSource::Ty(expected_ty) => expected_ty, 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 { let hir::ExprKind::MethodCall(segment, _, args, _) = call_expr.kind else {
return false; return false;
}; };
@ -310,6 +316,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}; };
let possible_rcvr_ty = expr_finder.uses.iter().find_map(|binding| { let possible_rcvr_ty = expr_finder.uses.iter().find_map(|binding| {
let possible_rcvr_ty = self.node_ty_opt(binding.hir_id)?; 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 possible_rcvr_ty = possible_rcvr_ty.fold_with(&mut fudger);
let method = self let method = self
.lookup_method( .lookup_method(
@ -321,6 +328,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
args, args,
) )
.ok()?; .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 let _ = self
.at(&ObligationCause::dummy(), self.param_env) .at(&ObligationCause::dummy(), self.param_env)
.eq(DefineOpaqueTypes::No, method.sig.inputs()[idx + 1], arg_ty) .eq(DefineOpaqueTypes::No, method.sig.inputs()[idx + 1], arg_ty)
@ -339,11 +349,16 @@ impl<'a, 'tcx> FnCtxt<'a, '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)) { if !self.can_eq(self.param_env, expected_ty, init_ty.fold_with(&mut fudger)) {
return false; return false;
} }
for window in expr_finder.uses.windows(2) { 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; }; let [binding, next_usage] = *window else { continue; };
// Don't go past the binding (always gonna be a nonsense label if so) // Don't go past the binding (always gonna be a nonsense label if so)
@ -363,6 +378,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&& let hir::ExprKind::MethodCall(segment, rcvr, args, _) = parent_expr.kind && let hir::ExprKind::MethodCall(segment, rcvr, args, _) = parent_expr.kind
&& rcvr.hir_id == binding.hir_id && 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 Some(rcvr_ty) = self.node_ty_opt(rcvr.hir_id) else { continue; };
let rcvr_ty = rcvr_ty.fold_with(&mut fudger); let rcvr_ty = rcvr_ty.fold_with(&mut fudger);
let Ok(method) = let Ok(method) =
@ -371,14 +389,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
continue; continue;
}; };
// NOTE: For future removers of `fudge_inference_if_ok`, you let ideal_rcvr_ty = rcvr_ty.fold_with(&mut fudger);
// can replace this with another call to `lookup_method` but let ideal_method = self
// using `expected_ty` as the rcvr. .lookup_method(ideal_rcvr_ty, segment, DUMMY_SP, parent_expr, rcvr, args)
let ideal_method_sig: Result<_, TypeError<'tcx>> = self.fudge_inference_if_ok(|| { .ok()
let _ = self.at(&ObligationCause::dummy(), self.param_env).eq(rcvr_ty, expected_ty)?; .and_then(|method| {
Ok(method.sig) 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 for (idx, (expected_arg_ty, arg_expr)) in
std::iter::zip(&method.sig.inputs()[1..], args).enumerate() std::iter::zip(&method.sig.inputs()[1..], args).enumerate()
{ {
@ -391,27 +414,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
AllowTwoPhase::No, AllowTwoPhase::No,
None, 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) { if self.can_eq(self.param_env, rcvr_ty, expected_ty) {
continue; continue;
} }
err.span_label( err.span_label(arg_expr.span, format!("this argument has type `{arg_ty}`..."));
arg_expr.span,
format!("this argument has type `{arg_ty}`..."),
);
err.span_label( err.span_label(
binding.span, binding.span,
format!( format!("... which causes `{ident}` to have type `{next_use_ty}`"),
"... which constrains `{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(_)) if matches!(source, TypeMismatchSource::Ty(_))
&& let Ok(ideal_method_sig) = ideal_method_sig && let Some(ideal_method) = ideal_method
{ {
self.emit_type_mismatch_suggestions( self.emit_type_mismatch_suggestions(
err, err,
arg_expr, arg_expr,
arg_ty, arg_ty,
ideal_method_sig.inputs()[idx + 1], self.resolve_vars_if_possible(ideal_method.sig.inputs()[idx + 1]),
None, None,
None, None,
); );
@ -419,7 +448,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return true; return true;
} }
} }
err.span_label( err.span_label(
binding.span, binding.span,
format!("here the type of `{ident}` is inferred to be `{next_use_ty}`"), format!("here the type of `{ident}` is inferred to be `{next_use_ty}`"),
@ -2092,6 +2120,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} }
pub enum TypeMismatchSource<'tcx> { 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>), 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 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.note_source_of_type_mismatch_constraint( self.note_source_of_type_mismatch_constraint(
&mut err, &mut err,
rcvr, 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); let s = S(None);
s.infer(0i32); s.infer(0i32);
//~^ ERROR this method takes 2 arguments but 1 argument was supplied //~^ 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 //~| NOTE an argument is missing
//~| HELP provide the argument //~| HELP provide the argument
//~| HELP change the type of the numeric literal from `i32` to `u32`
let t: S<u32, _> = s; let t: S<u32, _> = s;
//~^ ERROR mismatched types //~^ ERROR mismatched types
//~| NOTE expected `S<u32, _>`, found `S<i32, _>` //~| NOTE expected `S<u32, _>`, found `S<i32, _>`

View File

@ -15,10 +15,12 @@ LL | s.infer(0i32, /* b */);
| ~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/point-at-inference-4.rs:17:24 --> $DIR/point-at-inference-4.rs:19:24
| |
LL | s.infer(0i32); 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; LL | let t: S<u32, _> = s;
| --------- ^ expected `S<u32, _>`, found `S<i32, _>` | --------- ^ expected `S<u32, _>`, found `S<i32, _>`
@ -27,6 +29,10 @@ LL | let t: S<u32, _> = s;
| |
= note: expected struct `S<u32, _>` = note: expected struct `S<u32, _>`
found struct `S<i32, _>` 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 error: aborting due to 2 previous errors