| use crate::errors::{ |
| AmbiguousImpl, AmbiguousReturn, AnnotationRequired, InferenceBadError, |
| SourceKindMultiSuggestion, SourceKindSubdiag, |
| }; |
| use crate::infer::error_reporting::TypeErrCtxt; |
| use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; |
| use crate::infer::InferCtxt; |
| use rustc_errors::IntoDiagnostic; |
| use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed, IntoDiagnosticArg}; |
| use rustc_hir as hir; |
| use rustc_hir::def::Res; |
| use rustc_hir::def::{CtorOf, DefKind, Namespace}; |
| use rustc_hir::def_id::{DefId, LocalDefId}; |
| use rustc_hir::intravisit::{self, Visitor}; |
| use rustc_hir::{Body, Closure, Expr, ExprKind, FnRetTy, HirId, Local, LocalSource}; |
| use rustc_middle::hir::nested_filter; |
| use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; |
| use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; |
| use rustc_middle::ty::print::{FmtPrinter, PrettyPrinter, Print, Printer}; |
| use rustc_middle::ty::{self, InferConst}; |
| use rustc_middle::ty::{GenericArg, GenericArgKind, GenericArgsRef}; |
| use rustc_middle::ty::{IsSuggestable, Ty, TyCtxt, TypeckResults}; |
| use rustc_span::symbol::{kw, sym, Ident}; |
| use rustc_span::{BytePos, Span}; |
| use std::borrow::Cow; |
| use std::iter; |
| |
| pub enum TypeAnnotationNeeded { |
| /// ```compile_fail,E0282 |
| /// let x; |
| /// ``` |
| E0282, |
| /// An implementation cannot be chosen unambiguously because of lack of information. |
| /// ```compile_fail,E0790 |
| /// let _ = Default::default(); |
| /// ``` |
| E0283, |
| /// ```compile_fail,E0284 |
| /// let mut d: u64 = 2; |
| /// d = d % 1u32.into(); |
| /// ``` |
| E0284, |
| } |
| |
| impl Into<rustc_errors::DiagnosticId> for TypeAnnotationNeeded { |
| fn into(self) -> rustc_errors::DiagnosticId { |
| match self { |
| Self::E0282 => rustc_errors::error_code!(E0282), |
| Self::E0283 => rustc_errors::error_code!(E0283), |
| Self::E0284 => rustc_errors::error_code!(E0284), |
| } |
| } |
| } |
| |
| /// Information about a constant or a type containing inference variables. |
| pub struct InferenceDiagnosticsData { |
| pub name: String, |
| pub span: Option<Span>, |
| pub kind: UnderspecifiedArgKind, |
| pub parent: Option<InferenceDiagnosticsParentData>, |
| } |
| |
| /// Data on the parent definition where a generic argument was declared. |
| pub struct InferenceDiagnosticsParentData { |
| prefix: &'static str, |
| name: String, |
| } |
| |
| #[derive(Clone)] |
| pub enum UnderspecifiedArgKind { |
| Type { prefix: Cow<'static, str> }, |
| Const { is_parameter: bool }, |
| } |
| |
| impl InferenceDiagnosticsData { |
| fn can_add_more_info(&self) -> bool { |
| !(self.name == "_" && matches!(self.kind, UnderspecifiedArgKind::Type { .. })) |
| } |
| |
| fn where_x_is_kind(&self, in_type: Ty<'_>) -> &'static str { |
| if in_type.is_ty_or_numeric_infer() { |
| "" |
| } else if self.name == "_" { |
| // FIXME: Consider specializing this message if there is a single `_` |
| // in the type. |
| "underscore" |
| } else { |
| "has_name" |
| } |
| } |
| |
| /// Generate a label for a generic argument which can't be inferred. When not |
| /// much is known about the argument, `use_diag` may be used to describe the |
| /// labeled value. |
| fn make_bad_error(&self, span: Span) -> InferenceBadError<'_> { |
| let has_parent = self.parent.is_some(); |
| let bad_kind = if self.can_add_more_info() { "more_info" } else { "other" }; |
| let (parent_prefix, parent_name) = self |
| .parent |
| .as_ref() |
| .map(|parent| (parent.prefix, parent.name.clone())) |
| .unwrap_or_default(); |
| InferenceBadError { |
| span, |
| bad_kind, |
| prefix_kind: self.kind.clone(), |
| prefix: self.kind.try_get_prefix().unwrap_or_default(), |
| name: self.name.clone(), |
| has_parent, |
| parent_prefix, |
| parent_name, |
| } |
| } |
| } |
| |
| impl InferenceDiagnosticsParentData { |
| fn for_parent_def_id( |
| tcx: TyCtxt<'_>, |
| parent_def_id: DefId, |
| ) -> Option<InferenceDiagnosticsParentData> { |
| let parent_name = |
| tcx.def_key(parent_def_id).disambiguated_data.data.get_opt_name()?.to_string(); |
| |
| Some(InferenceDiagnosticsParentData { |
| prefix: tcx.def_descr(parent_def_id), |
| name: parent_name, |
| }) |
| } |
| |
| fn for_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option<InferenceDiagnosticsParentData> { |
| Self::for_parent_def_id(tcx, tcx.parent(def_id)) |
| } |
| } |
| |
| impl IntoDiagnosticArg for UnderspecifiedArgKind { |
| fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> { |
| let kind = match self { |
| Self::Type { .. } => "type", |
| Self::Const { is_parameter: true } => "const_with_param", |
| Self::Const { is_parameter: false } => "const", |
| }; |
| rustc_errors::DiagnosticArgValue::Str(kind.into()) |
| } |
| } |
| |
| impl UnderspecifiedArgKind { |
| fn try_get_prefix(&self) -> Option<&str> { |
| match self { |
| Self::Type { prefix } => Some(prefix.as_ref()), |
| Self::Const { .. } => None, |
| } |
| } |
| } |
| |
| fn fmt_printer<'a, 'tcx>(infcx: &'a InferCtxt<'tcx>, ns: Namespace) -> FmtPrinter<'a, 'tcx> { |
| let mut printer = FmtPrinter::new(infcx.tcx, ns); |
| let ty_getter = move |ty_vid| { |
| if infcx.probe_ty_var(ty_vid).is_ok() { |
| warn!("resolved ty var in error message"); |
| } |
| |
| let mut infcx_inner = infcx.inner.borrow_mut(); |
| let ty_vars = infcx_inner.type_variables(); |
| let var_origin = ty_vars.var_origin(ty_vid); |
| if let TypeVariableOriginKind::TypeParameterDefinition(name, def_id) = var_origin.kind |
| && name != kw::SelfUpper |
| && !var_origin.span.from_expansion() |
| { |
| let generics = infcx.tcx.generics_of(infcx.tcx.parent(def_id)); |
| let idx = generics.param_def_id_to_index(infcx.tcx, def_id).unwrap(); |
| let generic_param_def = generics.param_at(idx as usize, infcx.tcx); |
| if let ty::GenericParamDefKind::Type { synthetic: true, .. } = generic_param_def.kind { |
| None |
| } else { |
| Some(name) |
| } |
| } else { |
| None |
| } |
| }; |
| printer.ty_infer_name_resolver = Some(Box::new(ty_getter)); |
| let const_getter = move |ct_vid| { |
| if infcx.probe_const_var(ct_vid).is_ok() { |
| warn!("resolved const var in error message"); |
| } |
| if let ConstVariableOriginKind::ConstParameterDefinition(name, _) = |
| infcx.inner.borrow_mut().const_unification_table().probe_value(ct_vid).origin.kind |
| { |
| return Some(name); |
| } else { |
| None |
| } |
| }; |
| printer.const_infer_name_resolver = Some(Box::new(const_getter)); |
| printer |
| } |
| |
| fn ty_to_string<'tcx>( |
| infcx: &InferCtxt<'tcx>, |
| ty: Ty<'tcx>, |
| called_method_def_id: Option<DefId>, |
| ) -> String { |
| let mut printer = fmt_printer(infcx, Namespace::TypeNS); |
| let ty = infcx.resolve_vars_if_possible(ty); |
| match (ty.kind(), called_method_def_id) { |
| // We don't want the regular output for `fn`s because it includes its path in |
| // invalid pseudo-syntax, we want the `fn`-pointer output instead. |
| (ty::FnDef(..), _) => { |
| ty.fn_sig(infcx.tcx).print(&mut printer).unwrap(); |
| printer.into_buffer() |
| } |
| (_, Some(def_id)) |
| if ty.is_ty_or_numeric_infer() |
| && infcx.tcx.get_diagnostic_item(sym::iterator_collect_fn) == Some(def_id) => |
| { |
| "Vec<_>".to_string() |
| } |
| _ if ty.is_ty_or_numeric_infer() => "/* Type */".to_string(), |
| // FIXME: The same thing for closures, but this only works when the closure |
| // does not capture anything. |
| // |
| // We do have to hide the `extern "rust-call"` ABI in that case though, |
| // which is too much of a bother for now. |
| _ => { |
| ty.print(&mut printer).unwrap(); |
| printer.into_buffer() |
| } |
| } |
| } |
| |
| /// We don't want to directly use `ty_to_string` for closures as their type isn't really |
| /// something users are familiar with. Directly printing the `fn_sig` of closures also |
| /// doesn't work as they actually use the "rust-call" API. |
| fn closure_as_fn_str<'tcx>(infcx: &InferCtxt<'tcx>, ty: Ty<'tcx>) -> String { |
| let ty::Closure(_, args) = ty.kind() else { unreachable!() }; |
| let fn_sig = args.as_closure().sig(); |
| let args = fn_sig |
| .inputs() |
| .skip_binder() |
| .iter() |
| .next() |
| .map(|args| { |
| args.tuple_fields() |
| .iter() |
| .map(|arg| ty_to_string(infcx, arg, None)) |
| .collect::<Vec<_>>() |
| .join(", ") |
| }) |
| .unwrap_or_default(); |
| let ret = if fn_sig.output().skip_binder().is_unit() { |
| String::new() |
| } else { |
| format!(" -> {}", ty_to_string(infcx, fn_sig.output().skip_binder(), None)) |
| }; |
| format!("fn({args}){ret}") |
| } |
| |
| impl<'tcx> InferCtxt<'tcx> { |
| /// Extracts data used by diagnostic for either types or constants |
| /// which were stuck during inference. |
| pub fn extract_inference_diagnostics_data( |
| &self, |
| arg: GenericArg<'tcx>, |
| highlight: Option<ty::print::RegionHighlightMode<'tcx>>, |
| ) -> InferenceDiagnosticsData { |
| match arg.unpack() { |
| GenericArgKind::Type(ty) => { |
| if let ty::Infer(ty::TyVar(ty_vid)) = *ty.kind() { |
| let mut inner = self.inner.borrow_mut(); |
| let ty_vars = &inner.type_variables(); |
| let var_origin = ty_vars.var_origin(ty_vid); |
| if let TypeVariableOriginKind::TypeParameterDefinition(name, def_id) = |
| var_origin.kind |
| { |
| if name != kw::SelfUpper && !var_origin.span.from_expansion() { |
| return InferenceDiagnosticsData { |
| name: name.to_string(), |
| span: Some(var_origin.span), |
| kind: UnderspecifiedArgKind::Type { |
| prefix: "type parameter".into(), |
| }, |
| parent: InferenceDiagnosticsParentData::for_def_id( |
| self.tcx, def_id, |
| ), |
| }; |
| } |
| } |
| } |
| |
| let mut printer = ty::print::FmtPrinter::new(self.tcx, Namespace::TypeNS); |
| if let Some(highlight) = highlight { |
| printer.region_highlight_mode = highlight; |
| } |
| ty.print(&mut printer).unwrap(); |
| InferenceDiagnosticsData { |
| name: printer.into_buffer(), |
| span: None, |
| kind: UnderspecifiedArgKind::Type { prefix: ty.prefix_string(self.tcx) }, |
| parent: None, |
| } |
| } |
| GenericArgKind::Const(ct) => { |
| if let ty::ConstKind::Infer(InferConst::Var(vid)) = ct.kind() { |
| let origin = |
| self.inner.borrow_mut().const_unification_table().probe_value(vid).origin; |
| if let ConstVariableOriginKind::ConstParameterDefinition(name, def_id) = |
| origin.kind |
| { |
| return InferenceDiagnosticsData { |
| name: name.to_string(), |
| span: Some(origin.span), |
| kind: UnderspecifiedArgKind::Const { is_parameter: true }, |
| parent: InferenceDiagnosticsParentData::for_def_id(self.tcx, def_id), |
| }; |
| } |
| |
| debug_assert!(!origin.span.is_dummy()); |
| let mut printer = ty::print::FmtPrinter::new(self.tcx, Namespace::ValueNS); |
| if let Some(highlight) = highlight { |
| printer.region_highlight_mode = highlight; |
| } |
| ct.print(&mut printer).unwrap(); |
| InferenceDiagnosticsData { |
| name: printer.into_buffer(), |
| span: Some(origin.span), |
| kind: UnderspecifiedArgKind::Const { is_parameter: false }, |
| parent: None, |
| } |
| } else { |
| // If we end up here the `FindInferSourceVisitor` |
| // won't work, as its expected argument isn't an inference variable. |
| // |
| // FIXME: Ideally we should look into the generic constant |
| // to figure out which inference var is actually unresolved so that |
| // this path is unreachable. |
| let mut printer = ty::print::FmtPrinter::new(self.tcx, Namespace::ValueNS); |
| if let Some(highlight) = highlight { |
| printer.region_highlight_mode = highlight; |
| } |
| ct.print(&mut printer).unwrap(); |
| InferenceDiagnosticsData { |
| name: printer.into_buffer(), |
| span: None, |
| kind: UnderspecifiedArgKind::Const { is_parameter: false }, |
| parent: None, |
| } |
| } |
| } |
| GenericArgKind::Lifetime(_) => bug!("unexpected lifetime"), |
| } |
| } |
| |
| /// Used as a fallback in [TypeErrCtxt::emit_inference_failure_err] |
| /// in case we weren't able to get a better error. |
| fn bad_inference_failure_err( |
| &self, |
| span: Span, |
| arg_data: InferenceDiagnosticsData, |
| error_code: TypeAnnotationNeeded, |
| ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { |
| let source_kind = "other"; |
| let source_name = ""; |
| let failure_span = None; |
| let infer_subdiags = Vec::new(); |
| let multi_suggestions = Vec::new(); |
| let bad_label = Some(arg_data.make_bad_error(span)); |
| match error_code { |
| TypeAnnotationNeeded::E0282 => AnnotationRequired { |
| span, |
| source_kind, |
| source_name, |
| failure_span, |
| infer_subdiags, |
| multi_suggestions, |
| bad_label, |
| } |
| .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic), |
| TypeAnnotationNeeded::E0283 => AmbiguousImpl { |
| span, |
| source_kind, |
| source_name, |
| failure_span, |
| infer_subdiags, |
| multi_suggestions, |
| bad_label, |
| } |
| .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic), |
| TypeAnnotationNeeded::E0284 => AmbiguousReturn { |
| span, |
| source_kind, |
| source_name, |
| failure_span, |
| infer_subdiags, |
| multi_suggestions, |
| bad_label, |
| } |
| .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic), |
| } |
| } |
| } |
| |
| impl<'tcx> TypeErrCtxt<'_, 'tcx> { |
| #[instrument(level = "debug", skip(self, error_code))] |
| pub fn emit_inference_failure_err( |
| &self, |
| body_def_id: LocalDefId, |
| failure_span: Span, |
| arg: GenericArg<'tcx>, |
| error_code: TypeAnnotationNeeded, |
| should_label_span: bool, |
| ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { |
| let arg = self.resolve_vars_if_possible(arg); |
| let arg_data = self.extract_inference_diagnostics_data(arg, None); |
| |
| let Some(typeck_results) = &self.typeck_results else { |
| // If we don't have any typeck results we're outside |
| // of a body, so we won't be able to get better info |
| // here. |
| return self.bad_inference_failure_err(failure_span, arg_data, error_code); |
| }; |
| |
| let mut local_visitor = FindInferSourceVisitor::new(&self, typeck_results, arg); |
| if let Some(body_id) = self.tcx.hir().maybe_body_owned_by( |
| self.tcx.typeck_root_def_id(body_def_id.to_def_id()).expect_local(), |
| ) { |
| let expr = self.tcx.hir().body(body_id).value; |
| local_visitor.visit_expr(expr); |
| } |
| |
| let Some(InferSource { span, kind }) = local_visitor.infer_source else { |
| return self.bad_inference_failure_err(failure_span, arg_data, error_code); |
| }; |
| |
| let (source_kind, name) = kind.ty_localized_msg(self); |
| let failure_span = if should_label_span && !failure_span.overlaps(span) { |
| Some(failure_span) |
| } else { |
| None |
| }; |
| |
| let mut infer_subdiags = Vec::new(); |
| let mut multi_suggestions = Vec::new(); |
| match kind { |
| InferSourceKind::LetBinding { insert_span, pattern_name, ty, def_id } => { |
| infer_subdiags.push(SourceKindSubdiag::LetLike { |
| span: insert_span, |
| name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new), |
| x_kind: arg_data.where_x_is_kind(ty), |
| prefix_kind: arg_data.kind.clone(), |
| prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), |
| arg_name: arg_data.name, |
| kind: if pattern_name.is_some() { "with_pattern" } else { "other" }, |
| type_name: ty_to_string(self, ty, def_id), |
| }); |
| } |
| InferSourceKind::ClosureArg { insert_span, ty } => { |
| infer_subdiags.push(SourceKindSubdiag::LetLike { |
| span: insert_span, |
| name: String::new(), |
| x_kind: arg_data.where_x_is_kind(ty), |
| prefix_kind: arg_data.kind.clone(), |
| prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), |
| arg_name: arg_data.name, |
| kind: "closure", |
| type_name: ty_to_string(self, ty, None), |
| }); |
| } |
| InferSourceKind::GenericArg { |
| insert_span, |
| argument_index, |
| generics_def_id, |
| def_id: _, |
| generic_args, |
| have_turbofish, |
| } => { |
| let generics = self.tcx.generics_of(generics_def_id); |
| let is_type = matches!(arg.unpack(), GenericArgKind::Type(_)); |
| |
| let (parent_exists, parent_prefix, parent_name) = |
| InferenceDiagnosticsParentData::for_parent_def_id(self.tcx, generics_def_id) |
| .map_or((false, String::new(), String::new()), |parent| { |
| (true, parent.prefix.to_string(), parent.name) |
| }); |
| |
| infer_subdiags.push(SourceKindSubdiag::GenericLabel { |
| span, |
| is_type, |
| param_name: generics.params[argument_index].name.to_string(), |
| parent_exists, |
| parent_prefix, |
| parent_name, |
| }); |
| |
| let args = if self.infcx.tcx.get_diagnostic_item(sym::iterator_collect_fn) |
| == Some(generics_def_id) |
| { |
| "Vec<_>".to_string() |
| } else { |
| let mut printer = fmt_printer(self, Namespace::TypeNS); |
| printer |
| .comma_sep(generic_args.iter().copied().map(|arg| { |
| if arg.is_suggestable(self.tcx, true) { |
| return arg; |
| } |
| |
| match arg.unpack() { |
| GenericArgKind::Lifetime(_) => bug!("unexpected lifetime"), |
| GenericArgKind::Type(_) => self |
| .next_ty_var(TypeVariableOrigin { |
| span: rustc_span::DUMMY_SP, |
| kind: TypeVariableOriginKind::MiscVariable, |
| }) |
| .into(), |
| GenericArgKind::Const(arg) => self |
| .next_const_var( |
| arg.ty(), |
| ConstVariableOrigin { |
| span: rustc_span::DUMMY_SP, |
| kind: ConstVariableOriginKind::MiscVariable, |
| }, |
| ) |
| .into(), |
| } |
| })) |
| .unwrap(); |
| printer.into_buffer() |
| }; |
| |
| if !have_turbofish { |
| infer_subdiags.push(SourceKindSubdiag::GenericSuggestion { |
| span: insert_span, |
| arg_count: generic_args.len(), |
| args, |
| }); |
| } |
| } |
| InferSourceKind::FullyQualifiedMethodCall { receiver, successor, args, def_id } => { |
| let mut printer = fmt_printer(self, Namespace::ValueNS); |
| printer.print_def_path(def_id, args).unwrap(); |
| let def_path = printer.into_buffer(); |
| |
| // We only care about whether we have to add `&` or `&mut ` for now. |
| // This is the case if the last adjustment is a borrow and the |
| // first adjustment was not a builtin deref. |
| let adjustment = match typeck_results.expr_adjustments(receiver) { |
| [ |
| Adjustment { kind: Adjust::Deref(None), target: _ }, |
| .., |
| Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(..)), target: _ }, |
| ] => "", |
| [ |
| .., |
| Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(_, mut_)), target: _ }, |
| ] => hir::Mutability::from(*mut_).ref_prefix_str(), |
| _ => "", |
| }; |
| |
| multi_suggestions.push(SourceKindMultiSuggestion::new_fully_qualified( |
| receiver.span, |
| def_path, |
| adjustment, |
| successor, |
| )); |
| } |
| InferSourceKind::ClosureReturn { ty, data, should_wrap_expr } => { |
| let ty_info = ty_to_string(self, ty, None); |
| multi_suggestions.push(SourceKindMultiSuggestion::new_closure_return( |
| ty_info, |
| data, |
| should_wrap_expr, |
| )); |
| } |
| } |
| match error_code { |
| TypeAnnotationNeeded::E0282 => AnnotationRequired { |
| span, |
| source_kind, |
| source_name: &name, |
| failure_span, |
| infer_subdiags, |
| multi_suggestions, |
| bad_label: None, |
| } |
| .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic), |
| TypeAnnotationNeeded::E0283 => AmbiguousImpl { |
| span, |
| source_kind, |
| source_name: &name, |
| failure_span, |
| infer_subdiags, |
| multi_suggestions, |
| bad_label: None, |
| } |
| .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic), |
| TypeAnnotationNeeded::E0284 => AmbiguousReturn { |
| span, |
| source_kind, |
| source_name: &name, |
| failure_span, |
| infer_subdiags, |
| multi_suggestions, |
| bad_label: None, |
| } |
| .into_diagnostic(&self.tcx.sess.parse_sess.span_diagnostic), |
| } |
| } |
| } |
| |
| #[derive(Debug)] |
| struct InferSource<'tcx> { |
| span: Span, |
| kind: InferSourceKind<'tcx>, |
| } |
| |
| #[derive(Debug)] |
| enum InferSourceKind<'tcx> { |
| LetBinding { |
| insert_span: Span, |
| pattern_name: Option<Ident>, |
| ty: Ty<'tcx>, |
| def_id: Option<DefId>, |
| }, |
| ClosureArg { |
| insert_span: Span, |
| ty: Ty<'tcx>, |
| }, |
| GenericArg { |
| insert_span: Span, |
| argument_index: usize, |
| generics_def_id: DefId, |
| def_id: DefId, |
| generic_args: &'tcx [GenericArg<'tcx>], |
| have_turbofish: bool, |
| }, |
| FullyQualifiedMethodCall { |
| receiver: &'tcx Expr<'tcx>, |
| /// If the method has other arguments, this is ", " and the start of the first argument, |
| /// while for methods without arguments this is ")" and the end of the method call. |
| successor: (&'static str, BytePos), |
| args: GenericArgsRef<'tcx>, |
| def_id: DefId, |
| }, |
| ClosureReturn { |
| ty: Ty<'tcx>, |
| data: &'tcx FnRetTy<'tcx>, |
| should_wrap_expr: Option<Span>, |
| }, |
| } |
| |
| impl<'tcx> InferSource<'tcx> { |
| fn from_expansion(&self) -> bool { |
| let source_from_expansion = match self.kind { |
| InferSourceKind::LetBinding { insert_span, .. } |
| | InferSourceKind::ClosureArg { insert_span, .. } |
| | InferSourceKind::GenericArg { insert_span, .. } => insert_span.from_expansion(), |
| InferSourceKind::FullyQualifiedMethodCall { receiver, .. } => { |
| receiver.span.from_expansion() |
| } |
| InferSourceKind::ClosureReturn { data, should_wrap_expr, .. } => { |
| data.span().from_expansion() || should_wrap_expr.is_some_and(Span::from_expansion) |
| } |
| }; |
| source_from_expansion || self.span.from_expansion() |
| } |
| } |
| |
| impl<'tcx> InferSourceKind<'tcx> { |
| fn ty_localized_msg(&self, infcx: &InferCtxt<'tcx>) -> (&'static str, String) { |
| match *self { |
| InferSourceKind::LetBinding { ty, .. } |
| | InferSourceKind::ClosureArg { ty, .. } |
| | InferSourceKind::ClosureReturn { ty, .. } => { |
| if ty.is_closure() { |
| ("closure", closure_as_fn_str(infcx, ty)) |
| } else if !ty.is_ty_or_numeric_infer() { |
| ("normal", ty_to_string(infcx, ty, None)) |
| } else { |
| ("other", String::new()) |
| } |
| } |
| // FIXME: We should be able to add some additional info here. |
| InferSourceKind::GenericArg { .. } |
| | InferSourceKind::FullyQualifiedMethodCall { .. } => ("other", String::new()), |
| } |
| } |
| } |
| |
| #[derive(Debug)] |
| struct InsertableGenericArgs<'tcx> { |
| insert_span: Span, |
| args: GenericArgsRef<'tcx>, |
| generics_def_id: DefId, |
| def_id: DefId, |
| have_turbofish: bool, |
| } |
| |
| /// A visitor which searches for the "best" spot to use in the inference error. |
| /// |
| /// For this it walks over the hir body and tries to check all places where |
| /// inference variables could be bound. |
| /// |
| /// While doing so, the currently best spot is stored in `infer_source`. |
| /// For details on how we rank spots, see [Self::source_cost] |
| struct FindInferSourceVisitor<'a, 'tcx> { |
| infcx: &'a InferCtxt<'tcx>, |
| typeck_results: &'a TypeckResults<'tcx>, |
| |
| target: GenericArg<'tcx>, |
| |
| attempt: usize, |
| infer_source_cost: usize, |
| infer_source: Option<InferSource<'tcx>>, |
| } |
| |
| impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { |
| fn new( |
| infcx: &'a InferCtxt<'tcx>, |
| typeck_results: &'a TypeckResults<'tcx>, |
| target: GenericArg<'tcx>, |
| ) -> Self { |
| FindInferSourceVisitor { |
| infcx, |
| typeck_results, |
| |
| target, |
| |
| attempt: 0, |
| infer_source_cost: usize::MAX, |
| infer_source: None, |
| } |
| } |
| |
| /// Computes cost for the given source. |
| /// |
| /// Sources with a small cost are prefer and should result |
| /// in a clearer and idiomatic suggestion. |
| fn source_cost(&self, source: &InferSource<'tcx>) -> usize { |
| #[derive(Clone, Copy)] |
| struct CostCtxt<'tcx> { |
| tcx: TyCtxt<'tcx>, |
| } |
| impl<'tcx> CostCtxt<'tcx> { |
| fn arg_cost(self, arg: GenericArg<'tcx>) -> usize { |
| match arg.unpack() { |
| GenericArgKind::Lifetime(_) => 0, // erased |
| GenericArgKind::Type(ty) => self.ty_cost(ty), |
| GenericArgKind::Const(_) => 3, // some non-zero value |
| } |
| } |
| fn ty_cost(self, ty: Ty<'tcx>) -> usize { |
| match *ty.kind() { |
| ty::Closure(..) => 1000, |
| ty::FnDef(..) => 150, |
| ty::FnPtr(..) => 30, |
| ty::Adt(def, args) => { |
| 5 + self |
| .tcx |
| .generics_of(def.did()) |
| .own_args_no_defaults(self.tcx, args) |
| .iter() |
| .map(|&arg| self.arg_cost(arg)) |
| .sum::<usize>() |
| } |
| ty::Tuple(args) => 5 + args.iter().map(|arg| self.ty_cost(arg)).sum::<usize>(), |
| ty::Ref(_, ty, _) => 2 + self.ty_cost(ty), |
| ty::Infer(..) => 0, |
| _ => 1, |
| } |
| } |
| } |
| |
| // The sources are listed in order of preference here. |
| let tcx = self.infcx.tcx; |
| let ctx = CostCtxt { tcx }; |
| match source.kind { |
| InferSourceKind::LetBinding { ty, .. } => ctx.ty_cost(ty), |
| InferSourceKind::ClosureArg { ty, .. } => ctx.ty_cost(ty), |
| InferSourceKind::GenericArg { def_id, generic_args, .. } => { |
| let variant_cost = match tcx.def_kind(def_id) { |
| // `None::<u32>` and friends are ugly. |
| DefKind::Variant | DefKind::Ctor(CtorOf::Variant, _) => 15, |
| _ => 10, |
| }; |
| variant_cost + generic_args.iter().map(|&arg| ctx.arg_cost(arg)).sum::<usize>() |
| } |
| InferSourceKind::FullyQualifiedMethodCall { args, .. } => { |
| 20 + args.iter().map(|arg| ctx.arg_cost(arg)).sum::<usize>() |
| } |
| InferSourceKind::ClosureReturn { ty, should_wrap_expr, .. } => { |
| 30 + ctx.ty_cost(ty) + if should_wrap_expr.is_some() { 10 } else { 0 } |
| } |
| } |
| } |
| |
| /// Uses `fn source_cost` to determine whether this inference source is preferable to |
| /// previous sources. We generally prefer earlier sources. |
| #[instrument(level = "debug", skip(self))] |
| fn update_infer_source(&mut self, mut new_source: InferSource<'tcx>) { |
| if new_source.from_expansion() { |
| return; |
| } |
| |
| let cost = self.source_cost(&new_source) + self.attempt; |
| debug!(?cost); |
| self.attempt += 1; |
| if let Some(InferSource { kind: InferSourceKind::GenericArg { def_id: did, .. }, .. }) = |
| self.infer_source |
| && let InferSourceKind::LetBinding { ref ty, ref mut def_id, .. } = new_source.kind |
| && ty.is_ty_or_numeric_infer() |
| { |
| // Customize the output so we talk about `let x: Vec<_> = iter.collect();` instead of |
| // `let x: _ = iter.collect();`, as this is a very common case. |
| *def_id = Some(did); |
| } |
| |
| if cost < self.infer_source_cost { |
| self.infer_source_cost = cost; |
| self.infer_source = Some(new_source); |
| } |
| } |
| |
| fn node_args_opt(&self, hir_id: HirId) -> Option<GenericArgsRef<'tcx>> { |
| let args = self.typeck_results.node_args_opt(hir_id); |
| self.infcx.resolve_vars_if_possible(args) |
| } |
| |
| fn opt_node_type(&self, hir_id: HirId) -> Option<Ty<'tcx>> { |
| let ty = self.typeck_results.node_type_opt(hir_id); |
| self.infcx.resolve_vars_if_possible(ty) |
| } |
| |
| // Check whether this generic argument is the inference variable we |
| // are looking for. |
| fn generic_arg_is_target(&self, arg: GenericArg<'tcx>) -> bool { |
| if arg == self.target { |
| return true; |
| } |
| |
| match (arg.unpack(), self.target.unpack()) { |
| (GenericArgKind::Type(inner_ty), GenericArgKind::Type(target_ty)) => { |
| use ty::{Infer, TyVar}; |
| match (inner_ty.kind(), target_ty.kind()) { |
| (&Infer(TyVar(a_vid)), &Infer(TyVar(b_vid))) => { |
| self.infcx.inner.borrow_mut().type_variables().sub_unified(a_vid, b_vid) |
| } |
| _ => false, |
| } |
| } |
| (GenericArgKind::Const(inner_ct), GenericArgKind::Const(target_ct)) => { |
| use ty::InferConst::*; |
| match (inner_ct.kind(), target_ct.kind()) { |
| (ty::ConstKind::Infer(Var(a_vid)), ty::ConstKind::Infer(Var(b_vid))) => self |
| .infcx |
| .inner |
| .borrow_mut() |
| .const_unification_table() |
| .unioned(a_vid, b_vid), |
| _ => false, |
| } |
| } |
| _ => false, |
| } |
| } |
| |
| /// Does this generic argument contain our target inference variable |
| /// in a way which can be written by the user. |
| fn generic_arg_contains_target(&self, arg: GenericArg<'tcx>) -> bool { |
| let mut walker = arg.walk(); |
| while let Some(inner) = walker.next() { |
| if self.generic_arg_is_target(inner) { |
| return true; |
| } |
| match inner.unpack() { |
| GenericArgKind::Lifetime(_) => {} |
| GenericArgKind::Type(ty) => { |
| if matches!( |
| ty.kind(), |
| ty::Alias(ty::Opaque, ..) | ty::Closure(..) | ty::Coroutine(..) |
| ) { |
| // Opaque types can't be named by the user right now. |
| // |
| // Both the generic arguments of closures and coroutines can |
| // also not be named. We may want to only look into the closure |
| // signature in case it has no captures, as that can be represented |
| // using `fn(T) -> R`. |
| |
| // FIXME(type_alias_impl_trait): These opaque types |
| // can actually be named, so it would make sense to |
| // adjust this case and add a test for it. |
| walker.skip_current_subtree(); |
| } |
| } |
| GenericArgKind::Const(ct) => { |
| if matches!(ct.kind(), ty::ConstKind::Unevaluated(..)) { |
| // You can't write the generic arguments for |
| // unevaluated constants. |
| walker.skip_current_subtree(); |
| } |
| } |
| } |
| } |
| false |
| } |
| |
| fn expr_inferred_arg_iter( |
| &self, |
| expr: &'tcx hir::Expr<'tcx>, |
| ) -> Box<dyn Iterator<Item = InsertableGenericArgs<'tcx>> + 'a> { |
| let tcx = self.infcx.tcx; |
| match expr.kind { |
| hir::ExprKind::Path(ref path) => { |
| if let Some(args) = self.node_args_opt(expr.hir_id) { |
| return self.path_inferred_arg_iter(expr.hir_id, args, path); |
| } |
| } |
| // FIXME(#98711): Ideally we would also deal with type relative |
| // paths here, even if that is quite rare. |
| // |
| // See the `need_type_info/expr-struct-type-relative-gat.rs` test |
| // for an example where that would be needed. |
| // |
| // However, the `type_dependent_def_id` for `Self::Output` in an |
| // impl is currently the `DefId` of `Output` in the trait definition |
| // which makes this somewhat difficult and prevents us from just |
| // using `self.path_inferred_arg_iter` here. |
| hir::ExprKind::Struct(&hir::QPath::Resolved(_self_ty, path), _, _) |
| // FIXME(TaKO8Ki): Ideally we should support this. For that |
| // we have to map back from the self type to the |
| // type alias though. That's difficult. |
| // |
| // See the `need_type_info/issue-103053.rs` test for |
| // a example. |
| if !matches!(path.res, Res::Def(DefKind::TyAlias, _)) => { |
| if let Some(ty) = self.opt_node_type(expr.hir_id) |
| && let ty::Adt(_, args) = ty.kind() |
| { |
| return Box::new(self.resolved_path_inferred_arg_iter(path, args)); |
| } |
| } |
| hir::ExprKind::MethodCall(segment, ..) => { |
| if let Some(def_id) = self.typeck_results.type_dependent_def_id(expr.hir_id) { |
| let generics = tcx.generics_of(def_id); |
| let insertable: Option<_> = try { |
| if generics.has_impl_trait() { |
| None? |
| } |
| let args = self.node_args_opt(expr.hir_id)?; |
| let span = tcx.hir().span(segment.hir_id); |
| let insert_span = segment.ident.span.shrink_to_hi().with_hi(span.hi()); |
| InsertableGenericArgs { |
| insert_span, |
| args, |
| generics_def_id: def_id, |
| def_id, |
| have_turbofish: false, |
| } |
| }; |
| return Box::new(insertable.into_iter()); |
| } |
| } |
| _ => {} |
| } |
| |
| Box::new(iter::empty()) |
| } |
| |
| fn resolved_path_inferred_arg_iter( |
| &self, |
| path: &'tcx hir::Path<'tcx>, |
| args: GenericArgsRef<'tcx>, |
| ) -> impl Iterator<Item = InsertableGenericArgs<'tcx>> + 'a { |
| let tcx = self.infcx.tcx; |
| let have_turbofish = path.segments.iter().any(|segment| { |
| segment.args.is_some_and(|args| args.args.iter().any(|arg| arg.is_ty_or_const())) |
| }); |
| // The last segment of a path often has `Res::Err` and the |
| // correct `Res` is the one of the whole path. |
| // |
| // FIXME: We deal with that one separately for now, |
| // would be good to remove this special case. |
| let last_segment_using_path_data: Option<_> = try { |
| let generics_def_id = tcx.res_generics_def_id(path.res)?; |
| let generics = tcx.generics_of(generics_def_id); |
| if generics.has_impl_trait() { |
| None?; |
| } |
| let insert_span = |
| path.segments.last().unwrap().ident.span.shrink_to_hi().with_hi(path.span.hi()); |
| InsertableGenericArgs { |
| insert_span, |
| args, |
| generics_def_id, |
| def_id: path.res.def_id(), |
| have_turbofish, |
| } |
| }; |
| |
| path.segments |
| .iter() |
| .filter_map(move |segment| { |
| let res = segment.res; |
| let generics_def_id = tcx.res_generics_def_id(res)?; |
| let generics = tcx.generics_of(generics_def_id); |
| if generics.has_impl_trait() { |
| return None; |
| } |
| let span = tcx.hir().span(segment.hir_id); |
| let insert_span = segment.ident.span.shrink_to_hi().with_hi(span.hi()); |
| Some(InsertableGenericArgs { |
| insert_span, |
| args, |
| generics_def_id, |
| def_id: res.def_id(), |
| have_turbofish, |
| }) |
| }) |
| .chain(last_segment_using_path_data) |
| } |
| |
| fn path_inferred_arg_iter( |
| &self, |
| hir_id: HirId, |
| args: GenericArgsRef<'tcx>, |
| qpath: &'tcx hir::QPath<'tcx>, |
| ) -> Box<dyn Iterator<Item = InsertableGenericArgs<'tcx>> + 'a> { |
| let tcx = self.infcx.tcx; |
| match qpath { |
| hir::QPath::Resolved(_self_ty, path) => { |
| Box::new(self.resolved_path_inferred_arg_iter(path, args)) |
| } |
| hir::QPath::TypeRelative(ty, segment) => { |
| let Some(def_id) = self.typeck_results.type_dependent_def_id(hir_id) else { |
| return Box::new(iter::empty()); |
| }; |
| |
| let generics = tcx.generics_of(def_id); |
| let segment: Option<_> = try { |
| if !segment.infer_args || generics.has_impl_trait() { |
| None?; |
| } |
| let span = tcx.hir().span(segment.hir_id); |
| let insert_span = segment.ident.span.shrink_to_hi().with_hi(span.hi()); |
| InsertableGenericArgs { |
| insert_span, |
| args, |
| generics_def_id: def_id, |
| def_id, |
| have_turbofish: false, |
| } |
| }; |
| |
| let parent_def_id = generics.parent.unwrap(); |
| if let DefKind::Impl { .. } = tcx.def_kind(parent_def_id) { |
| let parent_ty = tcx.type_of(parent_def_id).instantiate(tcx, args); |
| match (parent_ty.kind(), &ty.kind) { |
| ( |
| ty::Adt(def, args), |
| hir::TyKind::Path(hir::QPath::Resolved(_self_ty, path)), |
| ) => { |
| if tcx.res_generics_def_id(path.res) != Some(def.did()) { |
| match path.res { |
| Res::Def(DefKind::TyAlias, _) => { |
| // FIXME: Ideally we should support this. For that |
| // we have to map back from the self type to the |
| // type alias though. That's difficult. |
| // |
| // See the `need_type_info/type-alias.rs` test for |
| // some examples. |
| } |
| // There cannot be inference variables in the self type, |
| // so there's nothing for us to do here. |
| Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } => {} |
| _ => warn!( |
| "unexpected path: def={:?} args={:?} path={:?}", |
| def, args, path, |
| ), |
| } |
| } else { |
| return Box::new( |
| self.resolved_path_inferred_arg_iter(path, args).chain(segment), |
| ); |
| } |
| } |
| _ => (), |
| } |
| } |
| |
| Box::new(segment.into_iter()) |
| } |
| hir::QPath::LangItem(_, _, _) => Box::new(iter::empty()), |
| } |
| } |
| } |
| |
| impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> { |
| type NestedFilter = nested_filter::OnlyBodies; |
| |
| fn nested_visit_map(&mut self) -> Self::Map { |
| self.infcx.tcx.hir() |
| } |
| |
| fn visit_local(&mut self, local: &'tcx Local<'tcx>) { |
| intravisit::walk_local(self, local); |
| |
| if let Some(ty) = self.opt_node_type(local.hir_id) { |
| if self.generic_arg_contains_target(ty.into()) { |
| match local.source { |
| LocalSource::Normal if local.ty.is_none() => { |
| self.update_infer_source(InferSource { |
| span: local.pat.span, |
| kind: InferSourceKind::LetBinding { |
| insert_span: local.pat.span.shrink_to_hi(), |
| pattern_name: local.pat.simple_ident(), |
| ty, |
| def_id: None, |
| }, |
| }) |
| } |
| _ => {} |
| } |
| } |
| } |
| } |
| |
| /// For closures, we first visit the parameters and then the content, |
| /// as we prefer those. |
| fn visit_body(&mut self, body: &'tcx Body<'tcx>) { |
| for param in body.params { |
| debug!( |
| "param: span {:?}, ty_span {:?}, pat.span {:?}", |
| param.span, param.ty_span, param.pat.span |
| ); |
| if param.ty_span != param.pat.span { |
| debug!("skipping param: has explicit type"); |
| continue; |
| } |
| |
| let Some(param_ty) = self.opt_node_type(param.hir_id) else { continue }; |
| |
| if self.generic_arg_contains_target(param_ty.into()) { |
| self.update_infer_source(InferSource { |
| span: param.pat.span, |
| kind: InferSourceKind::ClosureArg { |
| insert_span: param.pat.span.shrink_to_hi(), |
| ty: param_ty, |
| }, |
| }) |
| } |
| } |
| intravisit::walk_body(self, body); |
| } |
| |
| #[instrument(level = "debug", skip(self))] |
| fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { |
| let tcx = self.infcx.tcx; |
| match expr.kind { |
| // When encountering `func(arg)` first look into `arg` and then `func`, |
| // as `arg` is "more specific". |
| ExprKind::Call(func, args) => { |
| for arg in args { |
| self.visit_expr(arg); |
| } |
| self.visit_expr(func); |
| } |
| _ => intravisit::walk_expr(self, expr), |
| } |
| |
| for args in self.expr_inferred_arg_iter(expr) { |
| debug!(?args); |
| let InsertableGenericArgs { |
| insert_span, |
| args, |
| generics_def_id, |
| def_id, |
| have_turbofish, |
| } = args; |
| let generics = tcx.generics_of(generics_def_id); |
| if let Some(mut argument_index) = generics |
| .own_args(args) |
| .iter() |
| .position(|&arg| self.generic_arg_contains_target(arg)) |
| { |
| if generics.parent.is_none() && generics.has_self { |
| argument_index += 1; |
| } |
| let args = self.infcx.resolve_vars_if_possible(args); |
| let generic_args = |
| &generics.own_args_no_defaults(tcx, args)[generics.own_counts().lifetimes..]; |
| let span = match expr.kind { |
| ExprKind::MethodCall(path, ..) => path.ident.span, |
| _ => expr.span, |
| }; |
| |
| self.update_infer_source(InferSource { |
| span, |
| kind: InferSourceKind::GenericArg { |
| insert_span, |
| argument_index, |
| generics_def_id, |
| def_id, |
| generic_args, |
| have_turbofish, |
| }, |
| }); |
| } |
| } |
| |
| if let Some(node_ty) = self.opt_node_type(expr.hir_id) { |
| if let ( |
| &ExprKind::Closure(&Closure { fn_decl, body, fn_decl_span, .. }), |
| ty::Closure(_, args), |
| ) = (&expr.kind, node_ty.kind()) |
| { |
| let output = args.as_closure().sig().output().skip_binder(); |
| if self.generic_arg_contains_target(output.into()) { |
| let body = self.infcx.tcx.hir().body(body); |
| let should_wrap_expr = if matches!(body.value.kind, ExprKind::Block(..)) { |
| None |
| } else { |
| Some(body.value.span.shrink_to_hi()) |
| }; |
| self.update_infer_source(InferSource { |
| span: fn_decl_span, |
| kind: InferSourceKind::ClosureReturn { |
| ty: output, |
| data: &fn_decl.output, |
| should_wrap_expr, |
| }, |
| }) |
| } |
| } |
| } |
| |
| let has_impl_trait = |def_id| { |
| iter::successors(Some(tcx.generics_of(def_id)), |generics| { |
| generics.parent.map(|def_id| tcx.generics_of(def_id)) |
| }) |
| .any(|generics| generics.has_impl_trait()) |
| }; |
| if let ExprKind::MethodCall(path, receiver, method_args, span) = expr.kind |
| && let Some(args) = self.node_args_opt(expr.hir_id) |
| && args.iter().any(|arg| self.generic_arg_contains_target(arg)) |
| && let Some(def_id) = self.typeck_results.type_dependent_def_id(expr.hir_id) |
| && self.infcx.tcx.trait_of_item(def_id).is_some() |
| && !has_impl_trait(def_id) |
| { |
| let successor = |
| method_args.get(0).map_or_else(|| (")", span.hi()), |arg| (", ", arg.span.lo())); |
| let args = self.infcx.resolve_vars_if_possible(args); |
| self.update_infer_source(InferSource { |
| span: path.ident.span, |
| kind: InferSourceKind::FullyQualifiedMethodCall { |
| receiver, |
| successor, |
| args, |
| def_id, |
| }, |
| }) |
| } |
| } |
| } |