From 42c4373ad11a86d3028ddfe29d7f33edc9670ba8 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 2 Mar 2023 22:55:37 +0000 Subject: [PATCH 1/5] Make note_source_of_type_mismatch_constraint simpler --- compiler/rustc_hir_typeck/src/demand.rs | 276 +++++------------- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 22 +- .../type/type-check/assignment-in-if.stderr | 9 - .../type-check/point-at-inference-3.fixed | 1 - .../type/type-check/point-at-inference-3.rs | 1 - .../type-check/point-at-inference-3.stderr | 5 +- .../type/type-check/point-at-inference-4.rs | 1 + .../type-check/point-at-inference-4.stderr | 5 +- .../type/type-check/point-at-inference.fixed | 2 +- .../type/type-check/point-at-inference.stderr | 6 +- .../ui/typeck/bad-type-in-vec-contains.stderr | 1 - tests/ui/typeck/bad-type-in-vec-push.stderr | 2 - tests/ui/typeck/issue-107775.stderr | 4 +- 13 files changed, 94 insertions(+), 241 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index a4c3be1d17745..0c4f73e0db0eb 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1,6 +1,5 @@ use crate::FnCtxt; use rustc_ast::util::parser::PREC_POSTFIX; -use rustc_data_structures::fx::FxHashMap; use rustc_errors::MultiSpan; use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; use rustc_hir as hir; @@ -13,15 +12,13 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::middle::stability::EvalResult; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::error::{ExpectedFound, TypeError}; -use rustc_middle::ty::fold::{BottomUpFolder, TypeFolder}; -use rustc_middle::ty::print::{with_forced_trimmed_paths, with_no_trimmed_paths}; -use rustc_middle::ty::relate::TypeRelation; -use rustc_middle::ty::{self, Article, AssocItem, Ty, TypeAndMut, TypeVisitableExt}; +use rustc_middle::ty::fold::BottomUpFolder; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::{self, Article, AssocItem, Ty, TypeAndMut, TypeFoldable}; use rustc_span::symbol::{sym, Symbol}; -use rustc_span::{BytePos, Span}; +use rustc_span::{BytePos, Span, DUMMY_SP}; use rustc_target::abi::FieldIdx; use rustc_trait_selection::infer::InferCtxtExt as _; -use rustc_trait_selection::traits::error_reporting::method_chain::CollectAllMismatches; use rustc_trait_selection::traits::ObligationCause; use super::method::probe; @@ -62,9 +59,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { || self.suggest_into(err, expr, expr_ty, expected) || self.suggest_floating_point_literal(err, expr, expected) || self.suggest_null_ptr_for_literal_zero_given_to_ptr_arg(err, expr, expected) - || self.note_result_coercion(err, expr, expected, expr_ty); + || self.suggest_coercing_result_via_try_operator(err, expr, expected, expr_ty); + if !suggested { - self.point_at_expr_source_of_inferred_type(err, expr, expr_ty, expected, expr.span); + self.note_source_of_type_mismatch_constraint(err, expr, expected); } } @@ -218,37 +216,34 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { (expected, Some(err)) } - pub fn point_at_expr_source_of_inferred_type( + /// Notes the point at which a variable is constrained to some type incompatible + /// with `expected_ty`. + pub fn note_source_of_type_mismatch_constraint( &self, err: &mut Diagnostic, expr: &hir::Expr<'_>, - found: Ty<'tcx>, - expected: Ty<'tcx>, - mismatch_span: Span, + expected_ty: Ty<'tcx>, ) -> bool { - let map = self.tcx.hir(); + let hir = self.tcx.hir(); let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind else { return false; }; let [hir::PathSegment { ident, args: None, .. }] = p.segments else { return false; }; - let hir::def::Res::Local(hir_id) = p.res else { return false; }; - let Some(hir::Node::Pat(pat)) = map.find(hir_id) else { return false; }; - let Some(hir::Node::Local(hir::Local { - ty: None, - init: Some(init), - .. - })) = map.find_parent(pat.hir_id) else { return false; }; - let Some(ty) = self.node_ty_opt(init.hir_id) else { return false; }; - if ty.is_closure() || init.span.overlaps(expr.span) || pat.span.from_expansion() { - return false; - } + let hir::def::Res::Local(local_hir_id) = p.res else { return false; }; + let hir::Node::Pat(pat) = hir.get(local_hir_id) else { return false; }; + let (init_ty_hir_id, init) = match hir.get_parent(pat.hir_id) { + hir::Node::Local(hir::Local { ty: Some(ty), init, .. }) => (ty.hir_id, *init), + hir::Node::Local(hir::Local { init: Some(init), .. }) => (init.hir_id, Some(*init)), + _ => return false, + }; + let Some(init_ty) = self.node_ty_opt(init_ty_hir_id) else { return false; }; // Locate all the usages of the relevant binding. - struct FindExprs<'hir> { + struct FindExprs<'tcx> { hir_id: hir::HirId, - uses: Vec<&'hir hir::Expr<'hir>>, + uses: Vec<&'tcx hir::Expr<'tcx>>, } - impl<'v> Visitor<'v> for FindExprs<'v> { - fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { + impl<'tcx> Visitor<'tcx> for FindExprs<'tcx> { + fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = ex.kind && let hir::def::Res::Local(hir_id) = path.res && hir_id == self.hir_id @@ -259,180 +254,71 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - let mut expr_finder = FindExprs { hir_id, uses: vec![] }; - let id = map.get_parent_item(hir_id); - let hir_id: hir::HirId = id.into(); - - let Some(node) = map.find(hir_id) else { return false; }; - let Some(body_id) = node.body_id() else { return false; }; - let body = map.body(body_id); + let mut expr_finder = FindExprs { hir_id: local_hir_id, uses: init.into_iter().collect() }; + let body = + hir.body(hir.maybe_body_owned_by(self.body_id).expect("expected item to have body")); expr_finder.visit_expr(body.value); - // Hack to make equality checks on types with inference variables and regions useful. - let mut eraser = BottomUpFolder { - tcx: self.tcx, - lt_op: |_| self.tcx.lifetimes.re_erased, - ct_op: |c| c, - ty_op: |t| match *t.kind() { - ty::Infer(ty::TyVar(_)) => self.tcx.mk_ty_var(ty::TyVid::from_u32(0)), - ty::Infer(ty::IntVar(_)) => self.tcx.mk_int_var(ty::IntVid { index: 0 }), - ty::Infer(ty::FloatVar(_)) => self.tcx.mk_float_var(ty::FloatVid { index: 0 }), - _ => t, - }, - }; - let mut prev = eraser.fold_ty(ty); - let mut prev_span: Option = None; - - for binding in expr_finder.uses { - // In every expression where the binding is referenced, we will look at that - // expression's type and see if it is where the incorrect found type was fully - // "materialized" and point at it. We will also try to provide a suggestion there. - if let Some(hir::Node::Expr(expr) - | hir::Node::Stmt(hir::Stmt { - kind: hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr), - .. - })) = &map.find_parent(binding.hir_id) - && let hir::ExprKind::MethodCall(segment, rcvr, args, _span) = expr.kind - && rcvr.hir_id == binding.hir_id - && let Some(def_id) = self.typeck_results.borrow().type_dependent_def_id(expr.hir_id) - { - // We special case methods, because they can influence inference through the - // call's arguments and we can provide a more explicit span. - let sig = self.tcx.fn_sig(def_id).subst_identity(); - let def_self_ty = sig.input(0).skip_binder(); - let param_tys = sig.inputs().skip_binder().iter().skip(1); - // If there's an arity mismatch, pointing out the call as the source of an inference - // can be misleading, so we skip it. - if param_tys.len() != args.len() { - continue; - } - let rcvr_ty = self.node_ty(rcvr.hir_id); - // Get the evaluated type *after* calling the method call, so that the influence - // of the arguments can be reflected in the receiver type. The receiver - // expression has the type *before* this analysis is done. - let ty = match self.lookup_probe_for_diagnostic( - segment.ident, - rcvr_ty, - expr, - probe::ProbeScope::TraitsInScope, - None, - ) { - Ok(pick) => eraser.fold_ty(pick.self_ty), - Err(_) => rcvr_ty, - }; - // Remove one layer of references to account for `&mut self` and - // `&self`, so that we can compare it against the binding. - let (ty, def_self_ty) = match (ty.kind(), def_self_ty.kind()) { - (ty::Ref(_, ty, a), ty::Ref(_, self_ty, b)) if a == b => (*ty, *self_ty), - _ => (ty, def_self_ty), - }; - let mut param_args = FxHashMap::default(); - let mut param_expected = FxHashMap::default(); - let mut param_found = FxHashMap::default(); - if self.can_eq(self.param_env, ty, found) { - // We only point at the first place where the found type was inferred. - for (param_ty, arg) in param_tys.zip(args) { - if def_self_ty.contains(*param_ty) && let ty::Param(_) = param_ty.kind() { - // We found an argument that references a type parameter in `Self`, - // so we assume that this is the argument that caused the found - // type, which we know already because of `can_eq` above was first - // inferred in this method call. - let arg_ty = self.node_ty(arg.hir_id); - if !arg.span.overlaps(mismatch_span) { - err.span_label( - arg.span, - &format!( - "this is of type `{arg_ty}`, which causes `{ident}` to be \ - inferred as `{ty}`", - ), - ); - } - param_args.insert(param_ty, (arg, arg_ty)); - } - } - } - // Here we find, for a type param `T`, the type that `T` is in the current - // method call *and* in the original expected type. That way, we can see if we - // can give any structured suggestion for the function argument. - let mut c = CollectAllMismatches { - infcx: &self.infcx, - param_env: self.param_env, - errors: vec![], - }; - let _ = c.relate(def_self_ty, ty); - for error in c.errors { - if let TypeError::Sorts(error) = error { - param_found.insert(error.expected, error.found); + let fudge_equals_found_ty = |use_ty: Ty<'tcx>| { + use rustc_infer::infer::type_variable::*; + use rustc_middle::infer::unify_key::*; + let use_ty = use_ty.fold_with(&mut BottomUpFolder { + tcx: self.tcx, + ty_op: |ty| { + if let ty::Infer(infer) = ty.kind() { + match infer { + ty::InferTy::TyVar(_) => self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: DUMMY_SP, + }), + ty::InferTy::IntVar(_) => self.next_int_var(), + ty::InferTy::FloatVar(_) => self.next_float_var(), + _ => bug!(), + } + } else { + ty } - } - c.errors = vec![]; - let _ = c.relate(def_self_ty, expected); - for error in c.errors { - if let TypeError::Sorts(error) = error { - param_expected.insert(error.expected, error.found); + }, + lt_op: |_| self.tcx.lifetimes.re_erased, + ct_op: |ct| { + if let ty::ConstKind::Infer(_) = ct.kind() { + self.next_const_var( + ct.ty(), + ConstVariableOrigin { + kind: ConstVariableOriginKind::MiscVariable, + span: DUMMY_SP, + }, + ) + } else { + ct } - } - for (param, (arg, arg_ty)) in param_args.iter() { - let Some(expected) = param_expected.get(param) else { continue; }; - let Some(found) = param_found.get(param) else { continue; }; - if !self.can_eq(self.param_env, *arg_ty, *found) { continue; } - self.emit_coerce_suggestions(err, arg, *found, *expected, None, None); - } + }, + }); + self.can_eq(self.param_env, expected_ty, use_ty) + }; - let ty = eraser.fold_ty(ty); - if ty.references_error() { - break; - } - if ty != prev - && param_args.is_empty() - && self.can_eq(self.param_env, ty, found) - { - // We only point at the first place where the found type was inferred. - if !segment.ident.span.overlaps(mismatch_span) { - err.span_label( - segment.ident.span, - with_forced_trimmed_paths!(format!( - "here the type of `{ident}` is inferred to be `{ty}`", - )), - );} - break; - } else if !param_args.is_empty() { - break; - } - prev = ty; - } else { - let ty = eraser.fold_ty(self.node_ty(binding.hir_id)); - if ty.references_error() { - break; - } - if ty != prev - && let Some(span) = prev_span - && self.can_eq(self.param_env, ty, found) - { - // We only point at the first place where the found type was inferred. - // We use the *previous* span because if the type is known *here* it means - // it was *evaluated earlier*. We don't do this for method calls because we - // evaluate the method's self type eagerly, but not in any other case. - if !span.overlaps(mismatch_span) { - err.span_label( - span, - with_forced_trimmed_paths!(format!( - "here the type of `{ident}` is inferred to be `{ty}`", - )), - ); - } - break; - } - prev = ty; + if !fudge_equals_found_ty(init_ty) { + return false; + } + + for window in expr_finder.uses.windows(2) { + let [binding, next_usage] = *window else { continue; }; + let Some(next_use_ty) = self.node_ty_opt(next_usage.hir_id) else { continue; }; + if !fudge_equals_found_ty(next_use_ty) { + err.span_label( + binding.span, + format!("here the type of `{ident}` is inferred to be `{next_use_ty}`"), + ); + return true; } - if binding.hir_id == expr.hir_id { - // Do not look at expressions that come after the expression we were originally - // evaluating and had a type error. + + if next_usage.hir_id == expr.hir_id { break; } - prev_span = Some(binding.span); } - true + + // We must've not found something that constrained the expr. + false } fn annotate_expected_due_to_let_ty( @@ -708,7 +594,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - pub(crate) fn note_result_coercion( + pub(crate) fn suggest_coercing_result_via_try_operator( &self, err: &mut Diagnostic, expr: &hir::Expr<'tcx>, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index a009ae5d44eb1..d98e0d9096f53 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -807,24 +807,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { full_call_span, format!("arguments to this {} are incorrect", call_name), ); - if let (Some(callee_ty), hir::ExprKind::MethodCall(_, rcvr, _, _)) = - (callee_ty, &call_expr.kind) - { - // Type that would have accepted this argument if it hadn't been inferred earlier. - // FIXME: We leave an inference variable for now, but it'd be nice to get a more - // specific type to increase the accuracy of the diagnostic. - let expected = self.infcx.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::MiscVariable, - span: full_call_span, - }); - self.point_at_expr_source_of_inferred_type( - &mut err, - rcvr, - expected, - callee_ty, - provided_arg_span, - ); - } + + // TODO: We would like to point out when the rcvr was constrained + // such that the arg mismatch occurs. + // Call out where the function is defined self.label_fn_like( &mut err, diff --git a/tests/ui/type/type-check/assignment-in-if.stderr b/tests/ui/type/type-check/assignment-in-if.stderr index de133e5599cf9..9f4558adab150 100644 --- a/tests/ui/type/type-check/assignment-in-if.stderr +++ b/tests/ui/type/type-check/assignment-in-if.stderr @@ -67,9 +67,6 @@ LL | x == 5 error[E0308]: mismatched types --> $DIR/assignment-in-if.rs:44:18 | -LL | if y = (Foo { foo: x }) { - | - here the type of `x` is inferred to be `usize` -... LL | if x == x && x = x && x == x { | ------ ^ expected `bool`, found `usize` | | @@ -78,9 +75,6 @@ LL | if x == x && x = x && x == x { error[E0308]: mismatched types --> $DIR/assignment-in-if.rs:44:22 | -LL | if y = (Foo { foo: x }) { - | - here the type of `x` is inferred to be `usize` -... LL | if x == x && x = x && x == x { | ^ expected `bool`, found `usize` @@ -98,9 +92,6 @@ LL | if x == x && x == x && x == x { error[E0308]: mismatched types --> $DIR/assignment-in-if.rs:51:28 | -LL | if y = (Foo { foo: x }) { - | - here the type of `x` is inferred to be `usize` -... LL | if x == x && x == x && x = x { | ---------------- ^ expected `bool`, found `usize` | | diff --git a/tests/ui/type/type-check/point-at-inference-3.fixed b/tests/ui/type/type-check/point-at-inference-3.fixed index edd4adf8bd256..9c93063e04ae2 100644 --- a/tests/ui/type/type-check/point-at-inference-3.fixed +++ b/tests/ui/type/type-check/point-at-inference-3.fixed @@ -2,7 +2,6 @@ fn main() { let mut v = Vec::new(); v.push(0i32); - //~^ NOTE this is of type `i32`, which causes `v` to be inferred as `Vec` v.push(0); v.push(1i32); //~ ERROR mismatched types //~^ NOTE expected `i32`, found `u32` diff --git a/tests/ui/type/type-check/point-at-inference-3.rs b/tests/ui/type/type-check/point-at-inference-3.rs index 49d7b50075bbc..8bb1487420baa 100644 --- a/tests/ui/type/type-check/point-at-inference-3.rs +++ b/tests/ui/type/type-check/point-at-inference-3.rs @@ -2,7 +2,6 @@ fn main() { let mut v = Vec::new(); v.push(0i32); - //~^ NOTE this is of type `i32`, which causes `v` to be inferred as `Vec` v.push(0); v.push(1u32); //~ ERROR mismatched types //~^ NOTE expected `i32`, found `u32` diff --git a/tests/ui/type/type-check/point-at-inference-3.stderr b/tests/ui/type/type-check/point-at-inference-3.stderr index 2c4907ed263ba..32663bd632e12 100644 --- a/tests/ui/type/type-check/point-at-inference-3.stderr +++ b/tests/ui/type/type-check/point-at-inference-3.stderr @@ -1,9 +1,6 @@ error[E0308]: mismatched types - --> $DIR/point-at-inference-3.rs:7:12 + --> $DIR/point-at-inference-3.rs:6:12 | -LL | v.push(0i32); - | ---- this is of type `i32`, which causes `v` to be inferred as `Vec` -... LL | v.push(1u32); | ---- ^^^^ expected `i32`, found `u32` | | diff --git a/tests/ui/type/type-check/point-at-inference-4.rs b/tests/ui/type/type-check/point-at-inference-4.rs index aea9b2c6c14ee..7c25b91fd89e9 100644 --- a/tests/ui/type/type-check/point-at-inference-4.rs +++ b/tests/ui/type/type-check/point-at-inference-4.rs @@ -11,6 +11,7 @@ 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` //~| NOTE an argument is missing //~| HELP provide the argument let t: S = s; diff --git a/tests/ui/type/type-check/point-at-inference-4.stderr b/tests/ui/type/type-check/point-at-inference-4.stderr index 28833d2ed1c92..6181af70c8ed4 100644 --- a/tests/ui/type/type-check/point-at-inference-4.stderr +++ b/tests/ui/type/type-check/point-at-inference-4.stderr @@ -15,8 +15,11 @@ LL | s.infer(0i32, /* b */); | ~~~~~~~~~~~~~~~ error[E0308]: mismatched types - --> $DIR/point-at-inference-4.rs:16:24 + --> $DIR/point-at-inference-4.rs:17:24 | +LL | s.infer(0i32); + | - here the type of `s` is inferred to be `S` +... LL | let t: S = s; | --------- ^ expected `S`, found `S` | | diff --git a/tests/ui/type/type-check/point-at-inference.fixed b/tests/ui/type/type-check/point-at-inference.fixed index f41fbe59fba6c..6419e42e70d12 100644 --- a/tests/ui/type/type-check/point-at-inference.fixed +++ b/tests/ui/type/type-check/point-at-inference.fixed @@ -6,7 +6,7 @@ fn main() { let mut foo = vec![]; baz(&foo); for i in &v { - foo.push(*i); + foo.push(i); } baz(&foo); bar(foo); //~ ERROR E0308 diff --git a/tests/ui/type/type-check/point-at-inference.stderr b/tests/ui/type/type-check/point-at-inference.stderr index a76b4f90c734b..89c4cd1c37632 100644 --- a/tests/ui/type/type-check/point-at-inference.stderr +++ b/tests/ui/type/type-check/point-at-inference.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/point-at-inference.rs:12:9 | LL | foo.push(i); - | - this is of type `&{integer}`, which causes `foo` to be inferred as `Vec<&{integer}>` + | --- here the type of `foo` is inferred to be `Vec<&{integer}>` ... LL | bar(foo); | --- ^^^ expected `Vec`, found `Vec<&{integer}>` @@ -16,10 +16,6 @@ note: function defined here | LL | fn bar(_: Vec) {} | ^^^ ----------- -help: consider dereferencing the borrow - | -LL | foo.push(*i); - | + error: aborting due to previous error diff --git a/tests/ui/typeck/bad-type-in-vec-contains.stderr b/tests/ui/typeck/bad-type-in-vec-contains.stderr index 0e03388d2d58c..72533ab1fa37f 100644 --- a/tests/ui/typeck/bad-type-in-vec-contains.stderr +++ b/tests/ui/typeck/bad-type-in-vec-contains.stderr @@ -7,7 +7,6 @@ LL | primes.contains(3); | | expected `&_`, found integer | | help: consider borrowing here: `&3` | arguments to this method are incorrect - | here the type of `primes` is inferred to be `[_]` | = note: expected reference `&_` found type `{integer}` diff --git a/tests/ui/typeck/bad-type-in-vec-push.stderr b/tests/ui/typeck/bad-type-in-vec-push.stderr index ae46050c91be2..1d5337260fa0a 100644 --- a/tests/ui/typeck/bad-type-in-vec-push.stderr +++ b/tests/ui/typeck/bad-type-in-vec-push.stderr @@ -1,8 +1,6 @@ error[E0308]: mismatched types --> $DIR/bad-type-in-vec-push.rs:11:17 | -LL | vector.sort(); - | ------ here the type of `vector` is inferred to be `Vec<_>` LL | result.push(vector); | ---- ^^^^^^ expected integer, found `Vec<_>` | | diff --git a/tests/ui/typeck/issue-107775.stderr b/tests/ui/typeck/issue-107775.stderr index 9ee9c022c6e8c..39c727bd278bc 100644 --- a/tests/ui/typeck/issue-107775.stderr +++ b/tests/ui/typeck/issue-107775.stderr @@ -2,9 +2,7 @@ error[E0308]: mismatched types --> $DIR/issue-107775.rs:35:16 | LL | map.insert(1, Struct::do_something); - | - -------------------- this is of type `fn(u8) -> Pin + Send>> {::do_something::<'_>}`, which causes `map` to be inferred as `HashMap<{integer}, fn(u8) -> Pin + Send>> {::do_something::<'_>}>` - | | - | this is of type `{integer}`, which causes `map` to be inferred as `HashMap<{integer}, fn(u8) -> Pin + Send>> {::do_something::<'_>}>` + | --- here the type of `map` is inferred to be `HashMap<{integer}, fn(u8) -> Pin + Send>> {::do_something::<'_>}>` LL | Self { map } | ^^^ expected `HashMap Pin<...>>`, found `HashMap<{integer}, ...>` | From e72c45ad987b296baee79865b7e2ca00c518fb8b Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 2 Mar 2023 23:23:07 +0000 Subject: [PATCH 2/5] Point at which arg causes a binding to be constrained --- compiler/rustc_hir_typeck/src/demand.rs | 61 +++++++++++++++---- .../type/type-check/point-at-inference.stderr | 4 +- tests/ui/typeck/issue-107775.stderr | 4 +- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 0c4f73e0db0eb..0d3e4bde497f8 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -259,10 +259,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { hir.body(hir.maybe_body_owned_by(self.body_id).expect("expected item to have body")); expr_finder.visit_expr(body.value); - let fudge_equals_found_ty = |use_ty: Ty<'tcx>| { + let fudge_ty = |ty: Ty<'tcx>| { use rustc_infer::infer::type_variable::*; use rustc_middle::infer::unify_key::*; - let use_ty = use_ty.fold_with(&mut BottomUpFolder { + ty.fold_with(&mut BottomUpFolder { tcx: self.tcx, ty_op: |ty| { if let ty::Infer(infer) = ty.kind() { @@ -293,7 +293,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ct } }, - }); + }) + }; + + let fudge_equals_found_ty = |use_ty: Ty<'tcx>| { + let use_ty = fudge_ty(use_ty); self.can_eq(self.param_env, expected_ty, use_ty) }; @@ -303,18 +307,53 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { for window in expr_finder.uses.windows(2) { let [binding, next_usage] = *window else { continue; }; + + // Don't go past the binding (always gonna be a nonsense label if so) + if binding.hir_id == expr.hir_id { + break; + } + let Some(next_use_ty) = self.node_ty_opt(next_usage.hir_id) else { continue; }; - if !fudge_equals_found_ty(next_use_ty) { - err.span_label( - binding.span, - format!("here the type of `{ident}` is inferred to be `{next_use_ty}`"), - ); - return true; + + // If the type is not constrained in a way making it not possible to + // equate with `expected_ty` by this point, skip. + if fudge_equals_found_ty(next_use_ty) { + continue; } - if next_usage.hir_id == expr.hir_id { - break; + if let hir::Node::Expr(parent_expr) = hir.get_parent(binding.hir_id) + && let hir::ExprKind::MethodCall(segment, rcvr, args, _) = parent_expr.kind + && rcvr.hir_id == binding.hir_id + { + let Some(rcvr_ty) = self.node_ty_opt(rcvr.hir_id) else { continue; }; + let rcvr_ty = fudge_ty(rcvr_ty); + if let Ok(method) = + self.lookup_method(rcvr_ty, segment, DUMMY_SP, parent_expr, rcvr, args) + { + for (expected_arg_ty, arg_expr) in std::iter::zip(&method.sig.inputs()[1..], args) { + let Some(arg_ty) = self.node_ty_opt(arg_expr.hir_id) else { continue; }; + let arg_ty = fudge_ty(arg_ty); + let _ = self.try_coerce(arg_expr, arg_ty, *expected_arg_ty, AllowTwoPhase::No, None); + if !self.can_eq(self.param_env, rcvr_ty, expected_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}`") + ); + return true; + } + } + } } + + err.span_label( + binding.span, + format!("here the type of `{ident}` is inferred to be `{next_use_ty}`"), + ); + return true; } // We must've not found something that constrained the expr. diff --git a/tests/ui/type/type-check/point-at-inference.stderr b/tests/ui/type/type-check/point-at-inference.stderr index 89c4cd1c37632..5d46368b1fd38 100644 --- a/tests/ui/type/type-check/point-at-inference.stderr +++ b/tests/ui/type/type-check/point-at-inference.stderr @@ -2,7 +2,9 @@ error[E0308]: mismatched types --> $DIR/point-at-inference.rs:12:9 | LL | foo.push(i); - | --- here the type of `foo` is inferred to be `Vec<&{integer}>` + | --- - this argument has type `&{integer}`... + | | + | ... which causes `foo` to have type `Vec<&{integer}>` ... LL | bar(foo); | --- ^^^ expected `Vec`, found `Vec<&{integer}>` diff --git a/tests/ui/typeck/issue-107775.stderr b/tests/ui/typeck/issue-107775.stderr index 39c727bd278bc..b97e74b7e53fa 100644 --- a/tests/ui/typeck/issue-107775.stderr +++ b/tests/ui/typeck/issue-107775.stderr @@ -2,7 +2,9 @@ error[E0308]: mismatched types --> $DIR/issue-107775.rs:35:16 | LL | map.insert(1, Struct::do_something); - | --- here the type of `map` is inferred to be `HashMap<{integer}, fn(u8) -> Pin + Send>> {::do_something::<'_>}>` + | --- -------------------- this argument has type `fn(u8) -> Pin + Send>> {::do_something::<'_>}`... + | | + | ... which causes `map` to have type `HashMap<{integer}, fn(u8) -> Pin + Send>> {::do_something::<'_>}>` LL | Self { map } | ^^^ expected `HashMap Pin<...>>`, found `HashMap<{integer}, ...>` | From 29aee6a125ac65a01932cb0ece5485e7cf8cfe87 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 2 Mar 2023 23:49:24 +0000 Subject: [PATCH 3/5] Restore suggestion based off of backwards inference from bad usage to method call --- compiler/rustc_hir_typeck/src/demand.rs | 141 +++++++++++------- .../type/type-check/point-at-inference.fixed | 2 +- .../type/type-check/point-at-inference.stderr | 4 + 3 files changed, 88 insertions(+), 59 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 0d3e4bde497f8..f219068b4e87b 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -259,49 +259,43 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { hir.body(hir.maybe_body_owned_by(self.body_id).expect("expected item to have body")); expr_finder.visit_expr(body.value); - let fudge_ty = |ty: Ty<'tcx>| { - use rustc_infer::infer::type_variable::*; - use rustc_middle::infer::unify_key::*; - ty.fold_with(&mut BottomUpFolder { - tcx: self.tcx, - ty_op: |ty| { - if let ty::Infer(infer) = ty.kind() { - match infer { - ty::InferTy::TyVar(_) => self.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::MiscVariable, - span: DUMMY_SP, - }), - ty::InferTy::IntVar(_) => self.next_int_var(), - ty::InferTy::FloatVar(_) => self.next_float_var(), - _ => bug!(), - } - } else { - ty - } - }, - lt_op: |_| self.tcx.lifetimes.re_erased, - ct_op: |ct| { - if let ty::ConstKind::Infer(_) = ct.kind() { - self.next_const_var( - ct.ty(), - ConstVariableOrigin { - kind: ConstVariableOriginKind::MiscVariable, - span: DUMMY_SP, - }, - ) - } else { - ct + use rustc_infer::infer::type_variable::*; + use rustc_middle::infer::unify_key::*; + + let mut fudger = BottomUpFolder { + tcx: self.tcx, + ty_op: |ty| { + if let ty::Infer(infer) = ty.kind() { + match infer { + ty::InferTy::TyVar(_) => self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: DUMMY_SP, + }), + ty::InferTy::IntVar(_) => self.next_int_var(), + ty::InferTy::FloatVar(_) => self.next_float_var(), + _ => bug!(), } - }, - }) - }; - - let fudge_equals_found_ty = |use_ty: Ty<'tcx>| { - let use_ty = fudge_ty(use_ty); - self.can_eq(self.param_env, expected_ty, use_ty) + } else { + ty + } + }, + lt_op: |_| self.tcx.lifetimes.re_erased, + ct_op: |ct| { + if let ty::ConstKind::Infer(_) = ct.kind() { + self.next_const_var( + ct.ty(), + ConstVariableOrigin { + kind: ConstVariableOriginKind::MiscVariable, + span: DUMMY_SP, + }, + ) + } else { + ct + } + }, }; - if !fudge_equals_found_ty(init_ty) { + if !self.can_eq(self.param_env, expected_ty, init_ty.fold_with(&mut fudger)) { return false; } @@ -317,7 +311,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If the type is not constrained in a way making it not possible to // equate with `expected_ty` by this point, skip. - if fudge_equals_found_ty(next_use_ty) { + if self.can_eq(self.param_env, expected_ty, next_use_ty.fold_with(&mut fudger)) { continue; } @@ -326,26 +320,57 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { && rcvr.hir_id == binding.hir_id { let Some(rcvr_ty) = self.node_ty_opt(rcvr.hir_id) else { continue; }; - let rcvr_ty = fudge_ty(rcvr_ty); - if let Ok(method) = + let rcvr_ty = rcvr_ty.fold_with(&mut fudger); + let Ok(method) = self.lookup_method(rcvr_ty, segment, DUMMY_SP, parent_expr, rcvr, args) + else { + 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) + }); + + for (idx, (expected_arg_ty, arg_expr)) in + std::iter::zip(&method.sig.inputs()[1..], args).enumerate() { - for (expected_arg_ty, arg_expr) in std::iter::zip(&method.sig.inputs()[1..], args) { - let Some(arg_ty) = self.node_ty_opt(arg_expr.hir_id) else { continue; }; - let arg_ty = fudge_ty(arg_ty); - let _ = self.try_coerce(arg_expr, arg_ty, *expected_arg_ty, AllowTwoPhase::No, None); - if !self.can_eq(self.param_env, rcvr_ty, expected_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}`") - ); - return true; - } + let Some(arg_ty) = self.node_ty_opt(arg_expr.hir_id) else { continue; }; + let arg_ty = arg_ty.fold_with(&mut fudger); + let _ = self.try_coerce( + arg_expr, + arg_ty, + *expected_arg_ty, + AllowTwoPhase::No, + None, + ); + 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( + binding.span, + format!( + "... which constrains `{ident}` to have type `{next_use_ty}`" + ), + ); + if let Ok(ideal_method_sig) = ideal_method_sig { + self.emit_type_mismatch_suggestions( + err, + arg_expr, + arg_ty, + ideal_method_sig.inputs()[idx + 1], + None, + None, + ); } + return true; } } diff --git a/tests/ui/type/type-check/point-at-inference.fixed b/tests/ui/type/type-check/point-at-inference.fixed index 6419e42e70d12..f41fbe59fba6c 100644 --- a/tests/ui/type/type-check/point-at-inference.fixed +++ b/tests/ui/type/type-check/point-at-inference.fixed @@ -6,7 +6,7 @@ fn main() { let mut foo = vec![]; baz(&foo); for i in &v { - foo.push(i); + foo.push(*i); } baz(&foo); bar(foo); //~ ERROR E0308 diff --git a/tests/ui/type/type-check/point-at-inference.stderr b/tests/ui/type/type-check/point-at-inference.stderr index 5d46368b1fd38..5fc94d4d1b6ba 100644 --- a/tests/ui/type/type-check/point-at-inference.stderr +++ b/tests/ui/type/type-check/point-at-inference.stderr @@ -18,6 +18,10 @@ note: function defined here | LL | fn bar(_: Vec) {} | ^^^ ----------- +help: consider dereferencing the borrow + | +LL | foo.push(*i); + | + error: aborting due to previous error From 5a71029dd3b54046fbd6144de20e320db539820f Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 3 Mar 2023 00:44:22 +0000 Subject: [PATCH 4/5] Properly note source of arg mismatch --- compiler/rustc_hir_typeck/src/demand.rs | 57 ++++++++++++++++++- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 13 ++++- .../type-check/point-at-inference-3.fixed | 2 + .../type/type-check/point-at-inference-3.rs | 2 + .../type-check/point-at-inference-3.stderr | 7 ++- 5 files changed, 74 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index f219068b4e87b..9ffee56702329 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -62,7 +62,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { || self.suggest_coercing_result_via_try_operator(err, expr, expected, expr_ty); if !suggested { - self.note_source_of_type_mismatch_constraint(err, expr, expected); + self.note_source_of_type_mismatch_constraint( + err, + expr, + TypeMismatchSource::Ty(expected), + ); } } @@ -222,7 +226,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, err: &mut Diagnostic, expr: &hir::Expr<'_>, - expected_ty: Ty<'tcx>, + source: TypeMismatchSource<'tcx>, ) -> bool { let hir = self.tcx.hir(); @@ -295,6 +299,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }, }; + let expected_ty = match source { + TypeMismatchSource::Ty(expected_ty) => expected_ty, + TypeMismatchSource::Arg(call_expr, idx) => { + let hir::ExprKind::MethodCall(segment, _, args, _) = call_expr.kind else { + return false; + }; + let Some(arg_ty) = self.node_ty_opt(args[idx].hir_id) else { + return false; + }; + 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 = possible_rcvr_ty.fold_with(&mut fudger); + let method = self + .lookup_method( + possible_rcvr_ty, + segment, + DUMMY_SP, + call_expr, + binding, + args, + ) + .ok()?; + let _ = self + .at(&ObligationCause::dummy(), self.param_env) + .eq(DefineOpaqueTypes::No, method.sig.inputs()[idx + 1], arg_ty) + .ok()?; + self.select_obligations_where_possible(|errs| { + // Yeet the errors, we're already reporting errors. + errs.clear(); + }); + Some(self.resolve_vars_if_possible(possible_rcvr_ty)) + }); + if let Some(rcvr_ty) = possible_rcvr_ty { + rcvr_ty + } else { + return false; + } + } + }; + if !self.can_eq(self.param_env, expected_ty, init_ty.fold_with(&mut fudger)) { return false; } @@ -360,7 +404,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "... which constrains `{ident}` to have type `{next_use_ty}`" ), ); - if let Ok(ideal_method_sig) = ideal_method_sig { + if matches!(source, TypeMismatchSource::Ty(_)) + && let Ok(ideal_method_sig) = ideal_method_sig + { self.emit_type_mismatch_suggestions( err, arg_expr, @@ -2044,3 +2090,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } + +pub enum TypeMismatchSource<'tcx> { + Ty(Ty<'tcx>), + Arg(&'tcx hir::Expr<'tcx>, usize), +} diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index d98e0d9096f53..9ecb78e2dda80 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -472,7 +472,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err_code: &str, fn_def_id: Option, call_span: Span, - call_expr: &hir::Expr<'tcx>, + call_expr: &'tcx hir::Expr<'tcx>, ) { // Next, let's construct the error let (error_span, full_call_span, call_name, is_method) = match &call_expr.kind { @@ -808,8 +808,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { format!("arguments to this {} are incorrect", call_name), ); - // TODO: We would like to point out when the rcvr was constrained - // such that the arg mismatch occurs. + if let hir::ExprKind::MethodCall(_, rcvr, _, _) = call_expr.kind + && provided_idx.as_usize() == expected_idx.as_usize() + { + self.note_source_of_type_mismatch_constraint( + &mut err, + rcvr, + crate::demand::TypeMismatchSource::Arg(call_expr, provided_idx.as_usize()), + ); + } // Call out where the function is defined self.label_fn_like( diff --git a/tests/ui/type/type-check/point-at-inference-3.fixed b/tests/ui/type/type-check/point-at-inference-3.fixed index 9c93063e04ae2..15a3b580568d6 100644 --- a/tests/ui/type/type-check/point-at-inference-3.fixed +++ b/tests/ui/type/type-check/point-at-inference-3.fixed @@ -2,6 +2,8 @@ fn main() { let mut v = Vec::new(); v.push(0i32); + //~^ NOTE this argument has type `i32`... + //~| NOTE ... which causes `v` to have type `Vec` v.push(0); v.push(1i32); //~ ERROR mismatched types //~^ NOTE expected `i32`, found `u32` diff --git a/tests/ui/type/type-check/point-at-inference-3.rs b/tests/ui/type/type-check/point-at-inference-3.rs index 8bb1487420baa..a48c4f9862f75 100644 --- a/tests/ui/type/type-check/point-at-inference-3.rs +++ b/tests/ui/type/type-check/point-at-inference-3.rs @@ -2,6 +2,8 @@ fn main() { let mut v = Vec::new(); v.push(0i32); + //~^ NOTE this argument has type `i32`... + //~| NOTE ... which causes `v` to have type `Vec` v.push(0); v.push(1u32); //~ ERROR mismatched types //~^ NOTE expected `i32`, found `u32` diff --git a/tests/ui/type/type-check/point-at-inference-3.stderr b/tests/ui/type/type-check/point-at-inference-3.stderr index 32663bd632e12..238764812364c 100644 --- a/tests/ui/type/type-check/point-at-inference-3.stderr +++ b/tests/ui/type/type-check/point-at-inference-3.stderr @@ -1,6 +1,11 @@ error[E0308]: mismatched types - --> $DIR/point-at-inference-3.rs:6:12 + --> $DIR/point-at-inference-3.rs:8:12 | +LL | v.push(0i32); + | - ---- this argument has type `i32`... + | | + | ... which causes `v` to have type `Vec` +... LL | v.push(1u32); | ---- ^^^^ expected `i32`, found `u32` | | From 5cc475742151cd458091ca3c67598ddece3ee39b Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 3 Mar 2023 01:35:51 +0000 Subject: [PATCH 5/5] More accurate argument blames, add some comments --- compiler/rustc_hir_typeck/src/demand.rs | 75 +++++++++++++------ .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 5 +- .../type/type-check/point-at-inference-4.rs | 4 +- .../type-check/point-at-inference-4.stderr | 10 ++- 4 files changed, 69 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 9ffee56702329..13442c3164928 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -221,7 +221,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } /// 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 @@ impl<'a, 'tcx> FnCtxt<'a, '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 @@ impl<'a, 'tcx> FnCtxt<'a, '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` + // and `vec.push(1u32)`, we ideally want to deduce that the type of + // `vec` *should* have been `Vec`. 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 @@ impl<'a, 'tcx> FnCtxt<'a, '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 @@ impl<'a, 'tcx> FnCtxt<'a, '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 @@ 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)) { 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 @@ impl<'a, 'tcx> FnCtxt<'a, '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 @@ impl<'a, 'tcx> FnCtxt<'a, '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 @@ impl<'a, 'tcx> FnCtxt<'a, '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 @@ impl<'a, 'tcx> FnCtxt<'a, '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 @@ impl<'a, 'tcx> FnCtxt<'a, '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>), - 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 }, } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 9ecb78e2dda80..ea1b52daaa5e5 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -814,7 +814,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { 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(), + }, ); } diff --git a/tests/ui/type/type-check/point-at-inference-4.rs b/tests/ui/type/type-check/point-at-inference-4.rs index 7c25b91fd89e9..3deb234c2751e 100644 --- a/tests/ui/type/type-check/point-at-inference-4.rs +++ b/tests/ui/type/type-check/point-at-inference-4.rs @@ -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` + //~| NOTE this argument has type `i32`... + //~| NOTE ... which causes `s` to have type `S` //~| NOTE an argument is missing //~| HELP provide the argument + //~| HELP change the type of the numeric literal from `i32` to `u32` let t: S = s; //~^ ERROR mismatched types //~| NOTE expected `S`, found `S` diff --git a/tests/ui/type/type-check/point-at-inference-4.stderr b/tests/ui/type/type-check/point-at-inference-4.stderr index 6181af70c8ed4..5f7bb8b9367ec 100644 --- a/tests/ui/type/type-check/point-at-inference-4.stderr +++ b/tests/ui/type/type-check/point-at-inference-4.stderr @@ -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` + | - ---- this argument has type `i32`... + | | + | ... which causes `s` to have type `S` ... LL | let t: S = s; | --------- ^ expected `S`, found `S` @@ -27,6 +29,10 @@ LL | let t: S = s; | = note: expected struct `S` found struct `S` +help: change the type of the numeric literal from `i32` to `u32` + | +LL | s.infer(0u32); + | ~~~ error: aborting due to 2 previous errors