]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/internal.rs
lint: add diagnostic translation migration lints
[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::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
9 use rustc_hir::{HirId, Impl, Item, ItemKind, Node, Pat, 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 use rustc_span::Span;
15 use tracing::debug;
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 Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return };
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 /// Helper function for lints that check for expressions with calls and use typeck results to
52 /// get the `DefId` and `SubstsRef` of the function.
53 fn typeck_results_of_method_fn<'tcx>(
54     cx: &LateContext<'tcx>,
55     expr: &Expr<'_>,
56 ) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> {
57     // FIXME(rustdoc): Lints which use this function use typecheck results which can cause
58     // `rustdoc` to error if there are resolution failures.
59     //
60     // As internal lints are currently always run if there are `unstable_options`, they are added
61     // to the lint store of rustdoc. Internal lints are also not used via the `lint_mod` query.
62     // Crate lints run outside of a query so rustdoc currently doesn't disable them.
63     //
64     // Instead of relying on this, either change crate lints to a query disabled by rustdoc, only
65     // run internal lints if the user is explicitly opting in or figure out a different way to
66     // avoid running lints for rustdoc.
67     if cx.tcx.sess.opts.actually_rustdoc {
68         return None;
69     }
70
71     match expr.kind {
72         ExprKind::MethodCall(segment, _, _)
73             if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
74         {
75             Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id)))
76         },
77         _ => {
78             match cx.typeck_results().node_type(expr.hir_id).kind() {
79                 &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)),
80                 _ => None,
81             }
82         }
83     }
84 }
85
86 declare_tool_lint! {
87     pub rustc::POTENTIAL_QUERY_INSTABILITY,
88     Allow,
89     "require explicit opt-in when using potentially unstable methods or functions",
90     report_in_external_macro: true
91 }
92
93 declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]);
94
95 impl LateLintPass<'_> for QueryStability {
96     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
97         let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
98         if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) {
99             let def_id = instance.def_id();
100             if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
101                 cx.struct_span_lint(POTENTIAL_QUERY_INSTABILITY, span, |lint| {
102                     let msg = format!(
103                         "using `{}` can result in unstable query results",
104                         cx.tcx.item_name(def_id)
105                     );
106                     lint.build(&msg)
107                         .note("if you believe this case to be fine, allow this lint and add a comment explaining your rationale")
108                         .emit();
109                 })
110             }
111         }
112     }
113 }
114
115 declare_tool_lint! {
116     pub rustc::USAGE_OF_TY_TYKIND,
117     Allow,
118     "usage of `ty::TyKind` outside of the `ty::sty` module",
119     report_in_external_macro: true
120 }
121
122 declare_tool_lint! {
123     pub rustc::USAGE_OF_QUALIFIED_TY,
124     Allow,
125     "using `ty::{Ty,TyCtxt}` instead of importing it",
126     report_in_external_macro: true
127 }
128
129 declare_lint_pass!(TyTyKind => [
130     USAGE_OF_TY_TYKIND,
131     USAGE_OF_QUALIFIED_TY,
132 ]);
133
134 impl<'tcx> LateLintPass<'tcx> for TyTyKind {
135     fn check_path(
136         &mut self,
137         cx: &LateContext<'tcx>,
138         path: &'tcx rustc_hir::Path<'tcx>,
139         _: rustc_hir::HirId,
140     ) {
141         if let Some(segment) = path.segments.iter().nth_back(1)
142         && let Some(res) = &segment.res
143         && lint_ty_kind_usage(cx, res)
144         {
145             let span = path.span.with_hi(
146                 segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()
147             );
148             cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
149                 lint.build("usage of `ty::TyKind::<kind>`")
150                     .span_suggestion(
151                         span,
152                         "try using `ty::<kind>` directly",
153                         "ty".to_string(),
154                         Applicability::MaybeIncorrect, // ty maybe needs an import
155                     )
156                     .emit();
157             });
158         }
159     }
160
161     fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
162         match &ty.kind {
163             TyKind::Path(QPath::Resolved(_, path)) => {
164                 if lint_ty_kind_usage(cx, &path.res) {
165                     cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
166                         let hir = cx.tcx.hir();
167                         match hir.find(hir.get_parent_node(ty.hir_id)) {
168                             Some(Node::Pat(Pat {
169                                 kind:
170                                     PatKind::Path(qpath)
171                                     | PatKind::TupleStruct(qpath, ..)
172                                     | PatKind::Struct(qpath, ..),
173                                 ..
174                             })) => {
175                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
176                                     && qpath_ty.hir_id == ty.hir_id
177                                 {
178                                     lint.build("usage of `ty::TyKind::<kind>`")
179                                         .span_suggestion(
180                                             path.span,
181                                             "try using `ty::<kind>` directly",
182                                             "ty".to_string(),
183                                             Applicability::MaybeIncorrect, // ty maybe needs an import
184                                         )
185                                         .emit();
186                                     return;
187                                 }
188                             }
189                             Some(Node::Expr(Expr {
190                                 kind: ExprKind::Path(qpath),
191                                 ..
192                             })) => {
193                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
194                                     && qpath_ty.hir_id == ty.hir_id
195                                 {
196                                     lint.build("usage of `ty::TyKind::<kind>`")
197                                         .span_suggestion(
198                                             path.span,
199                                             "try using `ty::<kind>` directly",
200                                             "ty".to_string(),
201                                             Applicability::MaybeIncorrect, // ty maybe needs an import
202                                         )
203                                         .emit();
204                                     return;
205                                 }
206                             }
207                             // Can't unify these two branches because qpath below is `&&` and above is `&`
208                             // and `A | B` paths don't play well together with adjustments, apparently.
209                             Some(Node::Expr(Expr {
210                                 kind: ExprKind::Struct(qpath, ..),
211                                 ..
212                             })) => {
213                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
214                                     && qpath_ty.hir_id == ty.hir_id
215                                 {
216                                     lint.build("usage of `ty::TyKind::<kind>`")
217                                         .span_suggestion(
218                                             path.span,
219                                             "try using `ty::<kind>` directly",
220                                             "ty".to_string(),
221                                             Applicability::MaybeIncorrect, // ty maybe needs an import
222                                         )
223                                         .emit();
224                                     return;
225                                 }
226                             }
227                             _ => {}
228                         }
229                         lint.build("usage of `ty::TyKind`").help("try using `Ty` instead").emit();
230                     })
231                 } else if !ty.span.from_expansion() && let Some(t) = is_ty_or_ty_ctxt(cx, &path) {
232                     if path.segments.len() > 1 {
233                         cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
234                             lint.build(&format!("usage of qualified `ty::{}`", t))
235                                 .span_suggestion(
236                                     path.span,
237                                     "try importing it and using it unqualified",
238                                     t,
239                                     // The import probably needs to be changed
240                                     Applicability::MaybeIncorrect,
241                                 )
242                                 .emit();
243                         })
244                     }
245                 }
246             }
247             _ => {}
248         }
249     }
250 }
251
252 fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
253     if let Some(did) = res.opt_def_id() {
254         cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
255     } else {
256         false
257     }
258 }
259
260 fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> {
261     match &path.res {
262         Res::Def(_, def_id) => {
263             if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
264                 return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
265             }
266         }
267         // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
268         Res::SelfTy { trait_: None, alias_to: Some((did, _)) } => {
269             if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
270                 if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
271                 {
272                     // NOTE: This path is currently unreachable as `Ty<'tcx>` is
273                     // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
274                     // is not actually allowed.
275                     //
276                     // I(@lcnr) still kept this branch in so we don't miss this
277                     // if we ever change it in the future.
278                     return Some(format!("{}<{}>", name, substs[0]));
279                 }
280             }
281         }
282         _ => (),
283     }
284
285     None
286 }
287
288 fn gen_args(segment: &PathSegment<'_>) -> String {
289     if let Some(args) = &segment.args {
290         let lifetimes = args
291             .args
292             .iter()
293             .filter_map(|arg| {
294                 if let GenericArg::Lifetime(lt) = arg {
295                     Some(lt.name.ident().to_string())
296                 } else {
297                     None
298                 }
299             })
300             .collect::<Vec<_>>();
301
302         if !lifetimes.is_empty() {
303             return format!("<{}>", lifetimes.join(", "));
304         }
305     }
306
307     String::new()
308 }
309
310 declare_tool_lint! {
311     pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
312     Allow,
313     "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
314 }
315
316 declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
317
318 impl EarlyLintPass for LintPassImpl {
319     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
320         if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind {
321             if let Some(last) = lint_pass.path.segments.last() {
322                 if last.ident.name == sym::LintPass {
323                     let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
324                     let call_site = expn_data.call_site;
325                     if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
326                         && call_site.ctxt().outer_expn_data().kind
327                             != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
328                     {
329                         cx.struct_span_lint(
330                             LINT_PASS_IMPL_WITHOUT_MACRO,
331                             lint_pass.path.span,
332                             |lint| {
333                                 lint.build("implementing `LintPass` by hand")
334                                     .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
335                                     .emit();
336                             },
337                         )
338                     }
339                 }
340             }
341         }
342     }
343 }
344
345 declare_tool_lint! {
346     pub rustc::EXISTING_DOC_KEYWORD,
347     Allow,
348     "Check that documented keywords in std and core actually exist",
349     report_in_external_macro: true
350 }
351
352 declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]);
353
354 fn is_doc_keyword(s: Symbol) -> bool {
355     s <= kw::Union
356 }
357
358 impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
359     fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
360         for attr in cx.tcx.hir().attrs(item.hir_id()) {
361             if !attr.has_name(sym::doc) {
362                 continue;
363             }
364             if let Some(list) = attr.meta_item_list() {
365                 for nested in list {
366                     if nested.has_name(sym::keyword) {
367                         let v = nested
368                             .value_str()
369                             .expect("#[doc(keyword = \"...\")] expected a value!");
370                         if is_doc_keyword(v) {
371                             return;
372                         }
373                         cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| {
374                             lint.build(&format!(
375                                 "Found non-existing keyword `{}` used in \
376                                      `#[doc(keyword = \"...\")]`",
377                                 v,
378                             ))
379                             .help("only existing keywords are allowed in core/std")
380                             .emit();
381                         });
382                     }
383                 }
384             }
385         }
386     }
387 }
388
389 declare_tool_lint! {
390     pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
391     Allow,
392     "prevent creation of diagnostics which cannot be translated",
393     report_in_external_macro: true
394 }
395
396 declare_tool_lint! {
397     pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
398     Allow,
399     "prevent creation of diagnostics outside of `SessionDiagnostic`/`AddSubdiagnostic` impls",
400     report_in_external_macro: true
401 }
402
403 declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]);
404
405 impl LateLintPass<'_> for Diagnostics {
406     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
407         let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
408         debug!(?span, ?def_id, ?substs);
409         if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) &&
410             !cx.tcx.has_attr(instance.def_id(), sym::rustc_lint_diagnostics)
411         {
412             return;
413         }
414
415         let mut found_impl = false;
416         for (_, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
417             debug!(?parent);
418             if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent &&
419                 let Impl { of_trait: Some(of_trait), .. } = impl_ &&
420                 let Some(def_id) = of_trait.trait_def_id() &&
421                 let Some(name) = cx.tcx.get_diagnostic_name(def_id) &&
422                 matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic)
423             {
424                 found_impl = true;
425                 break;
426             }
427         }
428         debug!(?found_impl);
429         if !found_impl {
430             cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| {
431                 lint.build("diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls")
432                     .emit();
433             })
434         }
435
436         let mut found_diagnostic_message = false;
437         for ty in substs.types() {
438             debug!(?ty);
439             if let Some(adt_def) = ty.ty_adt_def() &&
440                 let Some(name) =  cx.tcx.get_diagnostic_name(adt_def.did()) &&
441                 matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage)
442             {
443                 found_diagnostic_message = true;
444                 break;
445             }
446         }
447         debug!(?found_diagnostic_message);
448         if !found_diagnostic_message {
449             cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| {
450                 lint.build("diagnostics should be created using translatable messages").emit();
451             })
452         }
453     }
454 }