Skip to content

Commit 5cc4757

Browse files
More accurate argument blames, add some comments
1 parent 5a71029 commit 5cc4757

File tree

4 files changed

+69
-25
lines changed

4 files changed

+69
-25
lines changed

compiler/rustc_hir_typeck/src/demand.rs

+54-21
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
221221
}
222222

223223
/// Notes the point at which a variable is constrained to some type incompatible
224-
/// with `expected_ty`.
224+
/// with some expectation given by `source`.
225225
pub fn note_source_of_type_mismatch_constraint(
226226
&self,
227227
err: &mut Diagnostic,
@@ -265,7 +265,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
265265

266266
use rustc_infer::infer::type_variable::*;
267267
use rustc_middle::infer::unify_key::*;
268-
268+
// Replaces all of the variables in the given type with a fresh inference variable.
269269
let mut fudger = BottomUpFolder {
270270
tcx: self.tcx,
271271
ty_op: |ty| {
@@ -301,7 +301,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
301301

302302
let expected_ty = match source {
303303
TypeMismatchSource::Ty(expected_ty) => expected_ty,
304-
TypeMismatchSource::Arg(call_expr, idx) => {
304+
// Try to deduce what the possible value of `expr` would be if the
305+
// incompatible arg were compatible. For example, given `Vec<i32>`
306+
// and `vec.push(1u32)`, we ideally want to deduce that the type of
307+
// `vec` *should* have been `Vec<u32>`. This will allow us to then
308+
// run the subsequent code with this expectation, finding out exactly
309+
// when this type diverged from our expectation.
310+
TypeMismatchSource::Arg { call_expr, incompatible_arg: idx } => {
305311
let hir::ExprKind::MethodCall(segment, _, args, _) = call_expr.kind else {
306312
return false;
307313
};
@@ -310,6 +316,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
310316
};
311317
let possible_rcvr_ty = expr_finder.uses.iter().find_map(|binding| {
312318
let possible_rcvr_ty = self.node_ty_opt(binding.hir_id)?;
319+
// Fudge the receiver, so we can do new inference on it.
313320
let possible_rcvr_ty = possible_rcvr_ty.fold_with(&mut fudger);
314321
let method = self
315322
.lookup_method(
@@ -321,6 +328,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
321328
args,
322329
)
323330
.ok()?;
331+
// Unify the method signature with our incompatible arg, to
332+
// do inference in the *opposite* direction and to find out
333+
// what our ideal rcvr ty would look like.
324334
let _ = self
325335
.at(&ObligationCause::dummy(), self.param_env)
326336
.eq(DefineOpaqueTypes::No, method.sig.inputs()[idx + 1], arg_ty)
@@ -339,11 +349,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
339349
}
340350
};
341351

352+
// If our expected_ty does not equal init_ty, then it *began* as incompatible.
353+
// No need to note in this case...
342354
if !self.can_eq(self.param_env, expected_ty, init_ty.fold_with(&mut fudger)) {
343355
return false;
344356
}
345357

346358
for window in expr_finder.uses.windows(2) {
359+
// Bindings always update their recorded type after the fact, so we
360+
// need to look at the *following* usage's type to see when the
361+
// binding became incompatible.
347362
let [binding, next_usage] = *window else { continue; };
348363

349364
// Don't go past the binding (always gonna be a nonsense label if so)
@@ -363,6 +378,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
363378
&& let hir::ExprKind::MethodCall(segment, rcvr, args, _) = parent_expr.kind
364379
&& rcvr.hir_id == binding.hir_id
365380
{
381+
// If our binding became incompatible while it was a receiver
382+
// to a method call, we may be able to make a better guess to
383+
// the source of a type mismatch.
366384
let Some(rcvr_ty) = self.node_ty_opt(rcvr.hir_id) else { continue; };
367385
let rcvr_ty = rcvr_ty.fold_with(&mut fudger);
368386
let Ok(method) =
@@ -371,14 +389,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
371389
continue;
372390
};
373391

374-
// NOTE: For future removers of `fudge_inference_if_ok`, you
375-
// can replace this with another call to `lookup_method` but
376-
// using `expected_ty` as the rcvr.
377-
let ideal_method_sig: Result<_, TypeError<'tcx>> = self.fudge_inference_if_ok(|| {
378-
let _ = self.at(&ObligationCause::dummy(), self.param_env).eq(rcvr_ty, expected_ty)?;
379-
Ok(method.sig)
380-
});
392+
let ideal_rcvr_ty = rcvr_ty.fold_with(&mut fudger);
393+
let ideal_method = self
394+
.lookup_method(ideal_rcvr_ty, segment, DUMMY_SP, parent_expr, rcvr, args)
395+
.ok()
396+
.and_then(|method| {
397+
let _ = self.at(&ObligationCause::dummy(), self.param_env)
398+
.eq(DefineOpaqueTypes::No, ideal_rcvr_ty, expected_ty)
399+
.ok()?;
400+
Some(method)
401+
});
381402

403+
// Find what argument caused our rcvr to become incompatible
404+
// with the expected ty.
382405
for (idx, (expected_arg_ty, arg_expr)) in
383406
std::iter::zip(&method.sig.inputs()[1..], args).enumerate()
384407
{
@@ -391,35 +414,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
391414
AllowTwoPhase::No,
392415
None,
393416
);
417+
self.select_obligations_where_possible(|errs| {
418+
// Yeet the errors, we're already reporting errors.
419+
errs.clear();
420+
});
421+
// If our rcvr, after inference due to unifying the signature
422+
// with the expected argument type, is still compatible with
423+
// the rcvr, then it must've not been the source of blame.
394424
if self.can_eq(self.param_env, rcvr_ty, expected_ty) {
395425
continue;
396426
}
397-
err.span_label(
398-
arg_expr.span,
399-
format!("this argument has type `{arg_ty}`..."),
400-
);
427+
err.span_label(arg_expr.span, format!("this argument has type `{arg_ty}`..."));
401428
err.span_label(
402429
binding.span,
403-
format!(
404-
"... which constrains `{ident}` to have type `{next_use_ty}`"
405-
),
430+
format!("... which causes `{ident}` to have type `{next_use_ty}`"),
406431
);
432+
// Using our "ideal" method signature, suggest a fix to this
433+
// blame arg, if possible. Don't do this if we're coming from
434+
// arg mismatch code, because we'll possibly suggest a mutually
435+
// incompatible fix at the original mismatch site.
407436
if matches!(source, TypeMismatchSource::Ty(_))
408-
&& let Ok(ideal_method_sig) = ideal_method_sig
437+
&& let Some(ideal_method) = ideal_method
409438
{
410439
self.emit_type_mismatch_suggestions(
411440
err,
412441
arg_expr,
413442
arg_ty,
414-
ideal_method_sig.inputs()[idx + 1],
443+
self.resolve_vars_if_possible(ideal_method.sig.inputs()[idx + 1]),
415444
None,
416445
None,
417446
);
418447
}
419448
return true;
420449
}
421450
}
422-
423451
err.span_label(
424452
binding.span,
425453
format!("here the type of `{ident}` is inferred to be `{next_use_ty}`"),
@@ -2092,6 +2120,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
20922120
}
20932121

20942122
pub enum TypeMismatchSource<'tcx> {
2123+
/// Expected the binding to have the given type, but it was found to have
2124+
/// a different type. Find out when that type first became incompatible.
20952125
Ty(Ty<'tcx>),
2096-
Arg(&'tcx hir::Expr<'tcx>, usize),
2126+
/// When we fail during method argument checking, try to find out if a previous
2127+
/// expression has constrained the method's receiver in a way that makes the
2128+
/// argument's type incompatible.
2129+
Arg { call_expr: &'tcx hir::Expr<'tcx>, incompatible_arg: usize },
20972130
}

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
814814
self.note_source_of_type_mismatch_constraint(
815815
&mut err,
816816
rcvr,
817-
crate::demand::TypeMismatchSource::Arg(call_expr, provided_idx.as_usize()),
817+
crate::demand::TypeMismatchSource::Arg {
818+
call_expr,
819+
incompatible_arg: provided_idx.as_usize(),
820+
},
818821
);
819822
}
820823

tests/ui/type/type-check/point-at-inference-4.rs

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

tests/ui/type/type-check/point-at-inference-4.stderr

+8-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ LL | s.infer(0i32, /* b */);
1515
| ~~~~~~~~~~~~~~~
1616

1717
error[E0308]: mismatched types
18-
--> $DIR/point-at-inference-4.rs:17:24
18+
--> $DIR/point-at-inference-4.rs:19:24
1919
|
2020
LL | s.infer(0i32);
21-
| - here the type of `s` is inferred to be `S<i32, _>`
21+
| - ---- this argument has type `i32`...
22+
| |
23+
| ... which causes `s` to have type `S<i32, _>`
2224
...
2325
LL | let t: S<u32, _> = s;
2426
| --------- ^ expected `S<u32, _>`, found `S<i32, _>`
@@ -27,6 +29,10 @@ LL | let t: S<u32, _> = s;
2729
|
2830
= note: expected struct `S<u32, _>`
2931
found struct `S<i32, _>`
32+
help: change the type of the numeric literal from `i32` to `u32`
33+
|
34+
LL | s.infer(0u32);
35+
| ~~~
3036

3137
error: aborting due to 2 previous errors
3238

0 commit comments

Comments
 (0)