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     GenericArg, HirId, Item, ItemKind, MutTy, Mutability, Node, Path, PathSegment, QPath, Ty,
10     TyKind,
11 };
12 use rustc_middle::ty;
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 use rustc_span::hygiene::{ExpnKind, MacroKind};
15 use rustc_span::symbol::{kw, sym, Symbol};
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 {
check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId)27     fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
28         let def_id = match path.res {
29             Res::Def(rustc_hir::def::DefKind::Struct, id) => id,
30             _ => return,
31         };
32         if matches!(cx.tcx.hir().get(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) {
33             // don't lint imports, only actual usages
34             return;
35         }
36         let replace = match cx.tcx.get_diagnostic_name(def_id) {
37             Some(sym::HashMap) => "FxHashMap",
38             Some(sym::HashSet) => "FxHashSet",
39             _ => return,
40         };
41         cx.struct_span_lint(DEFAULT_HASH_TYPES, path.span, |lint| {
42             let msg = format!(
43                 "prefer `{}` over `{}`, it has better performance",
44                 replace,
45                 cx.tcx.item_name(def_id)
46             );
47             lint.build(&msg)
48                 .note(&format!("a `use rustc_data_structures::fx::{}` may be necessary", replace))
49                 .emit();
50         });
51     }
52 }
53 
54 declare_tool_lint! {
55     pub rustc::USAGE_OF_TY_TYKIND,
56     Allow,
57     "usage of `ty::TyKind` outside of the `ty::sty` module",
58     report_in_external_macro: true
59 }
60 
61 declare_tool_lint! {
62     pub rustc::TY_PASS_BY_REFERENCE,
63     Allow,
64     "passing `Ty` or `TyCtxt` by reference",
65     report_in_external_macro: true
66 }
67 
68 declare_tool_lint! {
69     pub rustc::USAGE_OF_QUALIFIED_TY,
70     Allow,
71     "using `ty::{Ty,TyCtxt}` instead of importing it",
72     report_in_external_macro: true
73 }
74 
75 declare_lint_pass!(TyTyKind => [
76     USAGE_OF_TY_TYKIND,
77     TY_PASS_BY_REFERENCE,
78     USAGE_OF_QUALIFIED_TY,
79 ]);
80 
81 impl<'tcx> LateLintPass<'tcx> for TyTyKind {
check_path(&mut self, cx: &LateContext<'_>, path: &'tcx Path<'tcx>, _: HirId)82     fn check_path(&mut self, cx: &LateContext<'_>, path: &'tcx Path<'tcx>, _: HirId) {
83         let segments = path.segments.iter().rev().skip(1).rev();
84 
85         if let Some(last) = segments.last() {
86             let span = path.span.with_hi(last.ident.span.hi());
87             if lint_ty_kind_usage(cx, last) {
88                 cx.struct_span_lint(USAGE_OF_TY_TYKIND, span, |lint| {
89                     lint.build("usage of `ty::TyKind::<kind>`")
90                         .span_suggestion(
91                             span,
92                             "try using ty::<kind> directly",
93                             "ty".to_string(),
94                             Applicability::MaybeIncorrect, // ty maybe needs an import
95                         )
96                         .emit();
97                 })
98             }
99         }
100     }
101 
check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>)102     fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
103         match &ty.kind {
104             TyKind::Path(QPath::Resolved(_, path)) => {
105                 if let Some(last) = path.segments.iter().last() {
106                     if lint_ty_kind_usage(cx, last) {
107                         cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
108                             lint.build("usage of `ty::TyKind`")
109                                 .help("try using `Ty` instead")
110                                 .emit();
111                         })
112                     } else {
113                         if ty.span.from_expansion() {
114                             return;
115                         }
116                         if let Some(t) = is_ty_or_ty_ctxt(cx, ty) {
117                             if path.segments.len() > 1 {
118                                 cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
119                                     lint.build(&format!("usage of qualified `ty::{}`", t))
120                                         .span_suggestion(
121                                             path.span,
122                                             "try using it unqualified",
123                                             t,
124                                             // The import probably needs to be changed
125                                             Applicability::MaybeIncorrect,
126                                         )
127                                         .emit();
128                                 })
129                             }
130                         }
131                     }
132                 }
133             }
134             TyKind::Rptr(_, MutTy { ty: inner_ty, mutbl: Mutability::Not }) => {
135                 if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) {
136                     if cx.tcx.impl_trait_ref(impl_did).is_some() {
137                         return;
138                     }
139                 }
140                 if let Some(t) = is_ty_or_ty_ctxt(cx, &inner_ty) {
141                     cx.struct_span_lint(TY_PASS_BY_REFERENCE, ty.span, |lint| {
142                         lint.build(&format!("passing `{}` by reference", t))
143                             .span_suggestion(
144                                 ty.span,
145                                 "try passing by value",
146                                 t,
147                                 // Changing type of function argument
148                                 Applicability::MaybeIncorrect,
149                             )
150                             .emit();
151                     })
152                 }
153             }
154             _ => {}
155         }
156     }
157 }
158 
lint_ty_kind_usage(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> bool159 fn lint_ty_kind_usage(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> bool {
160     if let Some(res) = segment.res {
161         if let Some(did) = res.opt_def_id() {
162             return cx.tcx.is_diagnostic_item(sym::TyKind, did);
163         }
164     }
165 
166     false
167 }
168 
is_ty_or_ty_ctxt(cx: &LateContext<'_>, ty: &Ty<'_>) -> Option<String>169 fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, ty: &Ty<'_>) -> Option<String> {
170     if let TyKind::Path(QPath::Resolved(_, path)) = &ty.kind {
171         match path.res {
172             Res::Def(_, def_id) => {
173                 if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(def_id) {
174                     return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
175                 }
176             }
177             // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
178             Res::SelfTy(None, Some((did, _))) => {
179                 if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
180                     if let Some(name @ (sym::Ty | sym::TyCtxt)) =
181                         cx.tcx.get_diagnostic_name(adt.did)
182                     {
183                         // NOTE: This path is currently unreachable as `Ty<'tcx>` is
184                         // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
185                         // is not actually allowed.
186                         //
187                         // I(@lcnr) still kept this branch in so we don't miss this
188                         // if we ever change it in the future.
189                         return Some(format!("{}<{}>", name, substs[0]));
190                     }
191                 }
192             }
193             _ => (),
194         }
195     }
196 
197     None
198 }
199 
gen_args(segment: &PathSegment<'_>) -> String200 fn gen_args(segment: &PathSegment<'_>) -> String {
201     if let Some(args) = &segment.args {
202         let lifetimes = args
203             .args
204             .iter()
205             .filter_map(|arg| {
206                 if let GenericArg::Lifetime(lt) = arg {
207                     Some(lt.name.ident().to_string())
208                 } else {
209                     None
210                 }
211             })
212             .collect::<Vec<_>>();
213 
214         if !lifetimes.is_empty() {
215             return format!("<{}>", lifetimes.join(", "));
216         }
217     }
218 
219     String::new()
220 }
221 
222 declare_tool_lint! {
223     pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
224     Allow,
225     "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
226 }
227 
228 declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
229 
230 impl EarlyLintPass for LintPassImpl {
check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item)231     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
232         if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind {
233             if let Some(last) = lint_pass.path.segments.last() {
234                 if last.ident.name == sym::LintPass {
235                     let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
236                     let call_site = expn_data.call_site;
237                     if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
238                         && call_site.ctxt().outer_expn_data().kind
239                             != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
240                     {
241                         cx.struct_span_lint(
242                             LINT_PASS_IMPL_WITHOUT_MACRO,
243                             lint_pass.path.span,
244                             |lint| {
245                                 lint.build("implementing `LintPass` by hand")
246                                     .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
247                                     .emit();
248                             },
249                         )
250                     }
251                 }
252             }
253         }
254     }
255 }
256 
257 declare_tool_lint! {
258     pub rustc::EXISTING_DOC_KEYWORD,
259     Allow,
260     "Check that documented keywords in std and core actually exist",
261     report_in_external_macro: true
262 }
263 
264 declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]);
265 
is_doc_keyword(s: Symbol) -> bool266 fn is_doc_keyword(s: Symbol) -> bool {
267     s <= kw::Union
268 }
269 
270 impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>)271     fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
272         for attr in cx.tcx.hir().attrs(item.hir_id()) {
273             if !attr.has_name(sym::doc) {
274                 continue;
275             }
276             if let Some(list) = attr.meta_item_list() {
277                 for nested in list {
278                     if nested.has_name(sym::keyword) {
279                         let v = nested
280                             .value_str()
281                             .expect("#[doc(keyword = \"...\")] expected a value!");
282                         if is_doc_keyword(v) {
283                             return;
284                         }
285                         cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| {
286                             lint.build(&format!(
287                                 "Found non-existing keyword `{}` used in \
288                                      `#[doc(keyword = \"...\")]`",
289                                 v,
290                             ))
291                             .help("only existing keywords are allowed in core/std")
292                             .emit();
293                         });
294                     }
295                 }
296             }
297         }
298     }
299 }
300