1use rustc_ast as ast;
5use rustc_hir::def::Res;
6use rustc_hir::def_id::DefId;
7use rustc_hir::{
8 AmbigArg, BinOp, BinOpKind, Expr, ExprKind, GenericArg, HirId, Impl, Item, ItemKind, Node, Pat,
9 PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Ty, TyKind,
10};
11use rustc_middle::ty::{self, GenericArgsRef, Ty as MiddleTy};
12use rustc_session::{declare_lint_pass, declare_tool_lint};
13use rustc_span::hygiene::{ExpnKind, MacroKind};
14use rustc_span::{Span, sym};
15use tracing::debug;
16
17use crate::lints::{
18 BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand,
19 NonGlobImportTypeIrInherent, QueryInstability, QueryUntracked, SpanUseEqCtxtDiag,
20 SymbolInternStringLiteralDiag, TyQualified, TykindDiag, TykindKind, TypeIrInherentUsage,
21 UntranslatableDiag,
22};
23use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
24
25declare_tool_lint! {
26 pub rustc::DEFAULT_HASH_TYPES,
32 Allow,
33 "forbid HashMap and HashSet and suggest the FxHash* variants",
34 report_in_external_macro: true
35}
36
37declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
38
39impl LateLintPass<'_> for DefaultHashTypes {
40 fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
41 let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return };
42 if matches!(cx.tcx.hir_node(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) {
43 return;
45 }
46 let preferred = match cx.tcx.get_diagnostic_name(def_id) {
47 Some(sym::HashMap) => "FxHashMap",
48 Some(sym::HashSet) => "FxHashSet",
49 _ => return,
50 };
51 cx.emit_span_lint(
52 DEFAULT_HASH_TYPES,
53 path.span,
54 DefaultHashTypesDiag { preferred, used: cx.tcx.item_name(def_id) },
55 );
56 }
57}
58
59fn typeck_results_of_method_fn<'tcx>(
62 cx: &LateContext<'tcx>,
63 expr: &Expr<'_>,
64) -> Option<(Span, DefId, ty::GenericArgsRef<'tcx>)> {
65 match expr.kind {
66 ExprKind::MethodCall(segment, ..)
67 if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
68 {
69 Some((segment.ident.span, def_id, cx.typeck_results().node_args(expr.hir_id)))
70 }
71 _ => match cx.typeck_results().node_type(expr.hir_id).kind() {
72 &ty::FnDef(def_id, args) => Some((expr.span, def_id, args)),
73 _ => None,
74 },
75 }
76}
77
78declare_tool_lint! {
79 pub rustc::POTENTIAL_QUERY_INSTABILITY,
86 Allow,
87 "require explicit opt-in when using potentially unstable methods or functions",
88 report_in_external_macro: true
89}
90
91declare_tool_lint! {
92 pub rustc::UNTRACKED_QUERY_INFORMATION,
97 Allow,
98 "require explicit opt-in when accessing information not tracked by the query system",
99 report_in_external_macro: true
100}
101
102declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY, UNTRACKED_QUERY_INFORMATION]);
103
104impl LateLintPass<'_> for QueryStability {
105 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
106 let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return };
107 if let Ok(Some(instance)) = ty::Instance::try_resolve(cx.tcx, cx.typing_env(), def_id, args)
108 {
109 let def_id = instance.def_id();
110 if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
111 cx.emit_span_lint(
112 POTENTIAL_QUERY_INSTABILITY,
113 span,
114 QueryInstability { query: cx.tcx.item_name(def_id) },
115 );
116 }
117 if cx.tcx.has_attr(def_id, sym::rustc_lint_untracked_query_information) {
118 cx.emit_span_lint(
119 UNTRACKED_QUERY_INFORMATION,
120 span,
121 QueryUntracked { method: cx.tcx.item_name(def_id) },
122 );
123 }
124 }
125 }
126}
127
128declare_tool_lint! {
129 pub rustc::USAGE_OF_TY_TYKIND,
132 Allow,
133 "usage of `ty::TyKind` outside of the `ty::sty` module",
134 report_in_external_macro: true
135}
136
137declare_tool_lint! {
138 pub rustc::USAGE_OF_QUALIFIED_TY,
141 Allow,
142 "using `ty::{Ty,TyCtxt}` instead of importing it",
143 report_in_external_macro: true
144}
145
146declare_lint_pass!(TyTyKind => [
147 USAGE_OF_TY_TYKIND,
148 USAGE_OF_QUALIFIED_TY,
149]);
150
151impl<'tcx> LateLintPass<'tcx> for TyTyKind {
152 fn check_path(
153 &mut self,
154 cx: &LateContext<'tcx>,
155 path: &rustc_hir::Path<'tcx>,
156 _: rustc_hir::HirId,
157 ) {
158 if let Some(segment) = path.segments.iter().nth_back(1)
159 && lint_ty_kind_usage(cx, &segment.res)
160 {
161 let span =
162 path.span.with_hi(segment.args.map_or(segment.ident.span, |a| a.span_ext).hi());
163 cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span });
164 }
165 }
166
167 fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx, AmbigArg>) {
168 match &ty.kind {
169 TyKind::Path(QPath::Resolved(_, path)) => {
170 if lint_ty_kind_usage(cx, &path.res) {
171 let span = match cx.tcx.parent_hir_node(ty.hir_id) {
172 Node::PatExpr(PatExpr { kind: PatExprKind::Path(qpath), .. })
173 | Node::Pat(Pat {
174 kind: PatKind::TupleStruct(qpath, ..) | PatKind::Struct(qpath, ..),
175 ..
176 })
177 | Node::Expr(
178 Expr { kind: ExprKind::Path(qpath), .. }
179 | &Expr { kind: ExprKind::Struct(qpath, ..), .. },
180 ) => {
181 if let QPath::TypeRelative(qpath_ty, ..) = qpath
182 && qpath_ty.hir_id == ty.hir_id
183 {
184 Some(path.span)
185 } else {
186 None
187 }
188 }
189 _ => None,
190 };
191
192 match span {
193 Some(span) => {
194 cx.emit_span_lint(
195 USAGE_OF_TY_TYKIND,
196 path.span,
197 TykindKind { suggestion: span },
198 );
199 }
200 None => cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag),
201 }
202 } else if !ty.span.from_expansion()
203 && path.segments.len() > 1
204 && let Some(ty) = is_ty_or_ty_ctxt(cx, path)
205 {
206 cx.emit_span_lint(
207 USAGE_OF_QUALIFIED_TY,
208 path.span,
209 TyQualified { ty, suggestion: path.span },
210 );
211 }
212 }
213 _ => {}
214 }
215 }
216}
217
218fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
219 if let Some(did) = res.opt_def_id() {
220 cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
221 } else {
222 false
223 }
224}
225
226fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> {
227 match &path.res {
228 Res::Def(_, def_id) => {
229 if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
230 return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
231 }
232 }
233 Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => {
235 if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind()
236 && let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
237 {
238 return Some(format!("{}<{}>", name, args[0]));
239 }
240 }
241 _ => (),
242 }
243
244 None
245}
246
247fn gen_args(segment: &PathSegment<'_>) -> String {
248 if let Some(args) = &segment.args {
249 let lifetimes = args
250 .args
251 .iter()
252 .filter_map(|arg| {
253 if let GenericArg::Lifetime(lt) = arg { Some(lt.ident.to_string()) } else { None }
254 })
255 .collect::<Vec<_>>();
256
257 if !lifetimes.is_empty() {
258 return format!("<{}>", lifetimes.join(", "));
259 }
260 }
261
262 String::new()
263}
264
265declare_tool_lint! {
266 pub rustc::NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT,
269 Allow,
270 "non-glob import of `rustc_type_ir::inherent`",
271 report_in_external_macro: true
272}
273
274declare_tool_lint! {
275 pub rustc::USAGE_OF_TYPE_IR_INHERENT,
279 Allow,
280 "usage `rustc_type_ir::inherent` outside of trait system",
281 report_in_external_macro: true
282}
283
284declare_lint_pass!(TypeIr => [NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT, USAGE_OF_TYPE_IR_INHERENT]);
285
286impl<'tcx> LateLintPass<'tcx> for TypeIr {
287 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
288 let rustc_hir::ItemKind::Use(path, kind) = item.kind else { return };
289
290 let is_mod_inherent = |def_id| cx.tcx.is_diagnostic_item(sym::type_ir_inherent, def_id);
291
292 if let Some(seg) =
294 path.segments.iter().find(|seg| seg.res.opt_def_id().is_some_and(is_mod_inherent))
295 {
296 cx.emit_span_lint(USAGE_OF_TYPE_IR_INHERENT, seg.ident.span, TypeIrInherentUsage);
297 }
298 else if path.res.iter().any(|res| res.opt_def_id().is_some_and(is_mod_inherent)) {
300 cx.emit_span_lint(
301 USAGE_OF_TYPE_IR_INHERENT,
302 path.segments.last().unwrap().ident.span,
303 TypeIrInherentUsage,
304 );
305 }
306
307 let (lo, hi, snippet) = match path.segments {
308 [.., penultimate, segment]
309 if penultimate.res.opt_def_id().is_some_and(is_mod_inherent) =>
310 {
311 (segment.ident.span, item.kind.ident().unwrap().span, "*")
312 }
313 [.., segment]
314 if path.res.iter().flat_map(Res::opt_def_id).any(is_mod_inherent)
315 && let rustc_hir::UseKind::Single(ident) = kind =>
316 {
317 let (lo, snippet) =
318 match cx.tcx.sess.source_map().span_to_snippet(path.span).as_deref() {
319 Ok("self") => (path.span, "*"),
320 _ => (segment.ident.span.shrink_to_hi(), "::*"),
321 };
322 (lo, if segment.ident == ident { lo } else { ident.span }, snippet)
323 }
324 _ => return,
325 };
326 cx.emit_span_lint(
327 NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT,
328 path.span,
329 NonGlobImportTypeIrInherent { suggestion: lo.eq_ctxt(hi).then(|| lo.to(hi)), snippet },
330 );
331 }
332}
333
334declare_tool_lint! {
335 pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
338 Allow,
339 "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
340}
341
342declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
343
344impl EarlyLintPass for LintPassImpl {
345 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
346 if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind {
347 if let Some(last) = lint_pass.path.segments.last() {
348 if last.ident.name == sym::LintPass {
349 let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
350 let call_site = expn_data.call_site;
351 if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
352 && call_site.ctxt().outer_expn_data().kind
353 != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
354 {
355 cx.emit_span_lint(
356 LINT_PASS_IMPL_WITHOUT_MACRO,
357 lint_pass.path.span,
358 LintPassByHand,
359 );
360 }
361 }
362 }
363 }
364 }
365}
366
367declare_tool_lint! {
368 pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
374 Allow,
375 "prevent creation of diagnostics which cannot be translated",
376 report_in_external_macro: true,
377 @eval_always = true
378}
379
380declare_tool_lint! {
381 pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
388 Allow,
389 "prevent diagnostic creation outside of `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls",
390 report_in_external_macro: true,
391 @eval_always = true
392}
393
394declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL]);
395
396impl LateLintPass<'_> for Diagnostics {
397 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
398 let collect_args_tys_and_spans = |args: &[Expr<'_>], reserve_one_extra: bool| {
399 let mut result = Vec::with_capacity(args.len() + usize::from(reserve_one_extra));
400 result.extend(args.iter().map(|arg| (cx.typeck_results().expr_ty(arg), arg.span)));
401 result
402 };
403 let (span, def_id, fn_gen_args, arg_tys_and_spans) = match expr.kind {
405 ExprKind::Call(callee, args) => {
406 match cx.typeck_results().node_type(callee.hir_id).kind() {
407 &ty::FnDef(def_id, fn_gen_args) => {
408 (callee.span, def_id, fn_gen_args, collect_args_tys_and_spans(args, false))
409 }
410 _ => return, }
412 }
413 ExprKind::MethodCall(_segment, _recv, args, _span) => {
414 let Some((span, def_id, fn_gen_args)) = typeck_results_of_method_fn(cx, expr)
415 else {
416 return;
417 };
418 let mut args = collect_args_tys_and_spans(args, true);
419 args.insert(0, (cx.tcx.types.self_param, _recv.span)); (span, def_id, fn_gen_args, args)
421 }
422 _ => return,
423 };
424
425 Self::diagnostic_outside_of_impl(cx, span, expr.hir_id, def_id, fn_gen_args);
426 Self::untranslatable_diagnostic(cx, def_id, &arg_tys_and_spans);
427 }
428}
429
430impl Diagnostics {
431 fn is_diag_message<'cx>(cx: &LateContext<'cx>, ty: MiddleTy<'cx>) -> bool {
433 if let Some(adt_def) = ty.ty_adt_def()
434 && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
435 && matches!(name, sym::DiagMessage | sym::SubdiagMessage)
436 {
437 true
438 } else {
439 false
440 }
441 }
442
443 fn untranslatable_diagnostic<'cx>(
444 cx: &LateContext<'cx>,
445 def_id: DefId,
446 arg_tys_and_spans: &[(MiddleTy<'cx>, Span)],
447 ) {
448 let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
449 let predicates = cx.tcx.predicates_of(def_id).instantiate_identity(cx.tcx).predicates;
450 for (i, ¶m_ty) in fn_sig.inputs().iter().enumerate() {
451 if let ty::Param(sig_param) = param_ty.kind() {
452 for pred in predicates.iter() {
454 if let Some(trait_pred) = pred.as_trait_clause()
455 && let trait_ref = trait_pred.skip_binder().trait_ref
456 && trait_ref.self_ty() == param_ty && cx.tcx.is_diagnostic_item(sym::Into, trait_ref.def_id)
458 && let ty1 = trait_ref.args.type_at(1)
459 && Self::is_diag_message(cx, ty1)
460 {
461 let (arg_ty, arg_span) = arg_tys_and_spans[i];
465
466 let is_translatable = Self::is_diag_message(cx, arg_ty)
468 || matches!(arg_ty.kind(), ty::Param(arg_param) if arg_param.name == sig_param.name);
469 if !is_translatable {
470 cx.emit_span_lint(
471 UNTRANSLATABLE_DIAGNOSTIC,
472 arg_span,
473 UntranslatableDiag,
474 );
475 }
476 }
477 }
478 }
479 }
480 }
481
482 fn diagnostic_outside_of_impl<'cx>(
483 cx: &LateContext<'cx>,
484 span: Span,
485 current_id: HirId,
486 def_id: DefId,
487 fn_gen_args: GenericArgsRef<'cx>,
488 ) {
489 let Some(inst) =
491 ty::Instance::try_resolve(cx.tcx, cx.typing_env(), def_id, fn_gen_args).ok().flatten()
492 else {
493 return;
494 };
495 let has_attr = cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics);
496 if !has_attr {
497 return;
498 };
499
500 for (hir_id, _parent) in cx.tcx.hir_parent_iter(current_id) {
501 if let Some(owner_did) = hir_id.as_owner()
502 && cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics)
503 {
504 return;
506 }
507 }
508
509 let mut is_inside_appropriate_impl = false;
515 for (_hir_id, parent) in cx.tcx.hir_parent_iter(current_id) {
516 debug!(?parent);
517 if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent
518 && let Impl { of_trait: Some(of_trait), .. } = impl_
519 && let Some(def_id) = of_trait.trait_def_id()
520 && let Some(name) = cx.tcx.get_diagnostic_name(def_id)
521 && matches!(name, sym::Diagnostic | sym::Subdiagnostic | sym::LintDiagnostic)
522 {
523 is_inside_appropriate_impl = true;
524 break;
525 }
526 }
527 debug!(?is_inside_appropriate_impl);
528 if !is_inside_appropriate_impl {
529 cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
530 }
531 }
532}
533
534declare_tool_lint! {
535 pub rustc::BAD_OPT_ACCESS,
538 Deny,
539 "prevent using options by field access when there is a wrapper function",
540 report_in_external_macro: true
541}
542
543declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]);
544
545impl LateLintPass<'_> for BadOptAccess {
546 fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
547 let ExprKind::Field(base, target) = expr.kind else { return };
548 let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return };
549 if !cx.tcx.has_attr(adt_def.did(), sym::rustc_lint_opt_ty) {
552 return;
553 }
554
555 for field in adt_def.all_fields() {
556 if field.name == target.name
557 && let Some(attr) =
558 cx.tcx.get_attr(field.did, sym::rustc_lint_opt_deny_field_access)
559 && let Some(items) = attr.meta_item_list()
560 && let Some(item) = items.first()
561 && let Some(lit) = item.lit()
562 && let ast::LitKind::Str(val, _) = lit.kind
563 {
564 cx.emit_span_lint(
565 BAD_OPT_ACCESS,
566 expr.span,
567 BadOptAccessDiag { msg: val.as_str() },
568 );
569 }
570 }
571 }
572}
573
574declare_tool_lint! {
575 pub rustc::SPAN_USE_EQ_CTXT,
576 Allow,
577 "forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead",
578 report_in_external_macro: true
579}
580
581declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]);
582
583impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt {
584 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
585 if let ExprKind::Binary(BinOp { node: BinOpKind::Eq | BinOpKind::Ne, .. }, lhs, rhs) =
586 expr.kind
587 {
588 if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) {
589 cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag);
590 }
591 }
592 }
593}
594
595fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
596 match &expr.kind {
597 ExprKind::MethodCall(..) => cx
598 .typeck_results()
599 .type_dependent_def_id(expr.hir_id)
600 .is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)),
601
602 _ => false,
603 }
604}
605
606declare_tool_lint! {
607 pub rustc::SYMBOL_INTERN_STRING_LITERAL,
609 Allow,
612 "Forbid uses of string literals in `Symbol::intern`, suggesting preinterning instead",
613 report_in_external_macro: true
614}
615
616declare_lint_pass!(SymbolInternStringLiteral => [SYMBOL_INTERN_STRING_LITERAL]);
617
618impl<'tcx> LateLintPass<'tcx> for SymbolInternStringLiteral {
619 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
620 if let ExprKind::Call(path, [arg]) = expr.kind
621 && let ExprKind::Path(ref qpath) = path.kind
622 && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
623 && cx.tcx.is_diagnostic_item(sym::SymbolIntern, def_id)
624 && let ExprKind::Lit(kind) = arg.kind
625 && let rustc_ast::LitKind::Str(_, _) = kind.node
626 {
627 cx.emit_span_lint(
628 SYMBOL_INTERN_STRING_LITERAL,
629 kind.span,
630 SymbolInternStringLiteralDiag,
631 );
632 }
633 }
634}