rustc_lint/
internal.rs

1//! Some lints that are only useful in the compiler or crates that use compiler internals, such as
2//! Clippy.
3
4use rustc_hir::HirId;
5use rustc_hir::def::Res;
6use rustc_hir::def_id::DefId;
7use rustc_middle::ty::{self, GenericArgsRef, Ty as MiddleTy};
8use rustc_session::{declare_lint_pass, declare_tool_lint};
9use rustc_span::hygiene::{ExpnKind, MacroKind};
10use rustc_span::{Span, sym};
11use tracing::debug;
12use {rustc_ast as ast, rustc_hir as hir};
13
14use crate::lints::{
15    BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand,
16    NonGlobImportTypeIrInherent, QueryInstability, QueryUntracked, SpanUseEqCtxtDiag,
17    SymbolInternStringLiteralDiag, TyQualified, TykindDiag, TykindKind, TypeIrDirectUse,
18    TypeIrInherentUsage, TypeIrTraitUsage, UntranslatableDiag,
19};
20use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
21
22declare_tool_lint! {
23    /// The `default_hash_type` lint detects use of [`std::collections::HashMap`] and
24    /// [`std::collections::HashSet`], suggesting the use of `FxHashMap`/`FxHashSet`.
25    ///
26    /// This can help as `FxHasher` can perform better than the default hasher. DOS protection is
27    /// not required as input is assumed to be trusted.
28    pub rustc::DEFAULT_HASH_TYPES,
29    Allow,
30    "forbid HashMap and HashSet and suggest the FxHash* variants",
31    report_in_external_macro: true
32}
33
34declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
35
36impl LateLintPass<'_> for DefaultHashTypes {
37    fn check_path(&mut self, cx: &LateContext<'_>, path: &hir::Path<'_>, hir_id: HirId) {
38        let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return };
39        if matches!(
40            cx.tcx.hir_node(hir_id),
41            hir::Node::Item(hir::Item { kind: hir::ItemKind::Use(..), .. })
42        ) {
43            // Don't lint imports, only actual usages.
44            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
59/// Helper function for lints that check for expressions with calls and use typeck results to
60/// get the `DefId` and `GenericArgsRef` of the function.
61fn typeck_results_of_method_fn<'tcx>(
62    cx: &LateContext<'tcx>,
63    expr: &hir::Expr<'_>,
64) -> Option<(Span, DefId, ty::GenericArgsRef<'tcx>)> {
65    match expr.kind {
66        hir::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    /// The `potential_query_instability` lint detects use of methods which can lead to
80    /// potential query instability, such as iterating over a `HashMap`.
81    ///
82    /// Due to the [incremental compilation](https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation.html) model,
83    /// queries must return deterministic, stable results. `HashMap` iteration order can change
84    /// between compilations, and will introduce instability if query results expose the order.
85    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    /// The `untracked_query_information` lint detects use of methods which leak information not
93    /// tracked by the query system, such as whether a `Steal<T>` value has already been stolen. In
94    /// order not to break incremental compilation, such methods must be used very carefully or not
95    /// at all.
96    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: &hir::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    /// The `usage_of_ty_tykind` lint detects usages of `ty::TyKind::<kind>`,
130    /// where `ty::<kind>` would suffice.
131    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    /// The `usage_of_qualified_ty` lint detects usages of `ty::TyKind`,
139    /// where `Ty` should be used instead.
140    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 hir::Ty<'tcx, hir::AmbigArg>) {
168        match &ty.kind {
169            hir::TyKind::Path(hir::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                        hir::Node::PatExpr(hir::PatExpr {
173                            kind: hir::PatExprKind::Path(qpath),
174                            ..
175                        })
176                        | hir::Node::Pat(hir::Pat {
177                            kind:
178                                hir::PatKind::TupleStruct(qpath, ..) | hir::PatKind::Struct(qpath, ..),
179                            ..
180                        })
181                        | hir::Node::Expr(
182                            hir::Expr { kind: hir::ExprKind::Path(qpath), .. }
183                            | &hir::Expr { kind: hir::ExprKind::Struct(qpath, ..), .. },
184                        ) => {
185                            if let hir::QPath::TypeRelative(qpath_ty, ..) = qpath
186                                && qpath_ty.hir_id == ty.hir_id
187                            {
188                                Some(path.span)
189                            } else {
190                                None
191                            }
192                        }
193                        _ => None,
194                    };
195
196                    match span {
197                        Some(span) => {
198                            cx.emit_span_lint(
199                                USAGE_OF_TY_TYKIND,
200                                path.span,
201                                TykindKind { suggestion: span },
202                            );
203                        }
204                        None => cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag),
205                    }
206                } else if !ty.span.from_expansion()
207                    && path.segments.len() > 1
208                    && let Some(ty) = is_ty_or_ty_ctxt(cx, path)
209                {
210                    cx.emit_span_lint(
211                        USAGE_OF_QUALIFIED_TY,
212                        path.span,
213                        TyQualified { ty, suggestion: path.span },
214                    );
215                }
216            }
217            _ => {}
218        }
219    }
220}
221
222fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
223    if let Some(did) = res.opt_def_id() {
224        cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
225    } else {
226        false
227    }
228}
229
230fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &hir::Path<'_>) -> Option<String> {
231    match &path.res {
232        Res::Def(_, def_id) => {
233            if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
234                return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
235            }
236        }
237        // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
238        Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => {
239            if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind()
240                && let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
241            {
242                return Some(format!("{}<{}>", name, args[0]));
243            }
244        }
245        _ => (),
246    }
247
248    None
249}
250
251fn gen_args(segment: &hir::PathSegment<'_>) -> String {
252    if let Some(args) = &segment.args {
253        let lifetimes = args
254            .args
255            .iter()
256            .filter_map(|arg| {
257                if let hir::GenericArg::Lifetime(lt) = arg {
258                    Some(lt.ident.to_string())
259                } else {
260                    None
261                }
262            })
263            .collect::<Vec<_>>();
264
265        if !lifetimes.is_empty() {
266            return format!("<{}>", lifetimes.join(", "));
267        }
268    }
269
270    String::new()
271}
272
273declare_tool_lint! {
274    /// The `non_glob_import_of_type_ir_inherent_item` lint detects
275    /// non-glob imports of module `rustc_type_ir::inherent`.
276    pub rustc::NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT,
277    Allow,
278    "non-glob import of `rustc_type_ir::inherent`",
279    report_in_external_macro: true
280}
281
282declare_tool_lint! {
283    /// The `usage_of_type_ir_inherent` lint detects usage of `rustc_type_ir::inherent`.
284    ///
285    /// This module should only be used within the trait solver.
286    pub rustc::USAGE_OF_TYPE_IR_INHERENT,
287    Allow,
288    "usage `rustc_type_ir::inherent` outside of trait system",
289    report_in_external_macro: true
290}
291
292declare_tool_lint! {
293    /// The `usage_of_type_ir_traits` lint detects usage of `rustc_type_ir::Interner`,
294    /// or `rustc_infer::InferCtxtLike`.
295    ///
296    /// Methods of this trait should only be used within the type system abstraction layer,
297    /// and in the generic next trait solver implementation. Look for an analogously named
298    /// method on `TyCtxt` or `InferCtxt` (respectively).
299    pub rustc::USAGE_OF_TYPE_IR_TRAITS,
300    Allow,
301    "usage `rustc_type_ir`-specific abstraction traits outside of trait system",
302    report_in_external_macro: true
303}
304declare_tool_lint! {
305    /// The `direct_use_of_rustc_type_ir` lint detects usage of `rustc_type_ir`.
306    ///
307    /// This module should only be used within the trait solver and some desirable
308    /// crates like rustc_middle.
309    pub rustc::DIRECT_USE_OF_RUSTC_TYPE_IR,
310    Allow,
311    "usage `rustc_type_ir` abstraction outside of trait system",
312    report_in_external_macro: true
313}
314
315declare_lint_pass!(TypeIr => [DIRECT_USE_OF_RUSTC_TYPE_IR, NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT, USAGE_OF_TYPE_IR_INHERENT, USAGE_OF_TYPE_IR_TRAITS]);
316
317impl<'tcx> LateLintPass<'tcx> for TypeIr {
318    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
319        let res_def_id = match expr.kind {
320            hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => path.res.opt_def_id(),
321            hir::ExprKind::Path(hir::QPath::TypeRelative(..)) | hir::ExprKind::MethodCall(..) => {
322                cx.typeck_results().type_dependent_def_id(expr.hir_id)
323            }
324            _ => return,
325        };
326        let Some(res_def_id) = res_def_id else {
327            return;
328        };
329        if let Some(assoc_item) = cx.tcx.opt_associated_item(res_def_id)
330            && let Some(trait_def_id) = assoc_item.trait_container(cx.tcx)
331            && (cx.tcx.is_diagnostic_item(sym::type_ir_interner, trait_def_id)
332                | cx.tcx.is_diagnostic_item(sym::type_ir_infer_ctxt_like, trait_def_id))
333        {
334            cx.emit_span_lint(USAGE_OF_TYPE_IR_TRAITS, expr.span, TypeIrTraitUsage);
335        }
336    }
337
338    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
339        let rustc_hir::ItemKind::Use(path, kind) = item.kind else { return };
340
341        let is_mod_inherent = |res: Res| {
342            res.opt_def_id()
343                .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::type_ir_inherent, def_id))
344        };
345
346        // Path segments except for the final.
347        if let Some(seg) = path.segments.iter().find(|seg| is_mod_inherent(seg.res)) {
348            cx.emit_span_lint(USAGE_OF_TYPE_IR_INHERENT, seg.ident.span, TypeIrInherentUsage);
349        }
350        // Final path resolutions, like `use rustc_type_ir::inherent`
351        else if let Some(type_ns) = path.res.type_ns
352            && is_mod_inherent(type_ns)
353        {
354            cx.emit_span_lint(
355                USAGE_OF_TYPE_IR_INHERENT,
356                path.segments.last().unwrap().ident.span,
357                TypeIrInherentUsage,
358            );
359        }
360
361        let (lo, hi, snippet) = match path.segments {
362            [.., penultimate, segment] if is_mod_inherent(penultimate.res) => {
363                (segment.ident.span, item.kind.ident().unwrap().span, "*")
364            }
365            [.., segment]
366                if let Some(type_ns) = path.res.type_ns
367                    && is_mod_inherent(type_ns)
368                    && let rustc_hir::UseKind::Single(ident) = kind =>
369            {
370                let (lo, snippet) =
371                    match cx.tcx.sess.source_map().span_to_snippet(path.span).as_deref() {
372                        Ok("self") => (path.span, "*"),
373                        _ => (segment.ident.span.shrink_to_hi(), "::*"),
374                    };
375                (lo, if segment.ident == ident { lo } else { ident.span }, snippet)
376            }
377            _ => return,
378        };
379        cx.emit_span_lint(
380            NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT,
381            path.span,
382            NonGlobImportTypeIrInherent { suggestion: lo.eq_ctxt(hi).then(|| lo.to(hi)), snippet },
383        );
384    }
385
386    fn check_path(
387        &mut self,
388        cx: &LateContext<'tcx>,
389        path: &rustc_hir::Path<'tcx>,
390        _: rustc_hir::HirId,
391    ) {
392        if let Some(seg) = path.segments.iter().find(|seg| {
393            seg.res
394                .opt_def_id()
395                .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::type_ir, def_id))
396        }) {
397            cx.emit_span_lint(DIRECT_USE_OF_RUSTC_TYPE_IR, seg.ident.span, TypeIrDirectUse);
398        }
399    }
400}
401
402declare_tool_lint! {
403    /// The `lint_pass_impl_without_macro` detects manual implementations of a lint
404    /// pass, without using [`declare_lint_pass`] or [`impl_lint_pass`].
405    pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
406    Allow,
407    "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
408}
409
410declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
411
412impl EarlyLintPass for LintPassImpl {
413    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
414        if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind {
415            if let Some(last) = lint_pass.path.segments.last() {
416                if last.ident.name == sym::LintPass {
417                    let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
418                    let call_site = expn_data.call_site;
419                    if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
420                        && call_site.ctxt().outer_expn_data().kind
421                            != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
422                    {
423                        cx.emit_span_lint(
424                            LINT_PASS_IMPL_WITHOUT_MACRO,
425                            lint_pass.path.span,
426                            LintPassByHand,
427                        );
428                    }
429                }
430            }
431        }
432    }
433}
434
435declare_tool_lint! {
436    /// The `untranslatable_diagnostic` lint detects messages passed to functions with `impl
437    /// Into<{D,Subd}iagMessage` parameters without using translatable Fluent strings.
438    ///
439    /// More details on translatable diagnostics can be found
440    /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html).
441    pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
442    Allow,
443    "prevent creation of diagnostics which cannot be translated",
444    report_in_external_macro: true,
445    @eval_always = true
446}
447
448declare_tool_lint! {
449    /// The `diagnostic_outside_of_impl` lint detects calls to functions annotated with
450    /// `#[rustc_lint_diagnostics]` that are outside an `Diagnostic`, `Subdiagnostic`, or
451    /// `LintDiagnostic` impl (either hand-written or derived).
452    ///
453    /// More details on diagnostics implementations can be found
454    /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html).
455    pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
456    Allow,
457    "prevent diagnostic creation outside of `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls",
458    report_in_external_macro: true,
459    @eval_always = true
460}
461
462declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL]);
463
464impl LateLintPass<'_> for Diagnostics {
465    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
466        let collect_args_tys_and_spans = |args: &[hir::Expr<'_>], reserve_one_extra: bool| {
467            let mut result = Vec::with_capacity(args.len() + usize::from(reserve_one_extra));
468            result.extend(args.iter().map(|arg| (cx.typeck_results().expr_ty(arg), arg.span)));
469            result
470        };
471        // Only check function calls and method calls.
472        let (span, def_id, fn_gen_args, arg_tys_and_spans) = match expr.kind {
473            hir::ExprKind::Call(callee, args) => {
474                match cx.typeck_results().node_type(callee.hir_id).kind() {
475                    &ty::FnDef(def_id, fn_gen_args) => {
476                        (callee.span, def_id, fn_gen_args, collect_args_tys_and_spans(args, false))
477                    }
478                    _ => return, // occurs for fns passed as args
479                }
480            }
481            hir::ExprKind::MethodCall(_segment, _recv, args, _span) => {
482                let Some((span, def_id, fn_gen_args)) = typeck_results_of_method_fn(cx, expr)
483                else {
484                    return;
485                };
486                let mut args = collect_args_tys_and_spans(args, true);
487                args.insert(0, (cx.tcx.types.self_param, _recv.span)); // dummy inserted for `self`
488                (span, def_id, fn_gen_args, args)
489            }
490            _ => return,
491        };
492
493        Self::diagnostic_outside_of_impl(cx, span, expr.hir_id, def_id, fn_gen_args);
494        Self::untranslatable_diagnostic(cx, def_id, &arg_tys_and_spans);
495    }
496}
497
498impl Diagnostics {
499    // Is the type `{D,Subd}iagMessage`?
500    fn is_diag_message<'cx>(cx: &LateContext<'cx>, ty: MiddleTy<'cx>) -> bool {
501        if let Some(adt_def) = ty.ty_adt_def()
502            && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
503            && matches!(name, sym::DiagMessage | sym::SubdiagMessage)
504        {
505            true
506        } else {
507            false
508        }
509    }
510
511    fn untranslatable_diagnostic<'cx>(
512        cx: &LateContext<'cx>,
513        def_id: DefId,
514        arg_tys_and_spans: &[(MiddleTy<'cx>, Span)],
515    ) {
516        let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
517        let predicates = cx.tcx.predicates_of(def_id).instantiate_identity(cx.tcx).predicates;
518        for (i, &param_ty) in fn_sig.inputs().iter().enumerate() {
519            if let ty::Param(sig_param) = param_ty.kind() {
520                // It is a type parameter. Check if it is `impl Into<{D,Subd}iagMessage>`.
521                for pred in predicates.iter() {
522                    if let Some(trait_pred) = pred.as_trait_clause()
523                        && let trait_ref = trait_pred.skip_binder().trait_ref
524                        && trait_ref.self_ty() == param_ty // correct predicate for the param?
525                        && cx.tcx.is_diagnostic_item(sym::Into, trait_ref.def_id)
526                        && let ty1 = trait_ref.args.type_at(1)
527                        && Self::is_diag_message(cx, ty1)
528                    {
529                        // Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg
530                        // with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an
531                        // `UNTRANSLATABLE_DIAGNOSTIC` lint.
532                        let (arg_ty, arg_span) = arg_tys_and_spans[i];
533
534                        // Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`?
535                        let is_translatable = Self::is_diag_message(cx, arg_ty)
536                            || matches!(arg_ty.kind(), ty::Param(arg_param) if arg_param.name == sig_param.name);
537                        if !is_translatable {
538                            cx.emit_span_lint(
539                                UNTRANSLATABLE_DIAGNOSTIC,
540                                arg_span,
541                                UntranslatableDiag,
542                            );
543                        }
544                    }
545                }
546            }
547        }
548    }
549
550    fn diagnostic_outside_of_impl<'cx>(
551        cx: &LateContext<'cx>,
552        span: Span,
553        current_id: HirId,
554        def_id: DefId,
555        fn_gen_args: GenericArgsRef<'cx>,
556    ) {
557        // Is the callee marked with `#[rustc_lint_diagnostics]`?
558        let Some(inst) =
559            ty::Instance::try_resolve(cx.tcx, cx.typing_env(), def_id, fn_gen_args).ok().flatten()
560        else {
561            return;
562        };
563        let has_attr = cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics);
564        if !has_attr {
565            return;
566        };
567
568        for (hir_id, _parent) in cx.tcx.hir_parent_iter(current_id) {
569            if let Some(owner_did) = hir_id.as_owner()
570                && cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics)
571            {
572                // The parent method is marked with `#[rustc_lint_diagnostics]`
573                return;
574            }
575        }
576
577        // Calls to `#[rustc_lint_diagnostics]`-marked functions should only occur:
578        // - inside an impl of `Diagnostic`, `Subdiagnostic`, or `LintDiagnostic`, or
579        // - inside a parent function that is itself marked with `#[rustc_lint_diagnostics]`.
580        //
581        // Otherwise, emit a `DIAGNOSTIC_OUTSIDE_OF_IMPL` lint.
582        let mut is_inside_appropriate_impl = false;
583        for (_hir_id, parent) in cx.tcx.hir_parent_iter(current_id) {
584            debug!(?parent);
585            if let hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(impl_), .. }) = parent
586                && let hir::Impl { of_trait: Some(of_trait), .. } = impl_
587                && let Some(def_id) = of_trait.trait_def_id()
588                && let Some(name) = cx.tcx.get_diagnostic_name(def_id)
589                && matches!(name, sym::Diagnostic | sym::Subdiagnostic | sym::LintDiagnostic)
590            {
591                is_inside_appropriate_impl = true;
592                break;
593            }
594        }
595        debug!(?is_inside_appropriate_impl);
596        if !is_inside_appropriate_impl {
597            cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
598        }
599    }
600}
601
602declare_tool_lint! {
603    /// The `bad_opt_access` lint detects accessing options by field instead of
604    /// the wrapper function.
605    pub rustc::BAD_OPT_ACCESS,
606    Deny,
607    "prevent using options by field access when there is a wrapper function",
608    report_in_external_macro: true
609}
610
611declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]);
612
613impl LateLintPass<'_> for BadOptAccess {
614    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
615        let hir::ExprKind::Field(base, target) = expr.kind else { return };
616        let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return };
617        // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be
618        // avoided.
619        if !cx.tcx.has_attr(adt_def.did(), sym::rustc_lint_opt_ty) {
620            return;
621        }
622
623        for field in adt_def.all_fields() {
624            if field.name == target.name
625                && let Some(attr) =
626                    cx.tcx.get_attr(field.did, sym::rustc_lint_opt_deny_field_access)
627                && let Some(items) = attr.meta_item_list()
628                && let Some(item) = items.first()
629                && let Some(lit) = item.lit()
630                && let ast::LitKind::Str(val, _) = lit.kind
631            {
632                cx.emit_span_lint(
633                    BAD_OPT_ACCESS,
634                    expr.span,
635                    BadOptAccessDiag { msg: val.as_str() },
636                );
637            }
638        }
639    }
640}
641
642declare_tool_lint! {
643    pub rustc::SPAN_USE_EQ_CTXT,
644    Allow,
645    "forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead",
646    report_in_external_macro: true
647}
648
649declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]);
650
651impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt {
652    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
653        if let hir::ExprKind::Binary(
654            hir::BinOp { node: hir::BinOpKind::Eq | hir::BinOpKind::Ne, .. },
655            lhs,
656            rhs,
657        ) = expr.kind
658        {
659            if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) {
660                cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag);
661            }
662        }
663    }
664}
665
666fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
667    match &expr.kind {
668        hir::ExprKind::MethodCall(..) => cx
669            .typeck_results()
670            .type_dependent_def_id(expr.hir_id)
671            .is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)),
672
673        _ => false,
674    }
675}
676
677declare_tool_lint! {
678    /// The `symbol_intern_string_literal` detects `Symbol::intern` being called on a string literal
679    pub rustc::SYMBOL_INTERN_STRING_LITERAL,
680    // rustc_driver crates out of the compiler can't/shouldn't add preinterned symbols;
681    // bootstrap will deny this manually
682    Allow,
683    "Forbid uses of string literals in `Symbol::intern`, suggesting preinterning instead",
684    report_in_external_macro: true
685}
686
687declare_lint_pass!(SymbolInternStringLiteral => [SYMBOL_INTERN_STRING_LITERAL]);
688
689impl<'tcx> LateLintPass<'tcx> for SymbolInternStringLiteral {
690    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
691        if let hir::ExprKind::Call(path, [arg]) = expr.kind
692            && let hir::ExprKind::Path(ref qpath) = path.kind
693            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
694            && cx.tcx.is_diagnostic_item(sym::SymbolIntern, def_id)
695            && let hir::ExprKind::Lit(kind) = arg.kind
696            && let rustc_ast::LitKind::Str(_, _) = kind.node
697        {
698            cx.emit_span_lint(
699                SYMBOL_INTERN_STRING_LITERAL,
700                kind.span,
701                SymbolInternStringLiteralDiag,
702            );
703        }
704    }
705}