1 | use clippy_utils::diagnostics::{span_lint, span_lint_hir_and_then}; |
2 | use clippy_utils::path_res; |
3 | use clippy_utils::ty::implements_trait; |
4 | use rustc_hir::def_id::{DefId, LocalDefId}; |
5 | use rustc_hir::{Item, ItemKind}; |
6 | use rustc_lint::{LateContext, LateLintPass}; |
7 | use rustc_middle::ty::Visibility; |
8 | use rustc_session::declare_lint_pass; |
9 | use rustc_span::sym; |
10 | |
11 | declare_clippy_lint! { |
12 | /// ### What it does |
13 | /// Checks for types named `Error` that implement `Error`. |
14 | /// |
15 | /// ### Why restrict this? |
16 | /// It can become confusing when a codebase has 20 types all named `Error`, requiring either |
17 | /// aliasing them in the `use` statement or qualifying them like `my_module::Error`. This |
18 | /// hinders comprehension, as it requires you to memorize every variation of importing `Error` |
19 | /// used across a codebase. |
20 | /// |
21 | /// ### Example |
22 | /// ```rust,ignore |
23 | /// #[derive(Debug)] |
24 | /// pub enum Error { ... } |
25 | /// |
26 | /// impl std::fmt::Display for Error { ... } |
27 | /// |
28 | /// impl std::error::Error for Error { ... } |
29 | /// ``` |
30 | #[clippy::version = "1.73.0" ] |
31 | pub ERROR_IMPL_ERROR, |
32 | restriction, |
33 | "exported types named `Error` that implement `Error`" |
34 | } |
35 | declare_lint_pass!(ErrorImplError => [ERROR_IMPL_ERROR]); |
36 | |
37 | impl<'tcx> LateLintPass<'tcx> for ErrorImplError { |
38 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { |
39 | match item.kind { |
40 | ItemKind::TyAlias(ident, ..) |
41 | if ident.name == sym::Error |
42 | && is_visible_outside_module(cx, item.owner_id.def_id) |
43 | && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() |
44 | && let Some(error_def_id) = cx.tcx.get_diagnostic_item(sym::Error) |
45 | && implements_trait(cx, ty, error_def_id, &[]) => |
46 | { |
47 | span_lint( |
48 | cx, |
49 | ERROR_IMPL_ERROR, |
50 | ident.span, |
51 | "exported type alias named `Error` that implements `Error`" , |
52 | ); |
53 | }, |
54 | ItemKind::Impl(imp) |
55 | if let Some(trait_def_id) = imp.of_trait.and_then(|t| t.trait_def_id()) |
56 | && let Some(error_def_id) = cx.tcx.get_diagnostic_item(sym::Error) |
57 | && error_def_id == trait_def_id |
58 | && let Some(def_id) = path_res(cx, imp.self_ty).opt_def_id().and_then(DefId::as_local) |
59 | && let Some(ident) = cx.tcx.opt_item_ident(def_id.to_def_id()) |
60 | && ident.name == sym::Error |
61 | && is_visible_outside_module(cx, def_id) => |
62 | { |
63 | span_lint_hir_and_then( |
64 | cx, |
65 | ERROR_IMPL_ERROR, |
66 | cx.tcx.local_def_id_to_hir_id(def_id), |
67 | ident.span, |
68 | "exported type named `Error` that implements `Error`" , |
69 | |diag| { |
70 | diag.span_note(item.span, "`Error` was implemented here" ); |
71 | }, |
72 | ); |
73 | }, |
74 | _ => {}, |
75 | } |
76 | } |
77 | } |
78 | |
79 | /// Do not lint private `Error`s, i.e., ones without any `pub` (minus `pub(self)` of course) and |
80 | /// which aren't reexported |
81 | fn is_visible_outside_module(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { |
82 | !matches!( |
83 | cx.tcx.visibility(def_id), |
84 | Visibility::Restricted(mod_def_id) if cx.tcx.parent_module_from_def_id(def_id).to_def_id() == mod_def_id |
85 | ) |
86 | } |
87 | |