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