blob: 934b9f490addf9da7e9a10a08874d61ac2e861d4 [file] [log] [blame]
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{GenericArg, Item, ItemKind, QPath, Ty, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::GenericParamDefKind;
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for type parameters which are positioned inconsistently between
/// a type definition and impl block. Specifically, a parameter in an impl
/// block which has the same name as a parameter in the type def, but is in
/// a different place.
///
/// ### Why is this bad?
/// Type parameters are determined by their position rather than name.
/// Naming type parameters inconsistently may cause you to refer to the
/// wrong type parameter.
///
/// ### Limitations
/// This lint only applies to impl blocks with simple generic params, e.g.
/// `A`. If there is anything more complicated, such as a tuple, it will be
/// ignored.
///
/// ### Example
/// ```no_run
/// struct Foo<A, B> {
/// x: A,
/// y: B,
/// }
/// // inside the impl, B refers to Foo::A
/// impl<B, A> Foo<B, A> {}
/// ```
/// Use instead:
/// ```no_run
/// struct Foo<A, B> {
/// x: A,
/// y: B,
/// }
/// impl<A, B> Foo<A, B> {}
/// ```
#[clippy::version = "1.63.0"]
pub MISMATCHING_TYPE_PARAM_ORDER,
pedantic,
"type parameter positioned inconsistently between type def and impl block"
}
declare_lint_pass!(TypeParamMismatch => [MISMATCHING_TYPE_PARAM_ORDER]);
impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if !item.span.from_expansion()
&& let ItemKind::Impl(imp) = &item.kind
&& let TyKind::Path(QPath::Resolved(_, path)) = &imp.self_ty.kind
&& let Some(segment) = path.segments.iter().next()
&& let Some(generic_args) = segment.args
&& !generic_args.args.is_empty()
{
// get the name and span of the generic parameters in the Impl
let mut impl_params = Vec::new();
for p in generic_args.args {
match p {
GenericArg::Type(Ty {
kind: TyKind::Path(QPath::Resolved(_, path)),
..
}) => impl_params.push((path.segments[0].ident.to_string(), path.span)),
GenericArg::Type(_) => return,
_ => (),
};
}
// find the type that the Impl is for
// only lint on struct/enum/union for now
let Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, defid) = path.res else {
return;
};
// get the names of the generic parameters in the type
let type_params = &cx.tcx.generics_of(defid).own_params;
let type_param_names: Vec<_> = type_params
.iter()
.filter_map(|p| match p.kind {
GenericParamDefKind::Type { .. } => Some(p.name.to_string()),
_ => None,
})
.collect();
// hashmap of name -> index for mismatch_param_name
let type_param_names_hashmap: FxHashMap<&String, usize> = type_param_names
.iter()
.enumerate()
.map(|(i, param)| (param, i))
.collect();
let type_name = segment.ident;
for (i, (impl_param_name, impl_param_span)) in impl_params.iter().enumerate() {
if mismatch_param_name(i, impl_param_name, &type_param_names_hashmap) {
let msg = format!(
"`{type_name}` has a similarly named generic type parameter `{impl_param_name}` in its declaration, but in a different order"
);
let help = format!(
"try `{}`, or a name that does not conflict with `{type_name}`'s generic params",
type_param_names[i]
);
span_lint_and_help(cx, MISMATCHING_TYPE_PARAM_ORDER, *impl_param_span, msg, None, help);
}
}
}
}
}
// Checks if impl_param_name is the same as one of type_param_names,
// and is in a different position
fn mismatch_param_name(i: usize, impl_param_name: &String, type_param_names: &FxHashMap<&String, usize>) -> bool {
if let Some(j) = type_param_names.get(impl_param_name) {
if i != *j {
return true;
}
}
false
}