]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/internal.rs
more clippy fixes
[rust.git] / compiler / rustc_lint / src / internal.rs
1 //! Some lints that are only useful in the compiler or crates that use compiler internals, such as
2 //! Clippy.
3
4 use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
5 use rustc_ast as ast;
6 use rustc_errors::Applicability;
7 use rustc_hir::def::Res;
8 use rustc_hir::{
9     GenericArg, HirId, Item, ItemKind, MutTy, Mutability, Node, Path, PathSegment, QPath, Ty,
10     TyKind,
11 };
12 use rustc_middle::ty;
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 use rustc_span::hygiene::{ExpnKind, MacroKind};
15 use rustc_span::symbol::{kw, sym, Symbol};
16
17 declare_tool_lint! {
18     pub rustc::DEFAULT_HASH_TYPES,
19     Allow,
20     "forbid HashMap and HashSet and suggest the FxHash* variants",
21     report_in_external_macro: true
22 }
23
24 declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
25
26 impl LateLintPass<'_> for DefaultHashTypes {
27     fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
28         let def_id = match path.res {
29             Res::Def(rustc_hir::def::DefKind::Struct, id) => id,
30             _ => return,
31         };
32         if matches!(cx.tcx.hir().get(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) {
33             // don't lint imports, only actual usages
34             return;
35         }
36         let replace = match cx.tcx.get_diagnostic_name(def_id) {
37             Some(sym::HashMap) => "FxHashMap",
38             Some(sym::HashSet) => "FxHashSet",
39             _ => return,
40         };
41         cx.struct_span_lint(DEFAULT_HASH_TYPES, path.span, |lint| {
42             let msg = format!(
43                 "prefer `{}` over `{}`, it has better performance",
44                 replace,
45                 cx.tcx.item_name(def_id)
46             );
47             lint.build(&msg)
48                 .note(&format!("a `use rustc_data_structures::fx::{}` may be necessary", replace))
49                 .emit();
50         });
51     }
52 }
53
54 declare_tool_lint! {
55     pub rustc::USAGE_OF_TY_TYKIND,
56     Allow,
57     "usage of `ty::TyKind` outside of the `ty::sty` module",
58     report_in_external_macro: true
59 }
60
61 declare_tool_lint! {
62     pub rustc::TY_PASS_BY_REFERENCE,
63     Allow,
64     "passing `Ty` or `TyCtxt` by reference",
65     report_in_external_macro: true
66 }
67
68 declare_tool_lint! {
69     pub rustc::USAGE_OF_QUALIFIED_TY,
70     Allow,
71     "using `ty::{Ty,TyCtxt}` instead of importing it",
72     report_in_external_macro: true
73 }
74
75 declare_lint_pass!(TyTyKind => [
76     USAGE_OF_TY_TYKIND,
77     TY_PASS_BY_REFERENCE,
78     USAGE_OF_QUALIFIED_TY,
79 ]);
80
81 impl<'tcx> LateLintPass<'tcx> for TyTyKind {
82     fn check_path(&mut self, cx: &LateContext<'_>, path: &'tcx Path<'tcx>, _: HirId) {
83         let segments = path.segments.iter().rev().skip(1).rev();
84
85         if let Some(last) = segments.last() {
86             let span = path.span.with_hi(last.ident.span.hi());
87             if lint_ty_kind_usage(cx, last) {
88                 cx.struct_span_lint(USAGE_OF_TY_TYKIND, span, |lint| {
89                     lint.build("usage of `ty::TyKind::<kind>`")
90                         .span_suggestion(
91                             span,
92                             "try using ty::<kind> directly",
93                             "ty".to_string(),
94                             Applicability::MaybeIncorrect, // ty maybe needs an import
95                         )
96                         .emit();
97                 })
98             }
99         }
100     }
101
102     fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
103         match &ty.kind {
104             TyKind::Path(QPath::Resolved(_, path)) => {
105                 if let Some(last) = path.segments.iter().last() {
106                     if lint_ty_kind_usage(cx, last) {
107                         cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
108                             lint.build("usage of `ty::TyKind`")
109                                 .help("try using `Ty` instead")
110                                 .emit();
111                         })
112                     } else {
113                         if ty.span.from_expansion() {
114                             return;
115                         }
116                         if let Some(t) = is_ty_or_ty_ctxt(cx, ty) {
117                             if path.segments.len() > 1 {
118                                 cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
119                                     lint.build(&format!("usage of qualified `ty::{}`", t))
120                                         .span_suggestion(
121                                             path.span,
122                                             "try using it unqualified",
123                                             t,
124                                             // The import probably needs to be changed
125                                             Applicability::MaybeIncorrect,
126                                         )
127                                         .emit();
128                                 })
129                             }
130                         }
131                     }
132                 }
133             }
134             TyKind::Rptr(_, MutTy { ty: inner_ty, mutbl: Mutability::Not }) => {
135                 if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) {
136                     if cx.tcx.impl_trait_ref(impl_did).is_some() {
137                         return;
138                     }
139                 }
140                 if let Some(t) = is_ty_or_ty_ctxt(cx, &inner_ty) {
141                     cx.struct_span_lint(TY_PASS_BY_REFERENCE, ty.span, |lint| {
142                         lint.build(&format!("passing `{}` by reference", t))
143                             .span_suggestion(
144                                 ty.span,
145                                 "try passing by value",
146                                 t,
147                                 // Changing type of function argument
148                                 Applicability::MaybeIncorrect,
149                             )
150                             .emit();
151                     })
152                 }
153             }
154             _ => {}
155         }
156     }
157 }
158
159 fn lint_ty_kind_usage(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> bool {
160     if let Some(res) = segment.res {
161         if let Some(did) = res.opt_def_id() {
162             return cx.tcx.is_diagnostic_item(sym::TyKind, did);
163         }
164     }
165
166     false
167 }
168
169 fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, ty: &Ty<'_>) -> Option<String> {
170     if let TyKind::Path(QPath::Resolved(_, path)) = &ty.kind {
171         match path.res {
172             Res::Def(_, def_id) => {
173                 if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(def_id) {
174                     return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
175                 }
176             }
177             // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
178             Res::SelfTy(None, Some((did, _))) => {
179                 if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
180                     if let Some(name @ (sym::Ty | sym::TyCtxt)) =
181                         cx.tcx.get_diagnostic_name(adt.did)
182                     {
183                         // NOTE: This path is currently unreachable as `Ty<'tcx>` is
184                         // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
185                         // is not actually allowed.
186                         //
187                         // I(@lcnr) still kept this branch in so we don't miss this
188                         // if we ever change it in the future.
189                         return Some(format!("{}<{}>", name, substs[0]));
190                     }
191                 }
192             }
193             _ => (),
194         }
195     }
196
197     None
198 }
199
200 fn gen_args(segment: &PathSegment<'_>) -> String {
201     if let Some(args) = &segment.args {
202         let lifetimes = args
203             .args
204             .iter()
205             .filter_map(|arg| {
206                 if let GenericArg::Lifetime(lt) = arg {
207                     Some(lt.name.ident().to_string())
208                 } else {
209                     None
210                 }
211             })
212             .collect::<Vec<_>>();
213
214         if !lifetimes.is_empty() {
215             return format!("<{}>", lifetimes.join(", "));
216         }
217     }
218
219     String::new()
220 }
221
222 declare_tool_lint! {
223     pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
224     Allow,
225     "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
226 }
227
228 declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
229
230 impl EarlyLintPass for LintPassImpl {
231     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
232         if let ast::ItemKind::Impl(box ast::ImplKind { of_trait: Some(lint_pass), .. }) = &item.kind
233         {
234             if let Some(last) = lint_pass.path.segments.last() {
235                 if last.ident.name == sym::LintPass {
236                     let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
237                     let call_site = expn_data.call_site;
238                     if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
239                         && call_site.ctxt().outer_expn_data().kind
240                             != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
241                     {
242                         cx.struct_span_lint(
243                             LINT_PASS_IMPL_WITHOUT_MACRO,
244                             lint_pass.path.span,
245                             |lint| {
246                                 lint.build("implementing `LintPass` by hand")
247                                     .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
248                                     .emit();
249                             },
250                         )
251                     }
252                 }
253             }
254         }
255     }
256 }
257
258 declare_tool_lint! {
259     pub rustc::EXISTING_DOC_KEYWORD,
260     Allow,
261     "Check that documented keywords in std and core actually exist",
262     report_in_external_macro: true
263 }
264
265 declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]);
266
267 fn is_doc_keyword(s: Symbol) -> bool {
268     s <= kw::Union
269 }
270
271 impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
272     fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
273         for attr in cx.tcx.hir().attrs(item.hir_id()) {
274             if !attr.has_name(sym::doc) {
275                 continue;
276             }
277             if let Some(list) = attr.meta_item_list() {
278                 for nested in list {
279                     if nested.has_name(sym::keyword) {
280                         let v = nested
281                             .value_str()
282                             .expect("#[doc(keyword = \"...\")] expected a value!");
283                         if is_doc_keyword(v) {
284                             return;
285                         }
286                         cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| {
287                             lint.build(&format!(
288                                 "Found non-existing keyword `{}` used in \
289                                      `#[doc(keyword = \"...\")]`",
290                                 v,
291                             ))
292                             .help("only existing keywords are allowed in core/std")
293                             .emit();
294                         });
295                     }
296                 }
297             }
298         }
299     }
300 }