]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/internal.rs
d8a03024d132334d424361582457a34816612e6c
[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::{fluent, 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
16 declare_tool_lint! {
17     pub rustc::DEFAULT_HASH_TYPES,
18     Allow,
19     "forbid HashMap and HashSet and suggest the FxHash* variants",
20     report_in_external_macro: true
21 }
22
23 declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
24
25 impl LateLintPass<'_> for DefaultHashTypes {
26     fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
27         let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return };
28         if matches!(cx.tcx.hir().get(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) {
29             // don't lint imports, only actual usages
30             return;
31         }
32         let replace = match cx.tcx.get_diagnostic_name(def_id) {
33             Some(sym::HashMap) => "FxHashMap",
34             Some(sym::HashSet) => "FxHashSet",
35             _ => return,
36         };
37         cx.struct_span_lint(DEFAULT_HASH_TYPES, path.span, |lint| {
38             lint.build(fluent::lint::default_hash_types)
39                 .set_arg("preferred", replace)
40                 .set_arg("used", cx.tcx.item_name(def_id))
41                 .note(fluent::lint::note)
42                 .emit();
43         });
44     }
45 }
46
47 /// Helper function for lints that check for expressions with calls and use typeck results to
48 /// get the `DefId` and `SubstsRef` of the function.
49 fn typeck_results_of_method_fn<'tcx>(
50     cx: &LateContext<'tcx>,
51     expr: &Expr<'_>,
52 ) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> {
53     match expr.kind {
54         ExprKind::MethodCall(segment, ..)
55             if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
56         {
57             Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id)))
58         },
59         _ => {
60             match cx.typeck_results().node_type(expr.hir_id).kind() {
61                 &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)),
62                 _ => None,
63             }
64         }
65     }
66 }
67
68 declare_tool_lint! {
69     pub rustc::POTENTIAL_QUERY_INSTABILITY,
70     Allow,
71     "require explicit opt-in when using potentially unstable methods or functions",
72     report_in_external_macro: true
73 }
74
75 declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]);
76
77 impl LateLintPass<'_> for QueryStability {
78     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
79         let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
80         if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) {
81             let def_id = instance.def_id();
82             if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
83                 cx.struct_span_lint(POTENTIAL_QUERY_INSTABILITY, span, |lint| {
84                     lint.build(fluent::lint::query_instability)
85                         .set_arg("query", cx.tcx.item_name(def_id))
86                         .note(fluent::lint::note)
87                         .emit();
88                 })
89             }
90         }
91     }
92 }
93
94 declare_tool_lint! {
95     pub rustc::USAGE_OF_TY_TYKIND,
96     Allow,
97     "usage of `ty::TyKind` outside of the `ty::sty` module",
98     report_in_external_macro: true
99 }
100
101 declare_tool_lint! {
102     pub rustc::USAGE_OF_QUALIFIED_TY,
103     Allow,
104     "using `ty::{Ty,TyCtxt}` instead of importing it",
105     report_in_external_macro: true
106 }
107
108 declare_lint_pass!(TyTyKind => [
109     USAGE_OF_TY_TYKIND,
110     USAGE_OF_QUALIFIED_TY,
111 ]);
112
113 impl<'tcx> LateLintPass<'tcx> for TyTyKind {
114     fn check_path(
115         &mut self,
116         cx: &LateContext<'tcx>,
117         path: &'tcx rustc_hir::Path<'tcx>,
118         _: rustc_hir::HirId,
119     ) {
120         if let Some(segment) = path.segments.iter().nth_back(1)
121         && lint_ty_kind_usage(cx, &segment.res)
122         {
123             let span = path.span.with_hi(
124                 segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()
125             );
126             cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
127                 lint.build(fluent::lint::tykind_kind)
128                     .span_suggestion(
129                         span,
130                         fluent::lint::suggestion,
131                         "ty",
132                         Applicability::MaybeIncorrect, // ty maybe needs an import
133                     )
134                     .emit();
135             });
136         }
137     }
138
139     fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
140         match &ty.kind {
141             TyKind::Path(QPath::Resolved(_, path)) => {
142                 if lint_ty_kind_usage(cx, &path.res) {
143                     cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
144                         let hir = cx.tcx.hir();
145                         match hir.find(hir.get_parent_node(ty.hir_id)) {
146                             Some(Node::Pat(Pat {
147                                 kind:
148                                     PatKind::Path(qpath)
149                                     | PatKind::TupleStruct(qpath, ..)
150                                     | PatKind::Struct(qpath, ..),
151                                 ..
152                             })) => {
153                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
154                                     && qpath_ty.hir_id == ty.hir_id
155                                 {
156                                     lint.build(fluent::lint::tykind_kind)
157                                         .span_suggestion(
158                                             path.span,
159                                             fluent::lint::suggestion,
160                                             "ty",
161                                             Applicability::MaybeIncorrect, // ty maybe needs an import
162                                         )
163                                         .emit();
164                                     return;
165                                 }
166                             }
167                             Some(Node::Expr(Expr {
168                                 kind: ExprKind::Path(qpath),
169                                 ..
170                             })) => {
171                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
172                                     && qpath_ty.hir_id == ty.hir_id
173                                 {
174                                     lint.build(fluent::lint::tykind_kind)
175                                         .span_suggestion(
176                                             path.span,
177                                             fluent::lint::suggestion,
178                                             "ty",
179                                             Applicability::MaybeIncorrect, // ty maybe needs an import
180                                         )
181                                         .emit();
182                                     return;
183                                 }
184                             }
185                             // Can't unify these two branches because qpath below is `&&` and above is `&`
186                             // and `A | B` paths don't play well together with adjustments, apparently.
187                             Some(Node::Expr(Expr {
188                                 kind: ExprKind::Struct(qpath, ..),
189                                 ..
190                             })) => {
191                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
192                                     && qpath_ty.hir_id == ty.hir_id
193                                 {
194                                     lint.build(fluent::lint::tykind_kind)
195                                         .span_suggestion(
196                                             path.span,
197                                             fluent::lint::suggestion,
198                                             "ty",
199                                             Applicability::MaybeIncorrect, // ty maybe needs an import
200                                         )
201                                         .emit();
202                                     return;
203                                 }
204                             }
205                             _ => {}
206                         }
207                         lint.build(fluent::lint::tykind).help(fluent::lint::help).emit();
208                     })
209                 } else if !ty.span.from_expansion() && let Some(t) = is_ty_or_ty_ctxt(cx, &path) {
210                     if path.segments.len() > 1 {
211                         cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
212                             lint.build(fluent::lint::ty_qualified)
213                                 .set_arg("ty", t.clone())
214                                 .span_suggestion(
215                                     path.span,
216                                     fluent::lint::suggestion,
217                                     t,
218                                     // The import probably needs to be changed
219                                     Applicability::MaybeIncorrect,
220                                 )
221                                 .emit();
222                         })
223                     }
224                 }
225             }
226             _ => {}
227         }
228     }
229 }
230
231 fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
232     if let Some(did) = res.opt_def_id() {
233         cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
234     } else {
235         false
236     }
237 }
238
239 fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> {
240     match &path.res {
241         Res::Def(_, def_id) => {
242             if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
243                 return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
244             }
245         }
246         // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
247         Res::SelfTy { trait_: None, alias_to: Some((did, _)) } => {
248             if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
249                 if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
250                 {
251                     // NOTE: This path is currently unreachable as `Ty<'tcx>` is
252                     // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
253                     // is not actually allowed.
254                     //
255                     // I(@lcnr) still kept this branch in so we don't miss this
256                     // if we ever change it in the future.
257                     return Some(format!("{}<{}>", name, substs[0]));
258                 }
259             }
260         }
261         _ => (),
262     }
263
264     None
265 }
266
267 fn gen_args(segment: &PathSegment<'_>) -> String {
268     if let Some(args) = &segment.args {
269         let lifetimes = args
270             .args
271             .iter()
272             .filter_map(|arg| {
273                 if let GenericArg::Lifetime(lt) = arg {
274                     Some(lt.name.ident().to_string())
275                 } else {
276                     None
277                 }
278             })
279             .collect::<Vec<_>>();
280
281         if !lifetimes.is_empty() {
282             return format!("<{}>", lifetimes.join(", "));
283         }
284     }
285
286     String::new()
287 }
288
289 declare_tool_lint! {
290     pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
291     Allow,
292     "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
293 }
294
295 declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
296
297 impl EarlyLintPass for LintPassImpl {
298     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
299         if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind {
300             if let Some(last) = lint_pass.path.segments.last() {
301                 if last.ident.name == sym::LintPass {
302                     let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
303                     let call_site = expn_data.call_site;
304                     if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
305                         && call_site.ctxt().outer_expn_data().kind
306                             != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
307                     {
308                         cx.struct_span_lint(
309                             LINT_PASS_IMPL_WITHOUT_MACRO,
310                             lint_pass.path.span,
311                             |lint| {
312                                 lint.build(fluent::lint::lintpass_by_hand)
313                                     .help(fluent::lint::help)
314                                     .emit();
315                             },
316                         )
317                     }
318                 }
319             }
320         }
321     }
322 }
323
324 declare_tool_lint! {
325     pub rustc::EXISTING_DOC_KEYWORD,
326     Allow,
327     "Check that documented keywords in std and core actually exist",
328     report_in_external_macro: true
329 }
330
331 declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]);
332
333 fn is_doc_keyword(s: Symbol) -> bool {
334     s <= kw::Union
335 }
336
337 impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
338     fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
339         for attr in cx.tcx.hir().attrs(item.hir_id()) {
340             if !attr.has_name(sym::doc) {
341                 continue;
342             }
343             if let Some(list) = attr.meta_item_list() {
344                 for nested in list {
345                     if nested.has_name(sym::keyword) {
346                         let v = nested
347                             .value_str()
348                             .expect("#[doc(keyword = \"...\")] expected a value!");
349                         if is_doc_keyword(v) {
350                             return;
351                         }
352                         cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| {
353                             lint.build(fluent::lint::non_existant_doc_keyword)
354                                 .set_arg("keyword", v)
355                                 .help(fluent::lint::help)
356                                 .emit();
357                         });
358                     }
359                 }
360             }
361         }
362     }
363 }
364
365 declare_tool_lint! {
366     pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
367     Allow,
368     "prevent creation of diagnostics which cannot be translated",
369     report_in_external_macro: true
370 }
371
372 declare_tool_lint! {
373     pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
374     Allow,
375     "prevent creation of diagnostics outside of `IntoDiagnostic`/`AddToDiagnostic` impls",
376     report_in_external_macro: true
377 }
378
379 declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]);
380
381 impl LateLintPass<'_> for Diagnostics {
382     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
383         let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
384         debug!(?span, ?def_id, ?substs);
385         let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs)
386             .ok()
387             .and_then(|inst| inst)
388             .map(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics))
389             .unwrap_or(false);
390         if !has_attr {
391             return;
392         }
393
394         let mut found_parent_with_attr = false;
395         let mut found_impl = false;
396         for (hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
397             if let Some(owner_did) = hir_id.as_owner() {
398                 found_parent_with_attr = found_parent_with_attr
399                     || cx.tcx.has_attr(owner_did.to_def_id(), sym::rustc_lint_diagnostics);
400             }
401
402             debug!(?parent);
403             if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent &&
404                 let Impl { of_trait: Some(of_trait), .. } = impl_ &&
405                 let Some(def_id) = of_trait.trait_def_id() &&
406                 let Some(name) = cx.tcx.get_diagnostic_name(def_id) &&
407                 matches!(name, sym::IntoDiagnostic | sym::AddToDiagnostic | sym::DecorateLint)
408             {
409                 found_impl = true;
410                 break;
411             }
412         }
413         debug!(?found_impl);
414         if !found_parent_with_attr && !found_impl {
415             cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| {
416                 lint.build(fluent::lint::diag_out_of_impl).emit();
417             })
418         }
419
420         let mut found_diagnostic_message = false;
421         for ty in substs.types() {
422             debug!(?ty);
423             if let Some(adt_def) = ty.ty_adt_def() &&
424                 let Some(name) =  cx.tcx.get_diagnostic_name(adt_def.did()) &&
425                 matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage)
426             {
427                 found_diagnostic_message = true;
428                 break;
429             }
430         }
431         debug!(?found_diagnostic_message);
432         if !found_parent_with_attr && !found_diagnostic_message {
433             cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| {
434                 lint.build(fluent::lint::untranslatable_diag).emit();
435             })
436         }
437     }
438 }
439
440 declare_tool_lint! {
441     pub rustc::BAD_OPT_ACCESS,
442     Deny,
443     "prevent using options by field access when there is a wrapper function",
444     report_in_external_macro: true
445 }
446
447 declare_lint_pass!(BadOptAccess => [ BAD_OPT_ACCESS ]);
448
449 impl LateLintPass<'_> for BadOptAccess {
450     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
451         let ExprKind::Field(base, target) = expr.kind else { return };
452         let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return };
453         // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be
454         // avoided.
455         if !cx.tcx.has_attr(adt_def.did(), sym::rustc_lint_opt_ty) {
456             return;
457         }
458
459         for field in adt_def.all_fields() {
460             if field.name == target.name &&
461                 let Some(attr) = cx.tcx.get_attr(field.did, sym::rustc_lint_opt_deny_field_access) &&
462                 let Some(items) = attr.meta_item_list()  &&
463                 let Some(item) = items.first()  &&
464                 let Some(literal) = item.literal()  &&
465                 let ast::LitKind::Str(val, _) = literal.kind
466             {
467                 cx.struct_span_lint(BAD_OPT_ACCESS, expr.span, |lint| {
468                     lint.build(val.as_str()).emit(); }
469                 );
470             }
471         }
472     }
473 }