]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/internal.rs
Point at type parameter in plain path expr
[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         && let Some(res) = &segment.res
122         && lint_ty_kind_usage(cx, res)
123         {
124             let span = path.span.with_hi(
125                 segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()
126             );
127             cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
128                 lint.build(fluent::lint::tykind_kind)
129                     .span_suggestion(
130                         span,
131                         fluent::lint::suggestion,
132                         "ty",
133                         Applicability::MaybeIncorrect, // ty maybe needs an import
134                     )
135                     .emit();
136             });
137         }
138     }
139
140     fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
141         match &ty.kind {
142             TyKind::Path(QPath::Resolved(_, path)) => {
143                 if lint_ty_kind_usage(cx, &path.res) {
144                     cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
145                         let hir = cx.tcx.hir();
146                         match hir.find(hir.get_parent_node(ty.hir_id)) {
147                             Some(Node::Pat(Pat {
148                                 kind:
149                                     PatKind::Path(qpath)
150                                     | PatKind::TupleStruct(qpath, ..)
151                                     | PatKind::Struct(qpath, ..),
152                                 ..
153                             })) => {
154                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
155                                     && qpath_ty.hir_id == ty.hir_id
156                                 {
157                                     lint.build(fluent::lint::tykind_kind)
158                                         .span_suggestion(
159                                             path.span,
160                                             fluent::lint::suggestion,
161                                             "ty",
162                                             Applicability::MaybeIncorrect, // ty maybe needs an import
163                                         )
164                                         .emit();
165                                     return;
166                                 }
167                             }
168                             Some(Node::Expr(Expr {
169                                 kind: ExprKind::Path(qpath),
170                                 ..
171                             })) => {
172                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
173                                     && qpath_ty.hir_id == ty.hir_id
174                                 {
175                                     lint.build(fluent::lint::tykind_kind)
176                                         .span_suggestion(
177                                             path.span,
178                                             fluent::lint::suggestion,
179                                             "ty",
180                                             Applicability::MaybeIncorrect, // ty maybe needs an import
181                                         )
182                                         .emit();
183                                     return;
184                                 }
185                             }
186                             // Can't unify these two branches because qpath below is `&&` and above is `&`
187                             // and `A | B` paths don't play well together with adjustments, apparently.
188                             Some(Node::Expr(Expr {
189                                 kind: ExprKind::Struct(qpath, ..),
190                                 ..
191                             })) => {
192                                 if let QPath::TypeRelative(qpath_ty, ..) = qpath
193                                     && qpath_ty.hir_id == ty.hir_id
194                                 {
195                                     lint.build(fluent::lint::tykind_kind)
196                                         .span_suggestion(
197                                             path.span,
198                                             fluent::lint::suggestion,
199                                             "ty",
200                                             Applicability::MaybeIncorrect, // ty maybe needs an import
201                                         )
202                                         .emit();
203                                     return;
204                                 }
205                             }
206                             _ => {}
207                         }
208                         lint.build(fluent::lint::tykind).help(fluent::lint::help).emit();
209                     })
210                 } else if !ty.span.from_expansion() && let Some(t) = is_ty_or_ty_ctxt(cx, &path) {
211                     if path.segments.len() > 1 {
212                         cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
213                             lint.build(fluent::lint::ty_qualified)
214                                 .set_arg("ty", t.clone())
215                                 .span_suggestion(
216                                     path.span,
217                                     fluent::lint::suggestion,
218                                     t,
219                                     // The import probably needs to be changed
220                                     Applicability::MaybeIncorrect,
221                                 )
222                                 .emit();
223                         })
224                     }
225                 }
226             }
227             _ => {}
228         }
229     }
230 }
231
232 fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
233     if let Some(did) = res.opt_def_id() {
234         cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
235     } else {
236         false
237     }
238 }
239
240 fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> {
241     match &path.res {
242         Res::Def(_, def_id) => {
243             if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
244                 return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
245             }
246         }
247         // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
248         Res::SelfTy { trait_: None, alias_to: Some((did, _)) } => {
249             if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
250                 if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
251                 {
252                     // NOTE: This path is currently unreachable as `Ty<'tcx>` is
253                     // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
254                     // is not actually allowed.
255                     //
256                     // I(@lcnr) still kept this branch in so we don't miss this
257                     // if we ever change it in the future.
258                     return Some(format!("{}<{}>", name, substs[0]));
259                 }
260             }
261         }
262         _ => (),
263     }
264
265     None
266 }
267
268 fn gen_args(segment: &PathSegment<'_>) -> String {
269     if let Some(args) = &segment.args {
270         let lifetimes = args
271             .args
272             .iter()
273             .filter_map(|arg| {
274                 if let GenericArg::Lifetime(lt) = arg {
275                     Some(lt.name.ident().to_string())
276                 } else {
277                     None
278                 }
279             })
280             .collect::<Vec<_>>();
281
282         if !lifetimes.is_empty() {
283             return format!("<{}>", lifetimes.join(", "));
284         }
285     }
286
287     String::new()
288 }
289
290 declare_tool_lint! {
291     pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
292     Allow,
293     "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
294 }
295
296 declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
297
298 impl EarlyLintPass for LintPassImpl {
299     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
300         if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind {
301             if let Some(last) = lint_pass.path.segments.last() {
302                 if last.ident.name == sym::LintPass {
303                     let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
304                     let call_site = expn_data.call_site;
305                     if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
306                         && call_site.ctxt().outer_expn_data().kind
307                             != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
308                     {
309                         cx.struct_span_lint(
310                             LINT_PASS_IMPL_WITHOUT_MACRO,
311                             lint_pass.path.span,
312                             |lint| {
313                                 lint.build(fluent::lint::lintpass_by_hand)
314                                     .help(fluent::lint::help)
315                                     .emit();
316                             },
317                         )
318                     }
319                 }
320             }
321         }
322     }
323 }
324
325 declare_tool_lint! {
326     pub rustc::EXISTING_DOC_KEYWORD,
327     Allow,
328     "Check that documented keywords in std and core actually exist",
329     report_in_external_macro: true
330 }
331
332 declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]);
333
334 fn is_doc_keyword(s: Symbol) -> bool {
335     s <= kw::Union
336 }
337
338 impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
339     fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
340         for attr in cx.tcx.hir().attrs(item.hir_id()) {
341             if !attr.has_name(sym::doc) {
342                 continue;
343             }
344             if let Some(list) = attr.meta_item_list() {
345                 for nested in list {
346                     if nested.has_name(sym::keyword) {
347                         let v = nested
348                             .value_str()
349                             .expect("#[doc(keyword = \"...\")] expected a value!");
350                         if is_doc_keyword(v) {
351                             return;
352                         }
353                         cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| {
354                             lint.build(fluent::lint::non_existant_doc_keyword)
355                                 .set_arg("keyword", v)
356                                 .help(fluent::lint::help)
357                                 .emit();
358                         });
359                     }
360                 }
361             }
362         }
363     }
364 }
365
366 declare_tool_lint! {
367     pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
368     Allow,
369     "prevent creation of diagnostics which cannot be translated",
370     report_in_external_macro: true
371 }
372
373 declare_tool_lint! {
374     pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
375     Allow,
376     "prevent creation of diagnostics outside of `SessionDiagnostic`/`AddSubdiagnostic` impls",
377     report_in_external_macro: true
378 }
379
380 declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]);
381
382 impl LateLintPass<'_> for Diagnostics {
383     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
384         let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
385         debug!(?span, ?def_id, ?substs);
386         let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs)
387             .ok()
388             .and_then(|inst| inst)
389             .map(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics))
390             .unwrap_or(false);
391         if !has_attr {
392             return;
393         }
394
395         let mut found_parent_with_attr = false;
396         let mut found_impl = false;
397         for (hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
398             if let Some(owner_did) = hir_id.as_owner() {
399                 found_parent_with_attr = found_parent_with_attr
400                     || cx.tcx.has_attr(owner_did.to_def_id(), sym::rustc_lint_diagnostics);
401             }
402
403             debug!(?parent);
404             if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent &&
405                 let Impl { of_trait: Some(of_trait), .. } = impl_ &&
406                 let Some(def_id) = of_trait.trait_def_id() &&
407                 let Some(name) = cx.tcx.get_diagnostic_name(def_id) &&
408                 matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic | sym::DecorateLint)
409             {
410                 found_impl = true;
411                 break;
412             }
413         }
414         debug!(?found_impl);
415         if !found_parent_with_attr && !found_impl {
416             cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| {
417                 lint.build(fluent::lint::diag_out_of_impl).emit();
418             })
419         }
420
421         let mut found_diagnostic_message = false;
422         for ty in substs.types() {
423             debug!(?ty);
424             if let Some(adt_def) = ty.ty_adt_def() &&
425                 let Some(name) =  cx.tcx.get_diagnostic_name(adt_def.did()) &&
426                 matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage)
427             {
428                 found_diagnostic_message = true;
429                 break;
430             }
431         }
432         debug!(?found_diagnostic_message);
433         if !found_parent_with_attr && !found_diagnostic_message {
434             cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| {
435                 lint.build(fluent::lint::untranslatable_diag).emit();
436             })
437         }
438     }
439 }
440
441 declare_tool_lint! {
442     pub rustc::BAD_OPT_ACCESS,
443     Deny,
444     "prevent using options by field access when there is a wrapper function",
445     report_in_external_macro: true
446 }
447
448 declare_lint_pass!(BadOptAccess => [ BAD_OPT_ACCESS ]);
449
450 impl LateLintPass<'_> for BadOptAccess {
451     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
452         let ExprKind::Field(base, target) = expr.kind else { return };
453         let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return };
454         // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be
455         // avoided.
456         if !cx.tcx.has_attr(adt_def.did(), sym::rustc_lint_opt_ty) {
457             return;
458         }
459
460         for field in adt_def.all_fields() {
461             if field.name == target.name &&
462                 let Some(attr) = cx.tcx.get_attr(field.did, sym::rustc_lint_opt_deny_field_access) &&
463                 let Some(items) = attr.meta_item_list()  &&
464                 let Some(item) = items.first()  &&
465                 let Some(literal) = item.literal()  &&
466                 let ast::LitKind::Str(val, _) = literal.kind
467             {
468                 cx.struct_span_lint(BAD_OPT_ACCESS, expr.span, |lint| {
469                     lint.build(val.as_str()).emit(); }
470                 );
471             }
472         }
473     }
474 }