]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/internal.rs
Rollup merge of #90017 - jackh726:issue-tests, r=nikomatsakis
[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 use rustc_middle::ty;
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
11 use rustc_span::hygiene::{ExpnKind, MacroKind};
12 use rustc_span::symbol::{kw, sym, Symbol};
13
14 declare_tool_lint! {
15     pub rustc::DEFAULT_HASH_TYPES,
16     Allow,
17     "forbid HashMap and HashSet and suggest the FxHash* variants",
18     report_in_external_macro: true
19 }
20
21 declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
22
23 impl LateLintPass<'_> for DefaultHashTypes {
24     fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
25         let def_id = match path.res {
26             Res::Def(rustc_hir::def::DefKind::Struct, id) => id,
27             _ => return,
28         };
29         if matches!(cx.tcx.hir().get(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) {
30             // don't lint imports, only actual usages
31             return;
32         }
33         let replace = match cx.tcx.get_diagnostic_name(def_id) {
34             Some(sym::HashMap) => "FxHashMap",
35             Some(sym::HashSet) => "FxHashSet",
36             _ => return,
37         };
38         cx.struct_span_lint(DEFAULT_HASH_TYPES, path.span, |lint| {
39             let msg = format!(
40                 "prefer `{}` over `{}`, it has better performance",
41                 replace,
42                 cx.tcx.item_name(def_id)
43             );
44             lint.build(&msg)
45                 .note(&format!("a `use rustc_data_structures::fx::{}` may be necessary", replace))
46                 .emit();
47         });
48     }
49 }
50
51 declare_tool_lint! {
52     pub rustc::POTENTIAL_QUERY_INSTABILITY,
53     Allow,
54     "require explicit opt-in when using potentially unstable methods or functions",
55     report_in_external_macro: true
56 }
57
58 declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]);
59
60 impl LateLintPass<'_> for QueryStability {
61     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
62         // FIXME(rustdoc): This lint uses typecheck results, causing rustdoc to
63         // error if there are resolution failures.
64         //
65         // As internal lints are currently always run if there are `unstable_options`,
66         // they are added to the lint store of rustdoc. Internal lints are also
67         // not used via the `lint_mod` query. Crate lints run outside of a query
68         // so rustdoc currently doesn't disable them.
69         //
70         // Instead of relying on this, either change crate lints to a query disabled by
71         // rustdoc, only run internal lints if the user is explicitly opting in
72         // or figure out a different way to avoid running lints for rustdoc.
73         if cx.tcx.sess.opts.actually_rustdoc {
74             return;
75         }
76
77         let (def_id, span) = match expr.kind {
78             ExprKind::Path(ref path) if let Some(def_id) = cx.qpath_res(path, expr.hir_id).opt_def_id() => {
79                 (def_id, expr.span)
80             }
81             ExprKind::MethodCall(_, span, _, _) if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => {
82                 (def_id, span)
83             },
84             _ => return,
85         };
86
87         let substs = cx.typeck_results().node_substs(expr.hir_id);
88         if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) {
89             let def_id = instance.def_id();
90             if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
91                 cx.struct_span_lint(POTENTIAL_QUERY_INSTABILITY, span, |lint| {
92                     let msg = format!(
93                         "using `{}` can result in unstable query results",
94                         cx.tcx.item_name(def_id)
95                     );
96                     lint.build(&msg)
97                         .note("if you believe this case to be fine, allow this lint and add a comment explaining your rationale")
98                         .emit();
99                 })
100             }
101         }
102     }
103 }
104
105 declare_tool_lint! {
106     pub rustc::USAGE_OF_TY_TYKIND,
107     Allow,
108     "usage of `ty::TyKind` outside of the `ty::sty` module",
109     report_in_external_macro: true
110 }
111
112 declare_tool_lint! {
113     pub rustc::TY_PASS_BY_REFERENCE,
114     Allow,
115     "passing `Ty` or `TyCtxt` by reference",
116     report_in_external_macro: true
117 }
118
119 declare_tool_lint! {
120     pub rustc::USAGE_OF_QUALIFIED_TY,
121     Allow,
122     "using `ty::{Ty,TyCtxt}` instead of importing it",
123     report_in_external_macro: true
124 }
125
126 declare_lint_pass!(TyTyKind => [
127     USAGE_OF_TY_TYKIND,
128     TY_PASS_BY_REFERENCE,
129     USAGE_OF_QUALIFIED_TY,
130 ]);
131
132 impl<'tcx> LateLintPass<'tcx> for TyTyKind {
133     fn check_path(&mut self, cx: &LateContext<'_>, path: &'tcx Path<'tcx>, _: HirId) {
134         let segments = path.segments.iter().rev().skip(1).rev();
135
136         if let Some(last) = segments.last() {
137             let span = path.span.with_hi(last.ident.span.hi());
138             if lint_ty_kind_usage(cx, last) {
139                 cx.struct_span_lint(USAGE_OF_TY_TYKIND, span, |lint| {
140                     lint.build("usage of `ty::TyKind::<kind>`")
141                         .span_suggestion(
142                             span,
143                             "try using ty::<kind> directly",
144                             "ty".to_string(),
145                             Applicability::MaybeIncorrect, // ty maybe needs an import
146                         )
147                         .emit();
148                 })
149             }
150         }
151     }
152
153     fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
154         match &ty.kind {
155             TyKind::Path(qpath) => {
156                 if let QPath::Resolved(_, path) = qpath {
157                     if let Some(last) = path.segments.iter().last() {
158                         if lint_ty_kind_usage(cx, last) {
159                             cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
160                                 lint.build("usage of `ty::TyKind`")
161                                     .help("try using `Ty` instead")
162                                     .emit();
163                             })
164                         } else {
165                             if ty.span.from_expansion() {
166                                 return;
167                             }
168                             if let Some(t) = is_ty_or_ty_ctxt(cx, ty) {
169                                 if path.segments.len() > 1 {
170                                     cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
171                                         lint.build(&format!("usage of qualified `ty::{}`", t))
172                                             .span_suggestion(
173                                                 path.span,
174                                                 "try using it unqualified",
175                                                 t,
176                                                 // The import probably needs to be changed
177                                                 Applicability::MaybeIncorrect,
178                                             )
179                                             .emit();
180                                     })
181                                 }
182                             }
183                         }
184                     }
185                 }
186             }
187             TyKind::Rptr(_, MutTy { ty: inner_ty, mutbl: Mutability::Not }) => {
188                 if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) {
189                     if cx.tcx.impl_trait_ref(impl_did).is_some() {
190                         return;
191                     }
192                 }
193                 if let Some(t) = is_ty_or_ty_ctxt(cx, &inner_ty) {
194                     cx.struct_span_lint(TY_PASS_BY_REFERENCE, ty.span, |lint| {
195                         lint.build(&format!("passing `{}` by reference", t))
196                             .span_suggestion(
197                                 ty.span,
198                                 "try passing by value",
199                                 t,
200                                 // Changing type of function argument
201                                 Applicability::MaybeIncorrect,
202                             )
203                             .emit();
204                     })
205                 }
206             }
207             _ => {}
208         }
209     }
210 }
211
212 fn lint_ty_kind_usage(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> bool {
213     if let Some(res) = segment.res {
214         if let Some(did) = res.opt_def_id() {
215             return cx.tcx.is_diagnostic_item(sym::TyKind, did);
216         }
217     }
218
219     false
220 }
221
222 fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, ty: &Ty<'_>) -> Option<String> {
223     if let TyKind::Path(qpath) = &ty.kind {
224         if let QPath::Resolved(_, path) = qpath {
225             match path.res {
226                 Res::Def(_, def_id) => {
227                     if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(def_id)
228                     {
229                         return Some(format!(
230                             "{}{}",
231                             name,
232                             gen_args(path.segments.last().unwrap())
233                         ));
234                     }
235                 }
236                 // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
237                 Res::SelfTy(None, Some((did, _))) => {
238                     if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
239                         if let Some(name @ (sym::Ty | sym::TyCtxt)) =
240                             cx.tcx.get_diagnostic_name(adt.did)
241                         {
242                             // NOTE: This path is currently unreachable as `Ty<'tcx>` is
243                             // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
244                             // is not actually allowed.
245                             //
246                             // I(@lcnr) still kept this branch in so we don't miss this
247                             // if we ever change it in the future.
248                             return Some(format!("{}<{}>", name, substs[0]));
249                         }
250                     }
251                 }
252                 _ => (),
253             }
254         }
255     }
256
257     None
258 }
259
260 fn gen_args(segment: &PathSegment<'_>) -> String {
261     if let Some(args) = &segment.args {
262         let lifetimes = args
263             .args
264             .iter()
265             .filter_map(|arg| {
266                 if let GenericArg::Lifetime(lt) = arg {
267                     Some(lt.name.ident().to_string())
268                 } else {
269                     None
270                 }
271             })
272             .collect::<Vec<_>>();
273
274         if !lifetimes.is_empty() {
275             return format!("<{}>", lifetimes.join(", "));
276         }
277     }
278
279     String::new()
280 }
281
282 declare_tool_lint! {
283     pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
284     Allow,
285     "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
286 }
287
288 declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
289
290 impl EarlyLintPass for LintPassImpl {
291     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
292         if let ast::ItemKind::Impl(box ast::ImplKind { of_trait: Some(lint_pass), .. }) = &item.kind
293         {
294             if let Some(last) = lint_pass.path.segments.last() {
295                 if last.ident.name == sym::LintPass {
296                     let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
297                     let call_site = expn_data.call_site;
298                     if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
299                         && call_site.ctxt().outer_expn_data().kind
300                             != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
301                     {
302                         cx.struct_span_lint(
303                             LINT_PASS_IMPL_WITHOUT_MACRO,
304                             lint_pass.path.span,
305                             |lint| {
306                                 lint.build("implementing `LintPass` by hand")
307                                     .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
308                                     .emit();
309                             },
310                         )
311                     }
312                 }
313             }
314         }
315     }
316 }
317
318 declare_tool_lint! {
319     pub rustc::EXISTING_DOC_KEYWORD,
320     Allow,
321     "Check that documented keywords in std and core actually exist",
322     report_in_external_macro: true
323 }
324
325 declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]);
326
327 fn is_doc_keyword(s: Symbol) -> bool {
328     s <= kw::Union
329 }
330
331 impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
332     fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
333         for attr in cx.tcx.hir().attrs(item.hir_id()) {
334             if !attr.has_name(sym::doc) {
335                 continue;
336             }
337             if let Some(list) = attr.meta_item_list() {
338                 for nested in list {
339                     if nested.has_name(sym::keyword) {
340                         let v = nested
341                             .value_str()
342                             .expect("#[doc(keyword = \"...\")] expected a value!");
343                         if is_doc_keyword(v) {
344                             return;
345                         }
346                         cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| {
347                             lint.build(&format!(
348                                 "Found non-existing keyword `{}` used in \
349                                      `#[doc(keyword = \"...\")]`",
350                                 v,
351                             ))
352                             .help("only existing keywords are allowed in core/std")
353                             .emit();
354                         });
355                     }
356                 }
357             }
358         }
359     }
360 }