blob: 9ddba7a6e7a9bbf01519b2e57a9cf452bf25cbe8 [file] [log] [blame]
use crate::structured_errors::StructuredDiag;
use rustc_errors::{codes::*, pluralize, Applicability, Diag, MultiSpan};
use rustc_hir as hir;
use rustc_middle::ty::{self as ty, AssocItems, AssocKind, TyCtxt};
use rustc_session::Session;
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.bindings.iter().any(|binding| binding.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}`")
}
}
fn start_diagnostics(&self) -> Diag<'tcx> {
let span = self.path_segment.ident.span;
let msg = self.create_error_message();
self.tcx.dcx().struct_span_err(span, msg).with_code(self.code())
}
/// Builds the `expected 1 type argument / supplied 2 type arguments` message.
fn notify(&self, err: &mut Diag<'_>) {
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<'_>) {
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<'_>) {
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<'_>) {
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_bindings = !self.gen_args.bindings.is_empty();
let sugg_prefix = if is_first { "" } else { ", " };
let sugg_suffix =
if is_first && (has_non_lt_args || has_bindings) { ", " } 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<'_>) {
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.bindings.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<'_>) {
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<'_>,
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<'_>,
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<'_>) {
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<'_>) {
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<'_>) {
if !self.is_synth_provided() {
return;
}
err.note("`impl Trait` cannot be explicitly specified as a generic argument");
}
}
impl<'tcx> StructuredDiag<'tcx> for WrongNumberOfGenericArgs<'_, 'tcx> {
fn session(&self) -> &Session {
self.tcx.sess
}
fn code(&self) -> ErrCode {
E0107
}
fn diagnostic_common(&self) -> Diag<'tcx> {
let mut err = self.start_diagnostics();
self.notify(&mut err);
self.suggest(&mut err);
self.show_definition(&mut err);
self.note_synth_provided(&mut err);
err
}
}