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