From 348386985d41852829c0785dd84e50c2780b7f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 12 Dec 2022 09:13:27 -0800 Subject: [PATCH] Move logic to their own methods --- .../traits/error_reporting/method_chain.rs | 74 ++ .../src/traits/error_reporting/mod.rs | 1 + .../src/traits/error_reporting/suggestions.rs | 678 ++++++++---------- 3 files changed, 391 insertions(+), 362 deletions(-) create mode 100644 compiler/rustc_trait_selection/src/traits/error_reporting/method_chain.rs diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/method_chain.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/method_chain.rs new file mode 100644 index 0000000000000..cb373d657721b --- /dev/null +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/method_chain.rs @@ -0,0 +1,74 @@ +use crate::infer::InferCtxt; + +use rustc_middle::ty::error::TypeError; +use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation}; +use rustc_middle::ty::{self, Ty, TyCtxt}; + +pub struct CollectAllMismatches<'a, 'tcx> { + pub infcx: &'a InferCtxt<'tcx>, + pub param_env: ty::ParamEnv<'tcx>, + pub errors: Vec>, +} + +impl<'a, 'tcx> TypeRelation<'tcx> for CollectAllMismatches<'a, 'tcx> { + fn tag(&self) -> &'static str { + "CollectAllMismatches" + } + fn tcx(&self) -> TyCtxt<'tcx> { + self.infcx.tcx + } + fn intercrate(&self) -> bool { + false + } + fn param_env(&self) -> ty::ParamEnv<'tcx> { + self.param_env + } + fn a_is_expected(&self) -> bool { + true + } // irrelevant + fn mark_ambiguous(&mut self) { + bug!() + } + fn relate_with_variance>( + &mut self, + _: ty::Variance, + _: ty::VarianceDiagInfo<'tcx>, + a: T, + b: T, + ) -> RelateResult<'tcx, T> { + self.relate(a, b) + } + fn regions( + &mut self, + a: ty::Region<'tcx>, + _b: ty::Region<'tcx>, + ) -> RelateResult<'tcx, ty::Region<'tcx>> { + Ok(a) + } + fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { + if a == b || matches!(a.kind(), ty::Infer(_)) || matches!(b.kind(), ty::Infer(_)) { + return Ok(a); + } + relate::super_relate_tys(self, a, b).or_else(|e| { + self.errors.push(e); + Ok(a) + }) + } + fn consts( + &mut self, + a: ty::Const<'tcx>, + b: ty::Const<'tcx>, + ) -> RelateResult<'tcx, ty::Const<'tcx>> { + if a == b { + return Ok(a); + } + relate::super_relate_consts(self, a, b) // could do something similar here for constants! + } + fn binders>( + &mut self, + a: ty::Binder<'tcx, T>, + b: ty::Binder<'tcx, T>, + ) -> RelateResult<'tcx, ty::Binder<'tcx, T>> { + Ok(a.rebind(self.relate(a.skip_binder(), b.skip_binder())?)) + } +} diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 78364253adbca..a0ed1fc751b9c 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -1,4 +1,5 @@ mod ambiguity; +pub mod method_chain; pub mod on_unimplemented; pub mod suggestions; diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index 162e73faa2a8a..df470f6208910 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -24,7 +24,7 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi use rustc_infer::infer::{InferOk, LateBoundRegionConversionTime}; use rustc_middle::hir::map; use rustc_middle::ty::error::TypeError::{self, Sorts}; -use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation}; +use rustc_middle::ty::relate::TypeRelation; use rustc_middle::ty::{ self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, DefIdTree, GeneratorDiagnosticData, GeneratorInteriorTypeCause, Infer, InferTy, InternalSubsts, @@ -36,6 +36,7 @@ use rustc_span::{BytePos, DesugaringKind, ExpnKind, Span, DUMMY_SP}; use rustc_target::spec::abi; use std::ops::Deref; +use super::method_chain::CollectAllMismatches; use super::InferCtxtPrivExt; use crate::infer::InferCtxtExt as _; use crate::traits::query::evaluate_obligation::InferCtxtExt as _; @@ -332,6 +333,23 @@ pub trait TypeErrCtxtExt<'tcx> { err: &mut Diagnostic, trait_pred: ty::PolyTraitPredicate<'tcx>, ); + fn function_argument_obligation( + &self, + arg_hir_id: HirId, + err: &mut Diagnostic, + parent_code: &ObligationCauseCode<'tcx>, + param_env: ty::ParamEnv<'tcx>, + predicate: ty::Predicate<'tcx>, + call_hir_id: HirId, + ); + fn point_at_chain( + &self, + expr: &hir::Expr<'_>, + typeck_results: &TypeckResults<'tcx>, + type_diffs: Vec>, + param_env: ty::ParamEnv<'tcx>, + err: &mut Diagnostic, + ); } fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) { @@ -2840,298 +2858,14 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { call_hir_id, ref parent_code, } => { - let hir = self.tcx.hir(); - if let Some(Node::Expr(expr)) = hir.find(arg_hir_id) { - let parent_id = hir.get_parent_item(arg_hir_id); - let typeck_results: &TypeckResults<'tcx> = match &self.typeck_results { - Some(t) if t.hir_owner == parent_id => t, - _ => self.tcx.typeck(parent_id.def_id), - }; - if let hir::Expr { kind: hir::ExprKind::Block(..), .. } = expr { - let expr = expr.peel_blocks(); - let ty = - typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error()); - let span = expr.span; - if Some(span) != err.span.primary_span() { - err.span_label( - span, - if ty.references_error() { - String::new() - } else { - let ty = with_forced_trimmed_paths!(self.ty_to_string(ty)); - format!("this tail expression is of type `{ty}`") - }, - ); - } - } - - let mut primary_spans = vec![]; - let mut span_labels = vec![]; - - // FIXME: visit the ty to see if there's any closure involved, and if there is, - // check whether its evaluated return type is the same as the one corresponding - // to an associated type (as seen from `trait_pred`) in the predicate. Like in - // trait_pred `S: Sum<::Item>` and predicate `i32: Sum<&()>` - let mut type_diffs = vec![]; - - if let ObligationCauseCode::ExprBindingObligation(def_id, _, _, idx) = parent_code.deref() - && let predicates = self.tcx.predicates_of(def_id).instantiate_identity(self.tcx) - && let Some(pred) = predicates.predicates.get(*idx) - && let ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) = pred.kind().skip_binder() - { - let mut c = CollectAllMismatches { - infcx: self.infcx, - param_env: param_env, - errors: vec![], - }; - if let ty::PredicateKind::Clause(ty::Clause::Trait( - predicate - )) = predicate.kind().skip_binder() - { - if let Ok(_) = c.relate(trait_pred, predicate) { - type_diffs = c.errors; - } - } - } - let point_at_chain = |expr: &hir::Expr<'_>| { - let mut assocs = vec![]; - // We still want to point at the different methods even if there hasn't - // been a change of assoc type. - let mut call_spans = vec![]; - let mut expr = expr; - let mut prev_ty = self.resolve_vars_if_possible( - typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error()), - ); - while let hir::ExprKind::MethodCall(_path_segment, rcvr_expr, _args, span) = - expr.kind - { - // Point at every method call in the chain with the resulting type. - // vec![1, 2, 3].iter().map(mapper).sum() - // ^^^^^^ ^^^^^^^^^^^ - expr = rcvr_expr; - let mut assocs_in_this_method = Vec::with_capacity(type_diffs.len()); - call_spans.push(span); - - let ocx = ObligationCtxt::new_in_snapshot(self.infcx); - for diff in &type_diffs { - let Sorts(expected_found) = diff else { continue; }; - let ty::Projection(proj) = expected_found.expected.kind() else { continue; }; - - let origin = TypeVariableOrigin { - kind: TypeVariableOriginKind::TypeInference, - span, - }; - let trait_def_id = proj.trait_def_id(self.tcx); - // Make `Self` be equivalent to the type of the call chain - // expression we're looking at now, so that we can tell what - // for example `Iterator::Item` is at this point in the chain. - let substs = - InternalSubsts::for_item(self.tcx, trait_def_id, |param, _| { - match param.kind { - ty::GenericParamDefKind::Type { .. } => { - if param.index == 0 { - return prev_ty.into(); - } - } - ty::GenericParamDefKind::Lifetime - | ty::GenericParamDefKind::Const { .. } => {} - } - self.var_for_def(span, param) - }); - // This will hold the resolved type of the associated type, if the - // current expression implements the trait that associated type is - // in. For example, this would be what `Iterator::Item` is here. - let ty_var = self.infcx.next_ty_var(origin); - // This corresponds to `::Item = _`. - let trait_ref = ty::Binder::dummy(ty::PredicateKind::Clause( - ty::Clause::Projection(ty::ProjectionPredicate { - projection_ty: ty::ProjectionTy { - substs, - item_def_id: proj.item_def_id, - }, - term: ty_var.into(), - }), - )); - // Add `::Item = _` obligation. - ocx.register_obligation(Obligation::misc( - self.tcx, - span, - expr.hir_id, - param_env, - trait_ref, - )); - if ocx.select_where_possible().is_empty() { - // `ty_var` now holds the type that `Item` is for `ExprTy`. - let ty_var = self.resolve_vars_if_possible(ty_var); - assocs_in_this_method - .push(Some((span, (proj.item_def_id, ty_var)))); - } else { - // `` didn't select, so likely we've - // reached the end of the iterator chain, like the originating - // `Vec<_>`. - // Keep the space consistent for later zipping. - assocs_in_this_method.push(None); - } - } - assocs.push(assocs_in_this_method); - prev_ty = self.resolve_vars_if_possible( - typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error()), - ); - - if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind - && let hir::Path { res: hir::def::Res::Local(hir_id), .. } = path - && let Some(hir::Node::Pat(binding)) = self.tcx.hir().find(*hir_id) - && let parent_hir_id = self.tcx.hir().get_parent_node(binding.hir_id) - && let Some(hir::Node::Local(local)) = self.tcx.hir().find(parent_hir_id) - && let Some(binding_expr) = local.init - { - // We've reached the root of the method call chain and it is a - // binding. Get the binding creation and try to continue the chain. - expr = binding_expr; - } - } - - // We want the type before deref coercions, otherwise we talk about `&[_]` - // instead of `Vec<_>`. - if let Some(ty) = typeck_results.expr_ty_opt(expr) { - let ty = with_forced_trimmed_paths!(self.ty_to_string(ty)); - // Point at the root expression - // vec![1, 2, 3].iter().map(mapper).sum() - // ^^^^^^^^^^^^^ - span_labels - .push((expr.span, format!("this expression has type `{ty}`"))); - }; - // Only show this if it is not a "trivial" expression (not a method - // chain) and there are associated types to talk about. - let mut assocs = assocs.into_iter().peekable(); - while let Some(assocs_in_method) = assocs.next() { - let Some(prev_assoc_in_method) = assocs.peek() else { - for entry in assocs_in_method { - let Some((span, (assoc, ty))) = entry else { continue; }; - if type_diffs.iter().any(|diff| { - let Sorts(expected_found) = diff else { return false; }; - self.can_eq(param_env, expected_found.found, ty).is_ok() - }) { - // FIXME: this doesn't quite work for `Iterator::collect` - // because we have `Vec` and `()`, but we'd want `i32` - // to point at the `.into_iter()` call, but as long as we - // still point at the other method calls that might have - // introduced the issue, this is fine for now. - primary_spans.push(span); - } - span_labels.push(( - span, - with_forced_trimmed_paths!(format!( - "`{}` is `{ty}` here", - self.tcx.def_path_str(assoc), - )), - )); - } - break; - }; - for (entry, prev_entry) in - assocs_in_method.into_iter().zip(prev_assoc_in_method.into_iter()) - { - match (entry, prev_entry) { - (Some((span, (assoc, ty))), Some((_, (_, prev_ty)))) => { - let ty_str = - with_forced_trimmed_paths!(self.ty_to_string(ty)); - - let assoc = with_forced_trimmed_paths!( - self.tcx.def_path_str(assoc) - ); - if ty != *prev_ty { - if type_diffs.iter().any(|diff| { - let Sorts(expected_found) = diff else { return false; }; - self.can_eq(param_env, expected_found.found, ty).is_ok() - }) { - primary_spans.push(span); - } - span_labels.push(( - span, - format!("`{assoc}` changed to `{ty_str}` here"), - )); - } else { - span_labels.push(( - span, - format!("`{assoc}` remains `{ty_str}` here"), - )); - } - } - (Some((span, (assoc, ty))), None) => { - span_labels.push(( - span, - with_forced_trimmed_paths!(format!( - "`{}` is `{}` here", - self.tcx.def_path_str(assoc), - self.ty_to_string(ty), - )), - )); - } - (None, Some(_)) | (None, None) => {} - } - } - } - for span in call_spans { - if span_labels.iter().find(|(s, _)| *s == span).is_none() { - // Ensure we are showing the entire chain, even if the assoc types - // haven't changed. - span_labels.push((span, String::new())); - } - } - if !primary_spans.is_empty() { - let mut multi_span: MultiSpan = primary_spans.into(); - for (span, label) in span_labels { - multi_span.push_span_label(span, label); - } - err.span_note( - multi_span, - format!( - "the method call chain might not have had the expected \ - associated types", - ), - ); - } - }; - if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind - && let hir::Path { res: hir::def::Res::Local(hir_id), .. } = path - && let Some(hir::Node::Pat(binding)) = self.tcx.hir().find(*hir_id) - && let parent_hir_id = self.tcx.hir().get_parent_node(binding.hir_id) - && let Some(hir::Node::Local(local)) = self.tcx.hir().find(parent_hir_id) - && let Some(binding_expr) = local.init - { - // If the expression we're calling on is a binding, we want to point at the - // `let` when talking about the type. Otherwise we'll point at every part - // of the method chain with the type. - point_at_chain(binding_expr); - } else { - point_at_chain(expr); - } - } - let call_node = hir.find(call_hir_id); - if let Some(Node::Expr(hir::Expr { - kind: hir::ExprKind::MethodCall(path, rcvr, ..), - .. - })) = call_node - { - if Some(rcvr.span) == err.span.primary_span() { - err.replace_span_with(path.ident.span); - } - } - if let Some(Node::Expr(hir::Expr { - kind: - hir::ExprKind::Call(hir::Expr { span, .. }, _) - | hir::ExprKind::MethodCall( - hir::PathSegment { ident: Ident { span, .. }, .. }, - .., - ), - .. - })) = hir.find(call_hir_id) - { - if Some(*span) != err.span.primary_span() { - err.span_label(*span, "required by a bound introduced by this call"); - } - } + self.function_argument_obligation( + arg_hir_id, + err, + parent_code, + param_env, + predicate, + call_hir_id, + ); ensure_sufficient_stack(|| { self.note_obligation_cause_code( err, @@ -3356,6 +3090,295 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ); } } + fn function_argument_obligation( + &self, + arg_hir_id: HirId, + err: &mut Diagnostic, + parent_code: &ObligationCauseCode<'tcx>, + param_env: ty::ParamEnv<'tcx>, + predicate: ty::Predicate<'tcx>, + call_hir_id: HirId, + ) { + let tcx = self.tcx; + let hir = tcx.hir(); + if let Some(Node::Expr(expr)) = hir.find(arg_hir_id) { + let parent_id = hir.get_parent_item(arg_hir_id); + let typeck_results: &TypeckResults<'tcx> = match &self.typeck_results { + Some(t) if t.hir_owner == parent_id => t, + _ => self.tcx.typeck(parent_id.def_id), + }; + if let hir::Expr { kind: hir::ExprKind::Block(..), .. } = expr { + let expr = expr.peel_blocks(); + let ty = typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error()); + let span = expr.span; + if Some(span) != err.span.primary_span() { + err.span_label( + span, + if ty.references_error() { + String::new() + } else { + let ty = with_forced_trimmed_paths!(self.ty_to_string(ty)); + format!("this tail expression is of type `{ty}`") + }, + ); + } + } + + // FIXME: visit the ty to see if there's any closure involved, and if there is, + // check whether its evaluated return type is the same as the one corresponding + // to an associated type (as seen from `trait_pred`) in the predicate. Like in + // trait_pred `S: Sum<::Item>` and predicate `i32: Sum<&()>` + let mut type_diffs = vec![]; + + if let ObligationCauseCode::ExprBindingObligation(def_id, _, _, idx) = parent_code.deref() + && let predicates = self.tcx.predicates_of(def_id).instantiate_identity(self.tcx) + && let Some(pred) = predicates.predicates.get(*idx) + && let ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) = pred.kind().skip_binder() + { + let mut c = CollectAllMismatches { + infcx: self.infcx, + param_env, + errors: vec![], + }; + if let ty::PredicateKind::Clause(ty::Clause::Trait( + predicate + )) = predicate.kind().skip_binder() + { + if let Ok(_) = c.relate(trait_pred, predicate) { + type_diffs = c.errors; + } + } + } + if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind + && let hir::Path { res: hir::def::Res::Local(hir_id), .. } = path + && let Some(hir::Node::Pat(binding)) = self.tcx.hir().find(*hir_id) + && let parent_hir_id = self.tcx.hir().get_parent_node(binding.hir_id) + && let Some(hir::Node::Local(local)) = self.tcx.hir().find(parent_hir_id) + && let Some(binding_expr) = local.init + { + // If the expression we're calling on is a binding, we want to point at the + // `let` when talking about the type. Otherwise we'll point at every part + // of the method chain with the type. + self.point_at_chain(binding_expr, typeck_results, type_diffs, param_env, err); + } else { + self.point_at_chain(expr, typeck_results, type_diffs, param_env, err); + } + } + let call_node = hir.find(call_hir_id); + if let Some(Node::Expr(hir::Expr { + kind: hir::ExprKind::MethodCall(path, rcvr, ..), .. + })) = call_node + { + if Some(rcvr.span) == err.span.primary_span() { + err.replace_span_with(path.ident.span); + } + } + if let Some(Node::Expr(hir::Expr { + kind: + hir::ExprKind::Call(hir::Expr { span, .. }, _) + | hir::ExprKind::MethodCall(hir::PathSegment { ident: Ident { span, .. }, .. }, ..), + .. + })) = hir.find(call_hir_id) + { + if Some(*span) != err.span.primary_span() { + err.span_label(*span, "required by a bound introduced by this call"); + } + } + } + + fn point_at_chain( + &self, + expr: &hir::Expr<'_>, + typeck_results: &TypeckResults<'tcx>, + type_diffs: Vec>, + param_env: ty::ParamEnv<'tcx>, + err: &mut Diagnostic, + ) { + let mut primary_spans = vec![]; + let mut span_labels = vec![]; + + let tcx = self.tcx; + + let mut assocs = vec![]; + // We still want to point at the different methods even if there hasn't + // been a change of assoc type. + let mut call_spans = vec![]; + let mut expr = expr; + let mut prev_ty = self.resolve_vars_if_possible( + typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error()), + ); + while let hir::ExprKind::MethodCall(_path_segment, rcvr_expr, _args, span) = expr.kind { + // Point at every method call in the chain with the resulting type. + // vec![1, 2, 3].iter().map(mapper).sum() + // ^^^^^^ ^^^^^^^^^^^ + expr = rcvr_expr; + let mut assocs_in_this_method = Vec::with_capacity(type_diffs.len()); + call_spans.push(span); + + let ocx = ObligationCtxt::new_in_snapshot(self.infcx); + for diff in &type_diffs { + let Sorts(expected_found) = diff else { continue; }; + let ty::Projection(proj) = expected_found.expected.kind() else { continue; }; + + let origin = + TypeVariableOrigin { kind: TypeVariableOriginKind::TypeInference, span }; + let trait_def_id = proj.trait_def_id(self.tcx); + // Make `Self` be equivalent to the type of the call chain + // expression we're looking at now, so that we can tell what + // for example `Iterator::Item` is at this point in the chain. + let substs = InternalSubsts::for_item(self.tcx, trait_def_id, |param, _| { + match param.kind { + ty::GenericParamDefKind::Type { .. } => { + if param.index == 0 { + return prev_ty.into(); + } + } + ty::GenericParamDefKind::Lifetime + | ty::GenericParamDefKind::Const { .. } => {} + } + self.var_for_def(span, param) + }); + // This will hold the resolved type of the associated type, if the + // current expression implements the trait that associated type is + // in. For example, this would be what `Iterator::Item` is here. + let ty_var = self.infcx.next_ty_var(origin); + // This corresponds to `::Item = _`. + let trait_ref = ty::Binder::dummy(ty::PredicateKind::Clause( + ty::Clause::Projection(ty::ProjectionPredicate { + projection_ty: ty::ProjectionTy { substs, item_def_id: proj.item_def_id }, + term: ty_var.into(), + }), + )); + // Add `::Item = _` obligation. + ocx.register_obligation(Obligation::misc( + self.tcx, + span, + expr.hir_id, + param_env, + trait_ref, + )); + if ocx.select_where_possible().is_empty() { + // `ty_var` now holds the type that `Item` is for `ExprTy`. + let ty_var = self.resolve_vars_if_possible(ty_var); + assocs_in_this_method.push(Some((span, (proj.item_def_id, ty_var)))); + } else { + // `` didn't select, so likely we've + // reached the end of the iterator chain, like the originating + // `Vec<_>`. + // Keep the space consistent for later zipping. + assocs_in_this_method.push(None); + } + } + assocs.push(assocs_in_this_method); + prev_ty = self.resolve_vars_if_possible( + typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error()), + ); + + if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind + && let hir::Path { res: hir::def::Res::Local(hir_id), .. } = path + && let Some(hir::Node::Pat(binding)) = self.tcx.hir().find(*hir_id) + && let parent_hir_id = self.tcx.hir().get_parent_node(binding.hir_id) + && let Some(hir::Node::Local(local)) = self.tcx.hir().find(parent_hir_id) + && let Some(binding_expr) = local.init + { + // We've reached the root of the method call chain and it is a + // binding. Get the binding creation and try to continue the chain. + expr = binding_expr; + } + } + // We want the type before deref coercions, otherwise we talk about `&[_]` + // instead of `Vec<_>`. + if let Some(ty) = typeck_results.expr_ty_opt(expr) { + let ty = with_forced_trimmed_paths!(self.ty_to_string(ty)); + // Point at the root expression + // vec![1, 2, 3].iter().map(mapper).sum() + // ^^^^^^^^^^^^^ + span_labels.push((expr.span, format!("this expression has type `{ty}`"))); + }; + // Only show this if it is not a "trivial" expression (not a method + // chain) and there are associated types to talk about. + let mut assocs = assocs.into_iter().peekable(); + while let Some(assocs_in_method) = assocs.next() { + let Some(prev_assoc_in_method) = assocs.peek() else { + for entry in assocs_in_method { + let Some((span, (assoc, ty))) = entry else { continue; }; + if type_diffs.iter().any(|diff| { + let Sorts(expected_found) = diff else { return false; }; + self.can_eq(param_env, expected_found.found, ty).is_ok() + }) { + // FIXME: this doesn't quite work for `Iterator::collect` + // because we have `Vec` and `()`, but we'd want `i32` + // to point at the `.into_iter()` call, but as long as we + // still point at the other method calls that might have + // introduced the issue, this is fine for now. + primary_spans.push(span); + } + span_labels.push(( + span, + with_forced_trimmed_paths!(format!( + "`{}` is `{ty}` here", + self.tcx.def_path_str(assoc), + )), + )); + } + break; + }; + for (entry, prev_entry) in + assocs_in_method.into_iter().zip(prev_assoc_in_method.into_iter()) + { + match (entry, prev_entry) { + (Some((span, (assoc, ty))), Some((_, (_, prev_ty)))) => { + let ty_str = with_forced_trimmed_paths!(self.ty_to_string(ty)); + + let assoc = with_forced_trimmed_paths!(self.tcx.def_path_str(assoc)); + if ty != *prev_ty { + if type_diffs.iter().any(|diff| { + let Sorts(expected_found) = diff else { return false; }; + self.can_eq(param_env, expected_found.found, ty).is_ok() + }) { + primary_spans.push(span); + } + span_labels + .push((span, format!("`{assoc}` changed to `{ty_str}` here"))); + } else { + span_labels.push((span, format!("`{assoc}` remains `{ty_str}` here"))); + } + } + (Some((span, (assoc, ty))), None) => { + span_labels.push(( + span, + with_forced_trimmed_paths!(format!( + "`{}` is `{}` here", + self.tcx.def_path_str(assoc), + self.ty_to_string(ty), + )), + )); + } + (None, Some(_)) | (None, None) => {} + } + } + } + for span in call_spans { + if span_labels.iter().find(|(s, _)| *s == span).is_none() { + // Ensure we are showing the entire chain, even if the assoc types + // haven't changed. + span_labels.push((span, String::new())); + } + } + if !primary_spans.is_empty() { + let mut multi_span: MultiSpan = primary_spans.into(); + for (span, label) in span_labels { + multi_span.push_span_label(span, label); + } + err.span_note( + multi_span, + format!( + "the method call chain might not have had the expected \ + associated types", + ), + ); + } + } } /// Collect all the returned expressions within the input expression. @@ -3543,72 +3566,3 @@ impl<'tcx> TypeFolder<'tcx> for ReplaceImplTraitFolder<'tcx> { self.tcx } } - -pub struct CollectAllMismatches<'a, 'tcx> { - pub infcx: &'a InferCtxt<'tcx>, - pub param_env: ty::ParamEnv<'tcx>, - pub errors: Vec>, -} - -impl<'a, 'tcx> TypeRelation<'tcx> for CollectAllMismatches<'a, 'tcx> { - fn tag(&self) -> &'static str { - "CollectAllMismatches" - } - fn tcx(&self) -> TyCtxt<'tcx> { - self.infcx.tcx - } - fn intercrate(&self) -> bool { - false - } - fn param_env(&self) -> ty::ParamEnv<'tcx> { - self.param_env - } - fn a_is_expected(&self) -> bool { - true - } // irrelevant - fn mark_ambiguous(&mut self) { - bug!() - } - fn relate_with_variance>( - &mut self, - _: ty::Variance, - _: ty::VarianceDiagInfo<'tcx>, - a: T, - b: T, - ) -> RelateResult<'tcx, T> { - self.relate(a, b) - } - fn regions( - &mut self, - a: ty::Region<'tcx>, - _b: ty::Region<'tcx>, - ) -> RelateResult<'tcx, ty::Region<'tcx>> { - Ok(a) - } - fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { - if a == b || matches!(a.kind(), ty::Infer(_)) || matches!(b.kind(), ty::Infer(_)) { - return Ok(a); - } - relate::super_relate_tys(self, a, b).or_else(|e| { - self.errors.push(e); - Ok(a) - }) - } - fn consts( - &mut self, - a: ty::Const<'tcx>, - b: ty::Const<'tcx>, - ) -> RelateResult<'tcx, ty::Const<'tcx>> { - if a == b { - return Ok(a); - } - relate::super_relate_consts(self, a, b) // could do something similar here for constants! - } - fn binders>( - &mut self, - a: ty::Binder<'tcx, T>, - b: ty::Binder<'tcx, T>, - ) -> RelateResult<'tcx, ty::Binder<'tcx, T>> { - Ok(a.rebind(self.relate(a.skip_binder(), b.skip_binder())?)) - } -}