diff options
| author | Oli Scherer <git-spam-no-reply9815368754983@oli-obk.de> | 2024-07-05 08:59:31 +0000 |
|---|---|---|
| committer | Oli Scherer <git-spam-no-reply9815368754983@oli-obk.de> | 2024-07-08 19:29:55 +0000 |
| commit | af9ab1b026b0d021d7a7d7d443f4398641f093ef (patch) | |
| tree | caad8cdf5f4446a361addccaa375d9587fb59e03 /compiler/rustc_hir_analysis/src/errors | |
| parent | 2f0368c902e912242e86e49f140c666c712b79c7 (diff) | |
| download | rust-af9ab1b026b0d021d7a7d7d443f4398641f093ef.tar.gz rust-af9ab1b026b0d021d7a7d7d443f4398641f093ef.zip | |
Remove `structured_errors` module
Diffstat (limited to 'compiler/rustc_hir_analysis/src/errors')
| -rw-r--r-- | compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs | 1138 |
1 files changed, 1138 insertions, 0 deletions
diff --git a/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs b/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs new file mode 100644 index 00000000000..6426ad9dc18 --- /dev/null +++ b/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs @@ -0,0 +1,1138 @@ +use rustc_errors::{ + codes::*, pluralize, Applicability, Diag, Diagnostic, EmissionGuarantee, MultiSpan, +}; +use rustc_hir as hir; +use rustc_middle::ty::{self as ty, AssocItems, AssocKind, TyCtxt}; +use rustc_span::def_id::DefId; +use std::iter; + +use GenericArgsInfo::*; + +/// Handles the `wrong number of type / lifetime / ... arguments` family of error messages. +pub struct WrongNumberOfGenericArgs<'a, 'tcx> { + pub(crate) tcx: TyCtxt<'tcx>, + + pub(crate) angle_brackets: AngleBrackets, + + pub(crate) gen_args_info: GenericArgsInfo, + + /// Offending path segment + pub(crate) path_segment: &'a hir::PathSegment<'a>, + + /// Generic parameters as expected by type or trait + pub(crate) gen_params: &'a ty::Generics, + + /// Index offset into parameters. Depends on whether `Self` is included and on + /// number of lifetime parameters in case we're processing missing or redundant + /// type or constant arguments. + pub(crate) params_offset: usize, + + /// Generic arguments as provided by user + pub(crate) gen_args: &'a hir::GenericArgs<'a>, + + /// DefId of the generic type + pub(crate) def_id: DefId, +} + +// Provides information about the kind of arguments that were provided for +// the PathSegment, for which missing generic arguments were detected +#[derive(Debug)] +pub(crate) enum AngleBrackets { + // No angle brackets were provided, but generic arguments exist in elided form + Implied, + + // No angle brackets were provided + Missing, + + // Angle brackets are available, but missing some generic arguments + Available, +} + +// Information about the kind of arguments that are either missing or are unexpected +#[derive(Debug)] +pub enum GenericArgsInfo { + MissingLifetimes { + num_missing_args: usize, + }, + ExcessLifetimes { + num_redundant_args: usize, + }, + MissingTypesOrConsts { + num_missing_args: usize, + + // type or const generic arguments can have default values + num_default_params: usize, + + // lifetime arguments precede type and const parameters, this + // field gives the number of generic lifetime arguments to let + // us infer the position of type and const generic arguments + // in the angle brackets + args_offset: usize, + }, + + ExcessTypesOrConsts { + num_redundant_args: usize, + + // type or const generic arguments can have default values + num_default_params: usize, + + // lifetime arguments precede type and const parameters, this + // field gives the number of generic lifetime arguments to let + // us infer the position of type and const generic arguments + // in the angle brackets + args_offset: usize, + + // if synthetic type arguments (e.g. `impl Trait`) are specified + synth_provided: bool, + }, +} + +impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { + pub fn new( + tcx: TyCtxt<'tcx>, + gen_args_info: GenericArgsInfo, + path_segment: &'a hir::PathSegment<'_>, + gen_params: &'a ty::Generics, + params_offset: usize, + gen_args: &'a hir::GenericArgs<'a>, + def_id: DefId, + ) -> Self { + let angle_brackets = if gen_args.span_ext().is_none() { + if gen_args.is_empty() { AngleBrackets::Missing } else { AngleBrackets::Implied } + } else { + AngleBrackets::Available + }; + + Self { + tcx, + angle_brackets, + gen_args_info, + path_segment, + gen_params, + params_offset, + gen_args, + def_id, + } + } + + fn missing_lifetimes(&self) -> bool { + match self.gen_args_info { + MissingLifetimes { .. } | ExcessLifetimes { .. } => true, + MissingTypesOrConsts { .. } | ExcessTypesOrConsts { .. } => false, + } + } + + fn kind(&self) -> &str { + if self.missing_lifetimes() { "lifetime" } else { "generic" } + } + + /// Returns true if the generic type is a trait + /// and is being referred to from one of its trait impls + fn is_in_trait_impl(&self) -> bool { + if self.tcx.is_trait(self.def_id) { + // Here we check if the reference to the generic type + // is from the 'of_trait' field of the enclosing impl + + let parent = self.tcx.parent_hir_node(self.path_segment.hir_id); + let parent_item = self.tcx.hir_node_by_def_id( + self.tcx.hir().get_parent_item(self.path_segment.hir_id).def_id, + ); + + // Get the HIR id of the trait ref + let hir::Node::TraitRef(hir::TraitRef { hir_ref_id: trait_ref_id, .. }) = parent else { + return false; + }; + + // Get the HIR id of the 'of_trait' field of the impl + let hir::Node::Item(hir::Item { + kind: + hir::ItemKind::Impl(hir::Impl { + of_trait: Some(hir::TraitRef { hir_ref_id: id_in_of_trait, .. }), + .. + }), + .. + }) = parent_item + else { + return false; + }; + + // Check that trait is referred to from the of_trait field of impl + trait_ref_id == id_in_of_trait + } else { + false + } + } + + fn num_provided_args(&self) -> usize { + if self.missing_lifetimes() { + self.num_provided_lifetime_args() + } else { + self.num_provided_type_or_const_args() + } + } + + fn num_provided_lifetime_args(&self) -> usize { + match self.angle_brackets { + AngleBrackets::Missing => 0, + // Only lifetime arguments can be implied + AngleBrackets::Implied => self.gen_args.args.len(), + AngleBrackets::Available => self.gen_args.num_lifetime_params(), + } + } + + fn num_provided_type_or_const_args(&self) -> usize { + match self.angle_brackets { + AngleBrackets::Missing => 0, + // Only lifetime arguments can be implied + AngleBrackets::Implied => 0, + AngleBrackets::Available => self.gen_args.num_generic_params(), + } + } + + fn num_expected_lifetime_args(&self) -> usize { + let num_provided_args = self.num_provided_lifetime_args(); + match self.gen_args_info { + MissingLifetimes { num_missing_args } => num_provided_args + num_missing_args, + ExcessLifetimes { num_redundant_args } => num_provided_args - num_redundant_args, + _ => 0, + } + } + + fn num_expected_type_or_const_args(&self) -> usize { + let num_provided_args = self.num_provided_type_or_const_args(); + match self.gen_args_info { + MissingTypesOrConsts { num_missing_args, .. } => num_provided_args + num_missing_args, + ExcessTypesOrConsts { num_redundant_args, .. } => { + num_provided_args - num_redundant_args + } + _ => 0, + } + } + + // Gives the number of expected arguments taking into account default arguments + fn num_expected_type_or_const_args_including_defaults(&self) -> usize { + let provided_args = self.num_provided_type_or_const_args(); + match self.gen_args_info { + MissingTypesOrConsts { num_missing_args, num_default_params, .. } => { + provided_args + num_missing_args - num_default_params + } + ExcessTypesOrConsts { num_redundant_args, num_default_params, .. } => { + provided_args - num_redundant_args - num_default_params + } + _ => 0, + } + } + + fn num_missing_lifetime_args(&self) -> usize { + let missing_args = self.num_expected_lifetime_args() - self.num_provided_lifetime_args(); + assert!(missing_args > 0); + missing_args + } + + fn num_missing_type_or_const_args(&self) -> usize { + let missing_args = self.num_expected_type_or_const_args_including_defaults() + - self.num_provided_type_or_const_args(); + assert!(missing_args > 0); + missing_args + } + + fn num_excess_lifetime_args(&self) -> usize { + match self.gen_args_info { + ExcessLifetimes { num_redundant_args } => num_redundant_args, + _ => 0, + } + } + + fn num_excess_type_or_const_args(&self) -> usize { + match self.gen_args_info { + ExcessTypesOrConsts { num_redundant_args, .. } => num_redundant_args, + _ => 0, + } + } + + fn too_many_args_provided(&self) -> bool { + match self.gen_args_info { + MissingLifetimes { .. } | MissingTypesOrConsts { .. } => false, + ExcessLifetimes { num_redundant_args } + | ExcessTypesOrConsts { num_redundant_args, .. } => { + assert!(num_redundant_args > 0); + true + } + } + } + + fn not_enough_args_provided(&self) -> bool { + match self.gen_args_info { + MissingLifetimes { num_missing_args } + | MissingTypesOrConsts { num_missing_args, .. } => { + assert!(num_missing_args > 0); + true + } + ExcessLifetimes { .. } | ExcessTypesOrConsts { .. } => false, + } + } + + // Helper method to get the index offset in angle brackets, at which type or const arguments + // start appearing + fn get_lifetime_args_offset(&self) -> usize { + match self.gen_args_info { + MissingLifetimes { .. } | ExcessLifetimes { .. } => 0, + MissingTypesOrConsts { args_offset, .. } | ExcessTypesOrConsts { args_offset, .. } => { + args_offset + } + } + } + + fn get_num_default_params(&self) -> usize { + match self.gen_args_info { + MissingTypesOrConsts { num_default_params, .. } + | ExcessTypesOrConsts { num_default_params, .. } => num_default_params, + _ => 0, + } + } + + fn is_synth_provided(&self) -> bool { + match self.gen_args_info { + ExcessTypesOrConsts { synth_provided, .. } => synth_provided, + _ => false, + } + } + + // Helper function to choose a quantifier word for the number of expected arguments + // and to give a bound for the number of expected arguments + fn get_quantifier_and_bound(&self) -> (&'static str, usize) { + if self.get_num_default_params() == 0 { + match self.gen_args_info { + MissingLifetimes { .. } | ExcessLifetimes { .. } => { + ("", self.num_expected_lifetime_args()) + } + MissingTypesOrConsts { .. } | ExcessTypesOrConsts { .. } => { + ("", self.num_expected_type_or_const_args()) + } + } + } else { + match self.gen_args_info { + MissingLifetimes { .. } => ("at least ", self.num_expected_lifetime_args()), + MissingTypesOrConsts { .. } => { + ("at least ", self.num_expected_type_or_const_args_including_defaults()) + } + ExcessLifetimes { .. } => ("at most ", self.num_expected_lifetime_args()), + ExcessTypesOrConsts { .. } => ("at most ", self.num_expected_type_or_const_args()), + } + } + } + + // Creates lifetime name suggestions from the lifetime parameter names + fn get_lifetime_args_suggestions_from_param_names( + &self, + path_hir_id: hir::HirId, + num_params_to_take: usize, + ) -> String { + debug!(?path_hir_id); + + // If there was already a lifetime among the arguments, just replicate that one. + if let Some(lt) = self.gen_args.args.iter().find_map(|arg| match arg { + hir::GenericArg::Lifetime(lt) => Some(lt), + _ => None, + }) { + return std::iter::repeat(lt.to_string()) + .take(num_params_to_take) + .collect::<Vec<_>>() + .join(", "); + } + + let mut ret = Vec::new(); + let mut ty_id = None; + for (id, node) in self.tcx.hir().parent_iter(path_hir_id) { + debug!(?id); + if let hir::Node::Ty(_) = node { + ty_id = Some(id); + } + + // Suggest `'_` when in function parameter or elided function return. + if let Some(fn_decl) = node.fn_decl() + && let Some(ty_id) = ty_id + { + let in_arg = fn_decl.inputs.iter().any(|t| t.hir_id == ty_id); + let in_ret = + matches!(fn_decl.output, hir::FnRetTy::Return(ty) if ty.hir_id == ty_id); + + if in_arg || (in_ret && fn_decl.lifetime_elision_allowed) { + return std::iter::repeat("'_".to_owned()) + .take(num_params_to_take) + .collect::<Vec<_>>() + .join(", "); + } + } + + // Suggest `'static` when in const/static item-like. + if let hir::Node::Item(hir::Item { + kind: hir::ItemKind::Static { .. } | hir::ItemKind::Const { .. }, + .. + }) + | hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Const { .. }, + .. + }) + | hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Const { .. }, + .. + }) + | hir::Node::ForeignItem(hir::ForeignItem { + kind: hir::ForeignItemKind::Static { .. }, + .. + }) + | hir::Node::AnonConst(..) = node + { + return std::iter::repeat("'static".to_owned()) + .take(num_params_to_take.saturating_sub(ret.len())) + .collect::<Vec<_>>() + .join(", "); + } + + let params = if let Some(generics) = node.generics() { + generics.params + } else if let hir::Node::Ty(ty) = node + && let hir::TyKind::BareFn(bare_fn) = ty.kind + { + bare_fn.generic_params + } else { + &[] + }; + ret.extend(params.iter().filter_map(|p| { + let hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } = + p.kind + else { + return None; + }; + let hir::ParamName::Plain(name) = p.name else { return None }; + Some(name.to_string()) + })); + + if ret.len() >= num_params_to_take { + return ret[..num_params_to_take].join(", "); + } + // We cannot refer to lifetimes defined in an outer function. + if let hir::Node::Item(_) = node { + break; + } + } + + // We could not gather enough lifetime parameters in the scope. + // We use the parameter names from the target type's definition instead. + self.gen_params + .own_params + .iter() + .skip(self.params_offset + self.num_provided_lifetime_args()) + .take(num_params_to_take) + .map(|param| param.name.to_string()) + .collect::<Vec<_>>() + .join(", ") + } + + // Creates type or constant name suggestions from the provided parameter names + fn get_type_or_const_args_suggestions_from_param_names( + &self, + num_params_to_take: usize, + ) -> String { + let is_in_a_method_call = self + .tcx + .hir() + .parent_iter(self.path_segment.hir_id) + .skip(1) + .find_map(|(_, node)| match node { + hir::Node::Expr(expr) => Some(expr), + _ => None, + }) + .is_some_and(|expr| { + matches!( + expr.kind, + hir::ExprKind::MethodCall(hir::PathSegment { args: Some(_), .. }, ..) + ) + }); + + let fn_sig = self.tcx.hir().get_if_local(self.def_id).and_then(hir::Node::fn_sig); + let is_used_in_input = |def_id| { + fn_sig.is_some_and(|fn_sig| { + fn_sig.decl.inputs.iter().any(|ty| match ty.kind { + hir::TyKind::Path(hir::QPath::Resolved( + None, + hir::Path { res: hir::def::Res::Def(_, id), .. }, + )) => *id == def_id, + _ => false, + }) + }) + }; + self.gen_params + .own_params + .iter() + .skip(self.params_offset + self.num_provided_type_or_const_args()) + .take(num_params_to_take) + .map(|param| match param.kind { + // If it's in method call (turbofish), it might be inferred from the expression (e.g. `.collect::<Vec<_>>()`) + // If it is being inferred from the item's inputs, no need to set it. + ty::GenericParamDefKind::Type { .. } + if is_in_a_method_call || is_used_in_input(param.def_id) => + { + "_" + } + _ => param.name.as_str(), + }) + .intersperse(", ") + .collect() + } + + fn get_unbound_associated_types(&self) -> Vec<String> { + if self.tcx.is_trait(self.def_id) { + let items: &AssocItems = self.tcx.associated_items(self.def_id); + items + .in_definition_order() + .filter(|item| item.kind == AssocKind::Type) + .filter(|item| { + !self + .gen_args + .constraints + .iter() + .any(|constraint| constraint.ident.name == item.name) + }) + .map(|item| item.name.to_ident_string()) + .collect() + } else { + Vec::default() + } + } + + fn create_error_message(&self) -> String { + let def_path = self.tcx.def_path_str(self.def_id); + let def_kind = self.tcx.def_descr(self.def_id); + let (quantifier, bound) = self.get_quantifier_and_bound(); + let kind = self.kind(); + let provided_lt_args = self.num_provided_lifetime_args(); + let provided_type_or_const_args = self.num_provided_type_or_const_args(); + + let (provided_args_str, verb) = match self.gen_args_info { + MissingLifetimes { .. } | ExcessLifetimes { .. } => ( + format!("{} lifetime argument{}", provided_lt_args, pluralize!(provided_lt_args)), + pluralize!("was", provided_lt_args), + ), + MissingTypesOrConsts { .. } | ExcessTypesOrConsts { .. } => ( + format!( + "{} generic argument{}", + provided_type_or_const_args, + pluralize!(provided_type_or_const_args) + ), + pluralize!("was", provided_type_or_const_args), + ), + }; + + if self.gen_args.span_ext().is_some() { + format!( + "{} takes {}{} {} argument{} but {} {} supplied", + def_kind, + quantifier, + bound, + kind, + pluralize!(bound), + provided_args_str.as_str(), + verb + ) + } else { + format!("missing generics for {def_kind} `{def_path}`") + } + } + + /// Builds the `expected 1 type argument / supplied 2 type arguments` message. + fn notify(&self, err: &mut Diag<'_, impl EmissionGuarantee>) { + let (quantifier, bound) = self.get_quantifier_and_bound(); + let provided_args = self.num_provided_args(); + + err.span_label( + self.path_segment.ident.span, + format!( + "expected {}{} {} argument{}", + quantifier, + bound, + self.kind(), + pluralize!(bound), + ), + ); + + // When too many arguments were provided, we don't highlight each of them, because it + // would overlap with the suggestion to remove them: + // + // ``` + // type Foo = Bar<usize, usize>; + // ----- ----- supplied 2 type arguments + // ^^^^^^^ remove this type argument + // ``` + if self.too_many_args_provided() { + return; + } + + let args = self + .gen_args + .args + .iter() + .skip(self.get_lifetime_args_offset()) + .take(provided_args) + .enumerate(); + + for (i, arg) in args { + err.span_label( + arg.span(), + if i + 1 == provided_args { + format!( + "supplied {} {} argument{}", + provided_args, + self.kind(), + pluralize!(provided_args) + ) + } else { + String::new() + }, + ); + } + } + + fn suggest(&self, err: &mut Diag<'_, impl EmissionGuarantee>) { + debug!( + "suggest(self.provided {:?}, self.gen_args.span(): {:?})", + self.num_provided_args(), + self.gen_args.span(), + ); + + match self.angle_brackets { + AngleBrackets::Missing | AngleBrackets::Implied => self.suggest_adding_args(err), + AngleBrackets::Available => { + if self.not_enough_args_provided() { + self.suggest_adding_args(err); + } else if self.too_many_args_provided() { + self.suggest_moving_args_from_assoc_fn_to_trait(err); + self.suggest_removing_args_or_generics(err); + } else { + unreachable!(); + } + } + } + } + + /// Suggests to add missing argument(s) when current invocation site already contains some + /// generics: + /// + /// ```text + /// type Map = HashMap<String>; + /// ``` + fn suggest_adding_args(&self, err: &mut Diag<'_, impl EmissionGuarantee>) { + if self.gen_args.parenthesized != hir::GenericArgsParentheses::No { + return; + } + + match self.gen_args_info { + MissingLifetimes { .. } => { + self.suggest_adding_lifetime_args(err); + } + MissingTypesOrConsts { .. } => { + self.suggest_adding_type_and_const_args(err); + } + ExcessTypesOrConsts { .. } => { + // this can happen with `~const T` where T isn't a const_trait. + } + _ => unreachable!(), + } + } + + fn suggest_adding_lifetime_args(&self, err: &mut Diag<'_, impl EmissionGuarantee>) { + debug!("suggest_adding_lifetime_args(path_segment: {:?})", self.path_segment); + let num_missing_args = self.num_missing_lifetime_args(); + let num_params_to_take = num_missing_args; + let msg = format!("add missing {} argument{}", self.kind(), pluralize!(num_missing_args)); + + let suggested_args = self.get_lifetime_args_suggestions_from_param_names( + self.path_segment.hir_id, + num_params_to_take, + ); + debug!("suggested_args: {:?}", &suggested_args); + + match self.angle_brackets { + AngleBrackets::Missing => { + let span = self.path_segment.ident.span; + + // insert a suggestion of the form "Y<'a, 'b>" + let sugg = format!("<{suggested_args}>"); + debug!("sugg: {:?}", sugg); + + err.span_suggestion_verbose( + span.shrink_to_hi(), + msg, + sugg, + Applicability::HasPlaceholders, + ); + } + + AngleBrackets::Available => { + let (sugg_span, is_first) = if self.num_provided_lifetime_args() == 0 { + (self.gen_args.span().unwrap().shrink_to_lo(), true) + } else { + let last_lt = &self.gen_args.args[self.num_provided_lifetime_args() - 1]; + (last_lt.span().shrink_to_hi(), false) + }; + let has_non_lt_args = self.num_provided_type_or_const_args() != 0; + let has_constraints = !self.gen_args.constraints.is_empty(); + + let sugg_prefix = if is_first { "" } else { ", " }; + let sugg_suffix = + if is_first && (has_non_lt_args || has_constraints) { ", " } else { "" }; + + let sugg = format!("{sugg_prefix}{suggested_args}{sugg_suffix}"); + debug!("sugg: {:?}", sugg); + + err.span_suggestion_verbose(sugg_span, msg, sugg, Applicability::HasPlaceholders); + } + AngleBrackets::Implied => { + // We never encounter missing lifetimes in situations in which lifetimes are elided + unreachable!(); + } + } + } + + fn suggest_adding_type_and_const_args(&self, err: &mut Diag<'_, impl EmissionGuarantee>) { + let num_missing_args = self.num_missing_type_or_const_args(); + let msg = format!("add missing {} argument{}", self.kind(), pluralize!(num_missing_args)); + + let suggested_args = + self.get_type_or_const_args_suggestions_from_param_names(num_missing_args); + debug!("suggested_args: {:?}", suggested_args); + + match self.angle_brackets { + AngleBrackets::Missing | AngleBrackets::Implied => { + let span = self.path_segment.ident.span; + + // insert a suggestion of the form "Y<T, U>" + let sugg = format!("<{suggested_args}>"); + debug!("sugg: {:?}", sugg); + + err.span_suggestion_verbose( + span.shrink_to_hi(), + msg, + sugg, + Applicability::HasPlaceholders, + ); + } + AngleBrackets::Available => { + let gen_args_span = self.gen_args.span().unwrap(); + let sugg_offset = + self.get_lifetime_args_offset() + self.num_provided_type_or_const_args(); + + let (sugg_span, is_first) = if sugg_offset == 0 { + (gen_args_span.shrink_to_lo(), true) + } else { + let arg_span = self.gen_args.args[sugg_offset - 1].span(); + // If we came here then inferred lifetime's spans can only point + // to either the opening bracket or to the space right after. + // Both of these spans have an `hi` lower than or equal to the span + // of the generics excluding the brackets. + // This allows us to check if `arg_span` is the artificial span of + // an inferred lifetime, in which case the generic we're suggesting to + // add will be the first visible, even if it isn't the actual first generic. + (arg_span.shrink_to_hi(), arg_span.hi() <= gen_args_span.lo()) + }; + + let sugg_prefix = if is_first { "" } else { ", " }; + let sugg_suffix = + if is_first && !self.gen_args.constraints.is_empty() { ", " } else { "" }; + + let sugg = format!("{sugg_prefix}{suggested_args}{sugg_suffix}"); + debug!("sugg: {:?}", sugg); + + err.span_suggestion_verbose(sugg_span, msg, sugg, Applicability::HasPlaceholders); + } + } + } + + /// Suggests moving redundant argument(s) of an associate function to the + /// trait it belongs to. + /// + /// ```compile_fail + /// Into::into::<Option<_>>(42) // suggests considering `Into::<Option<_>>::into(42)` + /// ``` + fn suggest_moving_args_from_assoc_fn_to_trait( + &self, + err: &mut Diag<'_, impl EmissionGuarantee>, + ) { + let trait_ = match self.tcx.trait_of_item(self.def_id) { + Some(def_id) => def_id, + None => return, + }; + + // Skip suggestion when the associated function is itself generic, it is unclear + // how to split the provided parameters between those to suggest to the trait and + // those to remain on the associated type. + let num_assoc_fn_expected_args = + self.num_expected_type_or_const_args() + self.num_expected_lifetime_args(); + if num_assoc_fn_expected_args > 0 { + return; + } + + let num_assoc_fn_excess_args = + self.num_excess_type_or_const_args() + self.num_excess_lifetime_args(); + + let trait_generics = self.tcx.generics_of(trait_); + let num_trait_generics_except_self = + trait_generics.count() - if trait_generics.has_self { 1 } else { 0 }; + + let msg = format!( + "consider moving {these} generic argument{s} to the `{name}` trait, which takes up to {num} argument{s}", + these = pluralize!("this", num_assoc_fn_excess_args), + s = pluralize!(num_assoc_fn_excess_args), + name = self.tcx.item_name(trait_), + num = num_trait_generics_except_self, + ); + + if let hir::Node::Expr(expr) = self.tcx.parent_hir_node(self.path_segment.hir_id) { + match &expr.kind { + hir::ExprKind::Path(qpath) => self + .suggest_moving_args_from_assoc_fn_to_trait_for_qualified_path( + err, + qpath, + msg, + num_assoc_fn_excess_args, + num_trait_generics_except_self, + ), + hir::ExprKind::MethodCall(..) => self + .suggest_moving_args_from_assoc_fn_to_trait_for_method_call( + err, + trait_, + expr, + msg, + num_assoc_fn_excess_args, + num_trait_generics_except_self, + ), + _ => return, + } + } + } + + fn suggest_moving_args_from_assoc_fn_to_trait_for_qualified_path( + &self, + err: &mut Diag<'_, impl EmissionGuarantee>, + qpath: &'tcx hir::QPath<'tcx>, + msg: String, + num_assoc_fn_excess_args: usize, + num_trait_generics_except_self: usize, + ) { + if let hir::QPath::Resolved(_, path) = qpath + && let Some(trait_path_segment) = path.segments.get(0) + { + let num_generic_args_supplied_to_trait = trait_path_segment.args().num_generic_params(); + + if num_generic_args_supplied_to_trait + num_assoc_fn_excess_args + == num_trait_generics_except_self + { + if let Some(span) = self.gen_args.span_ext() + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + { + let sugg = vec![ + ( + self.path_segment.ident.span, + format!("{}::{}", snippet, self.path_segment.ident), + ), + (span.with_lo(self.path_segment.ident.span.hi()), "".to_owned()), + ]; + + err.multipart_suggestion(msg, sugg, Applicability::MaybeIncorrect); + } + } + } + } + + fn suggest_moving_args_from_assoc_fn_to_trait_for_method_call( + &self, + err: &mut Diag<'_, impl EmissionGuarantee>, + trait_def_id: DefId, + expr: &'tcx hir::Expr<'tcx>, + msg: String, + num_assoc_fn_excess_args: usize, + num_trait_generics_except_self: usize, + ) { + let sm = self.tcx.sess.source_map(); + let hir::ExprKind::MethodCall(_, rcvr, args, _) = expr.kind else { + return; + }; + if num_assoc_fn_excess_args != num_trait_generics_except_self { + return; + } + let Some(gen_args) = self.gen_args.span_ext() else { + return; + }; + let Ok(generics) = sm.span_to_snippet(gen_args) else { + return; + }; + let Ok(rcvr) = + sm.span_to_snippet(rcvr.span.find_ancestor_inside(expr.span).unwrap_or(rcvr.span)) + else { + return; + }; + let Ok(rest) = (match args { + [] => Ok(String::new()), + [arg] => { + sm.span_to_snippet(arg.span.find_ancestor_inside(expr.span).unwrap_or(arg.span)) + } + [first, .., last] => { + let first_span = first.span.find_ancestor_inside(expr.span).unwrap_or(first.span); + let last_span = last.span.find_ancestor_inside(expr.span).unwrap_or(last.span); + sm.span_to_snippet(first_span.to(last_span)) + } + }) else { + return; + }; + let comma = if args.len() > 0 { ", " } else { "" }; + let trait_path = self.tcx.def_path_str(trait_def_id); + let method_name = self.tcx.item_name(self.def_id); + err.span_suggestion( + expr.span, + msg, + format!("{trait_path}::{generics}::{method_name}({rcvr}{comma}{rest})"), + Applicability::MaybeIncorrect, + ); + } + + /// Suggests to remove redundant argument(s): + /// + /// ```text + /// type Map = HashMap<String, String, String, String>; + /// ``` + fn suggest_removing_args_or_generics(&self, err: &mut Diag<'_, impl EmissionGuarantee>) { + let num_provided_lt_args = self.num_provided_lifetime_args(); + let num_provided_type_const_args = self.num_provided_type_or_const_args(); + let unbound_types = self.get_unbound_associated_types(); + let num_provided_args = num_provided_lt_args + num_provided_type_const_args; + assert!(num_provided_args > 0); + + let num_redundant_lt_args = self.num_excess_lifetime_args(); + let num_redundant_type_or_const_args = self.num_excess_type_or_const_args(); + let num_redundant_args = num_redundant_lt_args + num_redundant_type_or_const_args; + + let redundant_lifetime_args = num_redundant_lt_args > 0; + let redundant_type_or_const_args = num_redundant_type_or_const_args > 0; + + let remove_entire_generics = num_redundant_args >= self.gen_args.args.len(); + let provided_args_matches_unbound_traits = + unbound_types.len() == num_redundant_type_or_const_args; + + let remove_lifetime_args = |err: &mut Diag<'_, _>| { + let mut lt_arg_spans = Vec::new(); + let mut found_redundant = false; + for arg in self.gen_args.args { + if let hir::GenericArg::Lifetime(_) = arg { + lt_arg_spans.push(arg.span()); + if lt_arg_spans.len() > self.num_expected_lifetime_args() { + found_redundant = true; + } + } else if found_redundant { + // Argument which is redundant and separated like this `'c` + // is not included to avoid including `Bar` in span. + // ``` + // type Foo<'a, T> = &'a T; + // let _: Foo<'a, 'b, Bar, 'c>; + // ``` + break; + } + } + + let span_lo_redundant_lt_args = lt_arg_spans[self.num_expected_lifetime_args()]; + let span_hi_redundant_lt_args = lt_arg_spans[lt_arg_spans.len() - 1]; + + let span_redundant_lt_args = span_lo_redundant_lt_args.to(span_hi_redundant_lt_args); + debug!("span_redundant_lt_args: {:?}", span_redundant_lt_args); + + let num_redundant_lt_args = lt_arg_spans.len() - self.num_expected_lifetime_args(); + let msg_lifetimes = format!( + "remove {these} lifetime argument{s}", + these = pluralize!("this", num_redundant_lt_args), + s = pluralize!(num_redundant_lt_args), + ); + + err.span_suggestion( + span_redundant_lt_args, + msg_lifetimes, + "", + Applicability::MaybeIncorrect, + ); + }; + + let remove_type_or_const_args = |err: &mut Diag<'_, _>| { + let mut gen_arg_spans = Vec::new(); + let mut found_redundant = false; + for arg in self.gen_args.args { + match arg { + hir::GenericArg::Type(_) + | hir::GenericArg::Const(_) + | hir::GenericArg::Infer(_) => { + gen_arg_spans.push(arg.span()); + if gen_arg_spans.len() > self.num_expected_type_or_const_args() { + found_redundant = true; + } + } + _ if found_redundant => break, + _ => {} + } + } + + let span_lo_redundant_type_or_const_args = + gen_arg_spans[self.num_expected_type_or_const_args()]; + let span_hi_redundant_type_or_const_args = gen_arg_spans[gen_arg_spans.len() - 1]; + + let span_redundant_type_or_const_args = + span_lo_redundant_type_or_const_args.to(span_hi_redundant_type_or_const_args); + debug!("span_redundant_type_or_const_args: {:?}", span_redundant_type_or_const_args); + + let num_redundant_gen_args = + gen_arg_spans.len() - self.num_expected_type_or_const_args(); + let msg_types_or_consts = format!( + "remove {these} generic argument{s}", + these = pluralize!("this", num_redundant_gen_args), + s = pluralize!(num_redundant_gen_args), + ); + + err.span_suggestion( + span_redundant_type_or_const_args, + msg_types_or_consts, + "", + Applicability::MaybeIncorrect, + ); + }; + + // If there is a single unbound associated type and a single excess generic param + // suggest replacing the generic param with the associated type bound + if provided_args_matches_unbound_traits && !unbound_types.is_empty() { + // Don't suggest if we're in a trait impl as + // that would result in invalid syntax (fixes #116464) + if !self.is_in_trait_impl() { + let unused_generics = &self.gen_args.args[self.num_expected_type_or_const_args()..]; + let suggestions = iter::zip(unused_generics, &unbound_types) + .map(|(potential, name)| { + (potential.span().shrink_to_lo(), format!("{name} = ")) + }) + .collect::<Vec<_>>(); + + if !suggestions.is_empty() { + err.multipart_suggestion_verbose( + format!( + "replace the generic bound{s} with the associated type{s}", + s = pluralize!(unbound_types.len()) + ), + suggestions, + Applicability::MaybeIncorrect, + ); + } + } + } else if remove_entire_generics { + let span = self + .path_segment + .args + .unwrap() + .span_ext() + .unwrap() + .with_lo(self.path_segment.ident.span.hi()); + + let msg = format!( + "remove these {}generics", + if self.gen_args.parenthesized == hir::GenericArgsParentheses::ParenSugar { + "parenthetical " + } else { + "" + }, + ); + + err.span_suggestion(span, msg, "", Applicability::MaybeIncorrect); + } else if redundant_lifetime_args && redundant_type_or_const_args { + remove_lifetime_args(err); + remove_type_or_const_args(err); + } else if redundant_lifetime_args { + remove_lifetime_args(err); + } else { + assert!(redundant_type_or_const_args); + remove_type_or_const_args(err); + } + } + + /// Builds the `type defined here` message. + fn show_definition(&self, err: &mut Diag<'_, impl EmissionGuarantee>) { + let mut spans: MultiSpan = if let Some(def_span) = self.tcx.def_ident_span(self.def_id) { + if self.tcx.sess.source_map().is_span_accessible(def_span) { + def_span.into() + } else { + return; + } + } else { + return; + }; + + let msg = { + let def_kind = self.tcx.def_descr(self.def_id); + let (quantifier, bound) = self.get_quantifier_and_bound(); + + let params = if bound == 0 { + String::new() + } else { + let params = self + .gen_params + .own_params + .iter() + .skip(self.params_offset) + .take(bound) + .map(|param| { + let span = self.tcx.def_span(param.def_id); + spans.push_span_label(span, ""); + param + }) + .map(|param| format!("`{}`", param.name)) + .collect::<Vec<_>>() + .join(", "); + + format!(": {params}") + }; + + format!( + "{} defined here, with {}{} {} parameter{}{}", + def_kind, + quantifier, + bound, + self.kind(), + pluralize!(bound), + params, + ) + }; + + err.span_note(spans, msg); + } + + /// Add note if `impl Trait` is explicitly specified. + fn note_synth_provided(&self, err: &mut Diag<'_, impl EmissionGuarantee>) { + if !self.is_synth_provided() { + return; + } + + err.note("`impl Trait` cannot be explicitly specified as a generic argument"); + } +} + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for WrongNumberOfGenericArgs<'_, '_> { + fn into_diag( + self, + dcx: rustc_errors::DiagCtxtHandle<'a>, + level: rustc_errors::Level, + ) -> Diag<'a, G> { + let msg = self.create_error_message(); + let mut err = Diag::new(dcx, level, msg); + err.code(E0107); + err.span(self.path_segment.ident.span); + + self.notify(&mut err); + self.suggest(&mut err); + self.show_definition(&mut err); + self.note_synth_provided(&mut err); + + err + } +} |
