]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_lint/src/internal.rs
New upstream version 1.48.0~beta.8+dfsg1
[rustc.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::{Item, ItemKind};
6 use rustc_data_structures::fx::FxHashMap;
7 use rustc_errors::Applicability;
8 use rustc_hir::def::Res;
9 use rustc_hir::{GenericArg, HirId, MutTy, Mutability, Path, PathSegment, QPath, Ty, TyKind};
10 use rustc_middle::ty;
11 use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
12 use rustc_span::hygiene::{ExpnKind, MacroKind};
13 use rustc_span::symbol::{sym, Ident, 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 pub struct DefaultHashTypes {
23 map: FxHashMap<Symbol, Symbol>,
24 }
25
26 impl DefaultHashTypes {
27 // we are allowed to use `HashMap` and `HashSet` as identifiers for implementing the lint itself
28 #[allow(rustc::default_hash_types)]
29 pub fn new() -> Self {
30 let mut map = FxHashMap::default();
31 map.insert(sym::HashMap, sym::FxHashMap);
32 map.insert(sym::HashSet, sym::FxHashSet);
33 Self { map }
34 }
35 }
36
37 impl_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
38
39 impl EarlyLintPass for DefaultHashTypes {
40 fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
41 if let Some(replace) = self.map.get(&ident.name) {
42 cx.struct_span_lint(DEFAULT_HASH_TYPES, ident.span, |lint| {
43 // FIXME: We can avoid a copy here. Would require us to take String instead of &str.
44 let msg = format!("Prefer {} over {}, it has better performance", replace, ident);
45 lint.build(&msg)
46 .span_suggestion(
47 ident.span,
48 "use",
49 replace.to_string(),
50 Applicability::MaybeIncorrect, // FxHashMap, ... needs another import
51 )
52 .note(&format!(
53 "a `use rustc_data_structures::fx::{}` may be necessary",
54 replace
55 ))
56 .emit();
57 });
58 }
59 }
60 }
61
62 declare_tool_lint! {
63 pub rustc::USAGE_OF_TY_TYKIND,
64 Allow,
65 "usage of `ty::TyKind` outside of the `ty::sty` module",
66 report_in_external_macro: true
67 }
68
69 declare_tool_lint! {
70 pub rustc::TY_PASS_BY_REFERENCE,
71 Allow,
72 "passing `Ty` or `TyCtxt` by reference",
73 report_in_external_macro: true
74 }
75
76 declare_tool_lint! {
77 pub rustc::USAGE_OF_QUALIFIED_TY,
78 Allow,
79 "using `ty::{Ty,TyCtxt}` instead of importing it",
80 report_in_external_macro: true
81 }
82
83 declare_lint_pass!(TyTyKind => [
84 USAGE_OF_TY_TYKIND,
85 TY_PASS_BY_REFERENCE,
86 USAGE_OF_QUALIFIED_TY,
87 ]);
88
89 impl<'tcx> LateLintPass<'tcx> for TyTyKind {
90 fn check_path(&mut self, cx: &LateContext<'_>, path: &'tcx Path<'tcx>, _: HirId) {
91 let segments = path.segments.iter().rev().skip(1).rev();
92
93 if let Some(last) = segments.last() {
94 let span = path.span.with_hi(last.ident.span.hi());
95 if lint_ty_kind_usage(cx, last) {
96 cx.struct_span_lint(USAGE_OF_TY_TYKIND, span, |lint| {
97 lint.build("usage of `ty::TyKind::<kind>`")
98 .span_suggestion(
99 span,
100 "try using ty::<kind> directly",
101 "ty".to_string(),
102 Applicability::MaybeIncorrect, // ty maybe needs an import
103 )
104 .emit();
105 })
106 }
107 }
108 }
109
110 fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
111 match &ty.kind {
112 TyKind::Path(qpath) => {
113 if let QPath::Resolved(_, path) = qpath {
114 if let Some(last) = path.segments.iter().last() {
115 if lint_ty_kind_usage(cx, last) {
116 cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
117 lint.build("usage of `ty::TyKind`")
118 .help("try using `Ty` instead")
119 .emit();
120 })
121 } else {
122 if ty.span.from_expansion() {
123 return;
124 }
125 if let Some(t) = is_ty_or_ty_ctxt(cx, ty) {
126 if path.segments.len() > 1 {
127 cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
128 lint.build(&format!("usage of qualified `ty::{}`", t))
129 .span_suggestion(
130 path.span,
131 "try using it unqualified",
132 t,
133 // The import probably needs to be changed
134 Applicability::MaybeIncorrect,
135 )
136 .emit();
137 })
138 }
139 }
140 }
141 }
142 }
143 }
144 TyKind::Rptr(_, MutTy { ty: inner_ty, mutbl: Mutability::Not }) => {
145 if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) {
146 if cx.tcx.impl_trait_ref(impl_did).is_some() {
147 return;
148 }
149 }
150 if let Some(t) = is_ty_or_ty_ctxt(cx, &inner_ty) {
151 cx.struct_span_lint(TY_PASS_BY_REFERENCE, ty.span, |lint| {
152 lint.build(&format!("passing `{}` by reference", t))
153 .span_suggestion(
154 ty.span,
155 "try passing by value",
156 t,
157 // Changing type of function argument
158 Applicability::MaybeIncorrect,
159 )
160 .emit();
161 })
162 }
163 }
164 _ => {}
165 }
166 }
167 }
168
169 fn lint_ty_kind_usage(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> bool {
170 if let Some(res) = segment.res {
171 if let Some(did) = res.opt_def_id() {
172 return cx.tcx.is_diagnostic_item(sym::TyKind, did);
173 }
174 }
175
176 false
177 }
178
179 fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, ty: &Ty<'_>) -> Option<String> {
180 if let TyKind::Path(qpath) = &ty.kind {
181 if let QPath::Resolved(_, path) = qpath {
182 match path.res {
183 Res::Def(_, did) => {
184 if cx.tcx.is_diagnostic_item(sym::Ty, did) {
185 return Some(format!("Ty{}", gen_args(path.segments.last().unwrap())));
186 } else if cx.tcx.is_diagnostic_item(sym::TyCtxt, did) {
187 return Some(format!("TyCtxt{}", gen_args(path.segments.last().unwrap())));
188 }
189 }
190 // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
191 Res::SelfTy(None, Some((did, _))) => {
192 if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
193 if cx.tcx.is_diagnostic_item(sym::Ty, adt.did) {
194 // NOTE: This path is currently unreachable as `Ty<'tcx>` is
195 // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
196 // is not actually allowed.
197 //
198 // I(@lcnr) still kept this branch in so we don't miss this
199 // if we ever change it in the future.
200 return Some(format!("Ty<{}>", substs[0]));
201 } else if cx.tcx.is_diagnostic_item(sym::TyCtxt, adt.did) {
202 return Some(format!("TyCtxt<{}>", substs[0]));
203 }
204 }
205 }
206 _ => (),
207 }
208 }
209 }
210
211 None
212 }
213
214 fn gen_args(segment: &PathSegment<'_>) -> String {
215 if let Some(args) = &segment.args {
216 let lifetimes = args
217 .args
218 .iter()
219 .filter_map(|arg| {
220 if let GenericArg::Lifetime(lt) = arg {
221 Some(lt.name.ident().to_string())
222 } else {
223 None
224 }
225 })
226 .collect::<Vec<_>>();
227
228 if !lifetimes.is_empty() {
229 return format!("<{}>", lifetimes.join(", "));
230 }
231 }
232
233 String::new()
234 }
235
236 declare_tool_lint! {
237 pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
238 Allow,
239 "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
240 }
241
242 declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
243
244 impl EarlyLintPass for LintPassImpl {
245 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
246 if let ItemKind::Impl { of_trait: Some(lint_pass), .. } = &item.kind {
247 if let Some(last) = lint_pass.path.segments.last() {
248 if last.ident.name == sym::LintPass {
249 let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
250 let call_site = expn_data.call_site;
251 if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
252 && call_site.ctxt().outer_expn_data().kind
253 != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
254 {
255 cx.struct_span_lint(
256 LINT_PASS_IMPL_WITHOUT_MACRO,
257 lint_pass.path.span,
258 |lint| {
259 lint.build("implementing `LintPass` by hand")
260 .help("try using `declare_lint_pass!` or `impl_lint_pass!` instead")
261 .emit();
262 },
263 )
264 }
265 }
266 }
267 }
268 }
269 }