]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_lint/src/internal.rs
Merge 1.70 into proxmox/bookworm
[rustc.git] / compiler / rustc_lint / src / internal.rs
CommitLineData
532ac7d7
XL
1//! Some lints that are only useful in the compiler or crates that use compiler internals, such as
2//! Clippy.
3
9c376795 4use crate::lints::{
353b0b11 5 BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonExistentDocKeyword,
9c376795
FG
6 QueryInstability, TyQualified, TykindDiag, TykindKind, UntranslatableDiag,
7};
dfeec247 8use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
136023e0 9use rustc_ast as ast;
1b1a35ee 10use rustc_hir::def::Res;
923072b8
FG
11use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
12use rustc_hir::{HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
1b1a35ee 13use rustc_middle::ty;
136023e0 14use rustc_session::{declare_lint_pass, declare_tool_lint};
dfeec247 15use rustc_span::hygiene::{ExpnKind, MacroKind};
136023e0 16use rustc_span::symbol::{kw, sym, Symbol};
923072b8 17use rustc_span::Span;
532ac7d7 18
416331ca 19declare_tool_lint! {
353b0b11
FG
20 /// The `default_hash_type` lint detects use of [`std::collections::HashMap`]/[`std::collections::HashSet`],
21 /// suggesting the use of `FxHashMap`/`FxHashSet`.
22 ///
23 /// This can help as `FxHasher` can perform better than the default hasher. DOS protection is not
24 /// required as input is assumed to be trusted.
416331ca 25 pub rustc::DEFAULT_HASH_TYPES,
532ac7d7 26 Allow,
dfeec247
XL
27 "forbid HashMap and HashSet and suggest the FxHash* variants",
28 report_in_external_macro: true
532ac7d7
XL
29}
30
136023e0
XL
31declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
32
33impl LateLintPass<'_> for DefaultHashTypes {
34 fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
5e7ed085 35 let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return };
136023e0
XL
36 if matches!(cx.tcx.hir().get(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) {
37 // don't lint imports, only actual usages
38 return;
532ac7d7 39 }
9c376795 40 let preferred = match cx.tcx.get_diagnostic_name(def_id) {
c295e0f8
XL
41 Some(sym::HashMap) => "FxHashMap",
42 Some(sym::HashSet) => "FxHashSet",
43 _ => return,
136023e0 44 };
9c376795 45 cx.emit_spanned_lint(
2b03887a
FG
46 DEFAULT_HASH_TYPES,
47 path.span,
9c376795 48 DefaultHashTypesDiag { preferred, used: cx.tcx.item_name(def_id) },
2b03887a 49 );
532ac7d7
XL
50 }
51}
52
923072b8
FG
53/// Helper function for lints that check for expressions with calls and use typeck results to
54/// get the `DefId` and `SubstsRef` of the function.
55fn typeck_results_of_method_fn<'tcx>(
56 cx: &LateContext<'tcx>,
57 expr: &Expr<'_>,
58) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> {
923072b8 59 match expr.kind {
f2b60f7d 60 ExprKind::MethodCall(segment, ..)
923072b8
FG
61 if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
62 {
63 Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id)))
64 },
65 _ => {
66 match cx.typeck_results().node_type(expr.hir_id).kind() {
67 &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)),
68 _ => None,
69 }
70 }
71 }
72}
73
416331ca 74declare_tool_lint! {
353b0b11
FG
75 /// The `potential_query_instability` lint detects use of methods which can lead to
76 /// potential query instability, such as iterating over a `HashMap`.
77 ///
78 /// Due to the [incremental compilation](https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation.html) model,
79 /// queries must return deterministic, stable results. `HashMap` iteration order can change between compilations,
80 /// and will introduce instability if query results expose the order.
5099ac24 81 pub rustc::POTENTIAL_QUERY_INSTABILITY,
532ac7d7 82 Allow,
5099ac24 83 "require explicit opt-in when using potentially unstable methods or functions",
dfeec247 84 report_in_external_macro: true
532ac7d7
XL
85}
86
5099ac24
FG
87declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]);
88
89impl LateLintPass<'_> for QueryStability {
90 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
923072b8 91 let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
5099ac24
FG
92 if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) {
93 let def_id = instance.def_id();
94 if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
9c376795 95 cx.emit_spanned_lint(
2b03887a
FG
96 POTENTIAL_QUERY_INSTABILITY,
97 span,
9c376795
FG
98 QueryInstability { query: cx.tcx.item_name(def_id) },
99 );
5099ac24
FG
100 }
101 }
102 }
103}
104
416331ca 105declare_tool_lint! {
353b0b11
FG
106 /// The `usage_of_ty_tykind` lint detects usages of `ty::TyKind::<kind>`,
107 /// where `ty::<kind>` would suffice.
5099ac24 108 pub rustc::USAGE_OF_TY_TYKIND,
48663c56 109 Allow,
5099ac24 110 "usage of `ty::TyKind` outside of the `ty::sty` module",
dfeec247 111 report_in_external_macro: true
48663c56
XL
112}
113
416331ca 114declare_tool_lint! {
353b0b11
FG
115 /// The `usage_of_qualified_ty` lint detects usages of `ty::TyKind`,
116 /// where `Ty` should be used instead.
416331ca 117 pub rustc::USAGE_OF_QUALIFIED_TY,
48663c56 118 Allow,
dfeec247
XL
119 "using `ty::{Ty,TyCtxt}` instead of importing it",
120 report_in_external_macro: true
48663c56
XL
121}
122
123declare_lint_pass!(TyTyKind => [
124 USAGE_OF_TY_TYKIND,
48663c56
XL
125 USAGE_OF_QUALIFIED_TY,
126]);
532ac7d7 127
f035d41b 128impl<'tcx> LateLintPass<'tcx> for TyTyKind {
923072b8
FG
129 fn check_path(
130 &mut self,
131 cx: &LateContext<'tcx>,
487cf647 132 path: &rustc_hir::Path<'tcx>,
923072b8
FG
133 _: rustc_hir::HirId,
134 ) {
135 if let Some(segment) = path.segments.iter().nth_back(1)
f2b60f7d 136 && lint_ty_kind_usage(cx, &segment.res)
923072b8
FG
137 {
138 let span = path.span.with_hi(
139 segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()
140 );
9c376795
FG
141 cx.emit_spanned_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind {
142 suggestion: span,
923072b8 143 });
532ac7d7
XL
144 }
145 }
146
f035d41b 147 fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
e74abb32 148 match &ty.kind {
3c0e092e 149 TyKind::Path(QPath::Resolved(_, path)) => {
923072b8 150 if lint_ty_kind_usage(cx, &path.res) {
2b03887a 151 let hir = cx.tcx.hir();
9c376795 152 let span = match hir.find_parent(ty.hir_id) {
2b03887a
FG
153 Some(Node::Pat(Pat {
154 kind:
155 PatKind::Path(qpath)
156 | PatKind::TupleStruct(qpath, ..)
157 | PatKind::Struct(qpath, ..),
158 ..
159 })) => {
160 if let QPath::TypeRelative(qpath_ty, ..) = qpath
161 && qpath_ty.hir_id == ty.hir_id
162 {
163 Some(path.span)
164 } else {
165 None
923072b8 166 }
2b03887a
FG
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 Some(path.span)
176 } else {
177 None
48663c56 178 }
2b03887a
FG
179 }
180 // Can't unify these two branches because qpath below is `&&` and above is `&`
181 // and `A | B` paths don't play well together with adjustments, apparently.
182 Some(Node::Expr(Expr {
183 kind: ExprKind::Struct(qpath, ..),
184 ..
185 })) => {
186 if let QPath::TypeRelative(qpath_ty, ..) = qpath
187 && qpath_ty.hir_id == ty.hir_id
188 {
189 Some(path.span)
190 } else {
191 None
923072b8 192 }
48663c56 193 }
2b03887a
FG
194 _ => None
195 };
196
197 match span {
198 Some(span) => {
9c376795
FG
199 cx.emit_spanned_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind {
200 suggestion: span,
201 });
2b03887a 202 },
9c376795 203 None => cx.emit_spanned_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag),
48663c56 204 }
9c376795
FG
205 } else if !ty.span.from_expansion() && path.segments.len() > 1 && let Some(ty) = is_ty_or_ty_ctxt(cx, &path) {
206 cx.emit_spanned_lint(USAGE_OF_QUALIFIED_TY, path.span, TyQualified {
207 ty,
208 suggestion: path.span,
209 });
48663c56
XL
210 }
211 }
48663c56 212 _ => {}
532ac7d7
XL
213 }
214 }
215}
216
923072b8
FG
217fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
218 if let Some(did) = res.opt_def_id() {
219 cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
220 } else {
221 false
532ac7d7 222 }
532ac7d7 223}
48663c56 224
923072b8
FG
225fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> {
226 match &path.res {
227 Res::Def(_, def_id) => {
228 if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
229 return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
3c0e092e 230 }
923072b8
FG
231 }
232 // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
2b03887a 233 Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => {
9ffffee4 234 if let ty::Adt(adt, substs) = cx.tcx.type_of(did).subst_identity().kind() {
923072b8
FG
235 if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
236 {
237 // NOTE: This path is currently unreachable as `Ty<'tcx>` is
238 // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
239 // is not actually allowed.
240 //
241 // I(@lcnr) still kept this branch in so we don't miss this
242 // if we ever change it in the future.
243 return Some(format!("{}<{}>", name, substs[0]));
1b1a35ee 244 }
48663c56
XL
245 }
246 }
923072b8 247 _ => (),
48663c56
XL
248 }
249
250 None
251}
252
dfeec247 253fn gen_args(segment: &PathSegment<'_>) -> String {
48663c56
XL
254 if let Some(args) = &segment.args {
255 let lifetimes = args
256 .args
257 .iter()
258 .filter_map(|arg| {
487cf647 259 if let GenericArg::Lifetime(lt) = arg { Some(lt.ident.to_string()) } else { None }
48663c56
XL
260 })
261 .collect::<Vec<_>>();
262
263 if !lifetimes.is_empty() {
264 return format!("<{}>", lifetimes.join(", "));
265 }
266 }
267
268 String::new()
269}
416331ca
XL
270
271declare_tool_lint! {
353b0b11
FG
272 /// The `lint_pass_impl_without_macro` detects manual implementations of a lint
273 /// pass, without using [`declare_lint_pass`] or [`impl_lint_pass`].
416331ca
XL
274 pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
275 Allow,
276 "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
277}
278
279declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
280
281impl EarlyLintPass for LintPassImpl {
136023e0 282 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
3c0e092e 283 if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind {
416331ca
XL
284 if let Some(last) = lint_pass.path.segments.last() {
285 if last.ident.name == sym::LintPass {
e1599b0c
XL
286 let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
287 let call_site = expn_data.call_site;
136023e0
XL
288 if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
289 && call_site.ctxt().outer_expn_data().kind
290 != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
291 {
9c376795 292 cx.emit_spanned_lint(
e1599b0c
XL
293 LINT_PASS_IMPL_WITHOUT_MACRO,
294 lint_pass.path.span,
9c376795
FG
295 LintPassByHand,
296 );
416331ca
XL
297 }
298 }
299 }
300 }
301 }
302}
fc512014
XL
303
304declare_tool_lint! {
353b0b11
FG
305 /// The `existing_doc_keyword` lint detects use `#[doc()]` keywords
306 /// that don't exist, e.g. `#[doc(keyword = "..")]`.
fc512014
XL
307 pub rustc::EXISTING_DOC_KEYWORD,
308 Allow,
309 "Check that documented keywords in std and core actually exist",
310 report_in_external_macro: true
311}
312
313declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]);
314
315fn is_doc_keyword(s: Symbol) -> bool {
316 s <= kw::Union
317}
318
319impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
320 fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
6a06907d 321 for attr in cx.tcx.hir().attrs(item.hir_id()) {
fc512014
XL
322 if !attr.has_name(sym::doc) {
323 continue;
324 }
325 if let Some(list) = attr.meta_item_list() {
326 for nested in list {
327 if nested.has_name(sym::keyword) {
9c376795 328 let keyword = nested
fc512014
XL
329 .value_str()
330 .expect("#[doc(keyword = \"...\")] expected a value!");
9c376795 331 if is_doc_keyword(keyword) {
fc512014
XL
332 return;
333 }
9c376795 334 cx.emit_spanned_lint(
2b03887a
FG
335 EXISTING_DOC_KEYWORD,
336 attr.span,
353b0b11 337 NonExistentDocKeyword { keyword },
2b03887a 338 );
fc512014
XL
339 }
340 }
341 }
342 }
343 }
344}
923072b8
FG
345
346declare_tool_lint! {
353b0b11
FG
347 /// The `untranslatable_diagnostic` lint detects diagnostics created
348 /// without using translatable Fluent strings.
349 ///
350 /// More details on translatable diagnostics can be found [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html).
923072b8
FG
351 pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
352 Allow,
353 "prevent creation of diagnostics which cannot be translated",
354 report_in_external_macro: true
355}
356
357declare_tool_lint! {
353b0b11
FG
358 /// The `diagnostic_outside_of_impl` lint detects diagnostics created manually,
359 /// and inside an `IntoDiagnostic`/`AddToDiagnostic` implementation,
360 /// or a `#[derive(Diagnostic)]`/`#[derive(Subdiagnostic)]` expansion.
361 ///
362 /// More details on diagnostics implementations can be found [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html).
923072b8
FG
363 pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
364 Allow,
2b03887a 365 "prevent creation of diagnostics outside of `IntoDiagnostic`/`AddToDiagnostic` impls",
923072b8
FG
366 report_in_external_macro: true
367}
368
369declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]);
370
371impl LateLintPass<'_> for Diagnostics {
372 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
373 let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
374 debug!(?span, ?def_id, ?substs);
064997fb
FG
375 let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs)
376 .ok()
377 .and_then(|inst| inst)
378 .map(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics))
379 .unwrap_or(false);
380 if !has_attr {
923072b8
FG
381 return;
382 }
383
f2b60f7d 384 let mut found_parent_with_attr = false;
923072b8 385 let mut found_impl = false;
f2b60f7d
FG
386 for (hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
387 if let Some(owner_did) = hir_id.as_owner() {
388 found_parent_with_attr = found_parent_with_attr
353b0b11 389 || cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics);
f2b60f7d
FG
390 }
391
923072b8
FG
392 debug!(?parent);
393 if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent &&
394 let Impl { of_trait: Some(of_trait), .. } = impl_ &&
395 let Some(def_id) = of_trait.trait_def_id() &&
396 let Some(name) = cx.tcx.get_diagnostic_name(def_id) &&
2b03887a 397 matches!(name, sym::IntoDiagnostic | sym::AddToDiagnostic | sym::DecorateLint)
923072b8
FG
398 {
399 found_impl = true;
400 break;
401 }
402 }
403 debug!(?found_impl);
f2b60f7d 404 if !found_parent_with_attr && !found_impl {
9c376795 405 cx.emit_spanned_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
923072b8
FG
406 }
407
408 let mut found_diagnostic_message = false;
409 for ty in substs.types() {
410 debug!(?ty);
411 if let Some(adt_def) = ty.ty_adt_def() &&
412 let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) &&
413 matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage)
414 {
415 found_diagnostic_message = true;
416 break;
417 }
418 }
419 debug!(?found_diagnostic_message);
f2b60f7d 420 if !found_parent_with_attr && !found_diagnostic_message {
9c376795 421 cx.emit_spanned_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag);
923072b8
FG
422 }
423 }
424}
064997fb
FG
425
426declare_tool_lint! {
353b0b11
FG
427 /// The `bad_opt_access` lint detects accessing options by field instead of
428 /// the wrapper function.
064997fb
FG
429 pub rustc::BAD_OPT_ACCESS,
430 Deny,
431 "prevent using options by field access when there is a wrapper function",
432 report_in_external_macro: true
433}
434
435declare_lint_pass!(BadOptAccess => [ BAD_OPT_ACCESS ]);
436
437impl LateLintPass<'_> for BadOptAccess {
438 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
439 let ExprKind::Field(base, target) = expr.kind else { return };
440 let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return };
441 // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be
442 // avoided.
443 if !cx.tcx.has_attr(adt_def.did(), sym::rustc_lint_opt_ty) {
444 return;
445 }
446
447 for field in adt_def.all_fields() {
448 if field.name == target.name &&
449 let Some(attr) = cx.tcx.get_attr(field.did, sym::rustc_lint_opt_deny_field_access) &&
450 let Some(items) = attr.meta_item_list() &&
451 let Some(item) = items.first() &&
487cf647
FG
452 let Some(lit) = item.lit() &&
453 let ast::LitKind::Str(val, _) = lit.kind
064997fb 454 {
9c376795
FG
455 cx.emit_spanned_lint(BAD_OPT_ACCESS, expr.span, BadOptAccessDiag {
456 msg: val.as_str(),
457 });
064997fb
FG
458 }
459 }
460 }
461}