| 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 |
| } |