]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/internal.rs
Rollup merge of #90374 - GuillaumeGomez:unify-rustdoc-book-titles, r=camelid
[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) => {
105                 if let QPath::Resolved(_, path) = qpath {
106                     if let Some(last) = path.segments.iter().last() {
107                         if lint_ty_kind_usage(cx, last) {
108                             cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
109                                 lint.build("usage of `ty::TyKind`")
110                                     .help("try using `Ty` instead")
111                                     .emit();
112                             })
113                         } else {
114                             if ty.span.from_expansion() {
115                                 return;
116                             }
117                             if let Some(t) = is_ty_or_ty_ctxt(cx, ty) {
118                                 if path.segments.len() > 1 {
119                                     cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
120                                         lint.build(&format!("usage of qualified `ty::{}`", t))
121                                             .span_suggestion(
122                                                 path.span,
123                                                 "try using it unqualified",
124                                                 t,
125                                                 // The import probably needs to be changed
126                                                 Applicability::MaybeIncorrect,
127                                             )
128                                             .emit();
129                                     })
130                                 }
131                             }
132                         }
133                     }
134                 }
135             }
136             TyKind::Rptr(_, MutTy { ty: inner_ty, mutbl: Mutability::Not }) => {
137                 if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) {
138                     if cx.tcx.impl_trait_ref(impl_did).is_some() {
139                         return;
140                     }
141                 }
142                 if let Some(t) = is_ty_or_ty_ctxt(cx, &inner_ty) {
143                     cx.struct_span_lint(TY_PASS_BY_REFERENCE, ty.span, |lint| {
144                         lint.build(&format!("passing `{}` by reference", t))
145                             .span_suggestion(
146                                 ty.span,
147                                 "try passing by value",
148                                 t,
149                                 // Changing type of function argument
150                                 Applicability::MaybeIncorrect,
151                             )
152                             .emit();
153                     })
154                 }
155             }
156             _ => {}
157         }
158     }
159 }
160
161 fn lint_ty_kind_usage(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> bool {
162     if let Some(res) = segment.res {
163         if let Some(did) = res.opt_def_id() {
164             return cx.tcx.is_diagnostic_item(sym::TyKind, did);
165         }
166     }
167
168     false
169 }
170
171 fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, ty: &Ty<'_>) -> Option<String> {
172     if let TyKind::Path(qpath) = &ty.kind {
173         if let QPath::Resolved(_, path) = qpath {
174             match path.res {
175                 Res::Def(_, def_id) => {
176                     if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(def_id)
177                     {
178                         return Some(format!(
179                             "{}{}",
180                             name,
181                             gen_args(path.segments.last().unwrap())
182                         ));
183                     }
184                 }
185                 // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
186                 Res::SelfTy(None, Some((did, _))) => {
187                     if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
188                         if let Some(name @ (sym::Ty | sym::TyCtxt)) =
189                             cx.tcx.get_diagnostic_name(adt.did)
190                         {
191                             // NOTE: This path is currently unreachable as `Ty<'tcx>` is
192                             // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
193                             // is not actually allowed.
194                             //
195                             // I(@lcnr) still kept this branch in so we don't miss this
196                             // if we ever change it in the future.
197                             return Some(format!("{}<{}>", name, substs[0]));
198                         }
199                     }
200                 }
201                 _ => (),
202             }
203         }
204     }
205
206     None
207 }
208
209 fn gen_args(segment: &PathSegment<'_>) -> String {
210     if let Some(args) = &segment.args {
211         let lifetimes = args
212             .args
213             .iter()
214             .filter_map(|arg| {
215                 if let GenericArg::Lifetime(lt) = arg {
216                     Some(lt.name.ident().to_string())
217                 } else {
218                     None
219                 }
220             })
221             .collect::<Vec<_>>();
222
223         if !lifetimes.is_empty() {
224             return format!("<{}>", lifetimes.join(", "));
225         }
226     }
227
228     String::new()
229 }
230
231 declare_tool_lint! {
232     pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
233     Allow,
234     "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
235 }
236
237 declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
238
239 impl EarlyLintPass for LintPassImpl {
240     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
241         if let ast::ItemKind::Impl(box ast::ImplKind { of_trait: Some(lint_pass), .. }) = &item.kind
242         {
243             if let Some(last) = lint_pass.path.segments.last() {
244                 if last.ident.name == sym::LintPass {
245                     let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
246                     let call_site = expn_data.call_site;
247                     if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
248                         && call_site.ctxt().outer_expn_data().kind
249                             != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
250                     {
251                         cx.struct_span_lint(
252                             LINT_PASS_IMPL_WITHOUT_MACRO,
253                             lint_pass.path.span,
254                             |lint| {
255                                 lint.build("implementing `LintPass` by hand")
256                                     .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
257                                     .emit();
258                             },
259                         )
260                     }
261                 }
262             }
263         }
264     }
265 }
266
267 declare_tool_lint! {
268     pub rustc::EXISTING_DOC_KEYWORD,
269     Allow,
270     "Check that documented keywords in std and core actually exist",
271     report_in_external_macro: true
272 }
273
274 declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]);
275
276 fn is_doc_keyword(s: Symbol) -> bool {
277     s <= kw::Union
278 }
279
280 impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
281     fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
282         for attr in cx.tcx.hir().attrs(item.hir_id()) {
283             if !attr.has_name(sym::doc) {
284                 continue;
285             }
286             if let Some(list) = attr.meta_item_list() {
287                 for nested in list {
288                     if nested.has_name(sym::keyword) {
289                         let v = nested
290                             .value_str()
291                             .expect("#[doc(keyword = \"...\")] expected a value!");
292                         if is_doc_keyword(v) {
293                             return;
294                         }
295                         cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| {
296                             lint.build(&format!(
297                                 "Found non-existing keyword `{}` used in \
298                                      `#[doc(keyword = \"...\")]`",
299                                 v,
300                             ))
301                             .help("only existing keywords are allowed in core/std")
302                             .emit();
303                         });
304                     }
305                 }
306             }
307         }
308     }
309 }