More accurate argument blames, add some comments
This commit is contained in:
parent
5a71029dd3
commit
5cc4757421
@ -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 },
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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, _>`
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user