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::def::Res;
5use rustc_hir::def_id::DefId;
6use rustc_hir::{Expr, ExprKind, HirId};
7use rustc_middle::ty::{self, ClauseKind, GenericArgsRef, PredicatePolarity, TraitPredicate, Ty};
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
59declare_tool_lint! {
60    /// The `potential_query_instability` lint detects use of methods which can lead to
61    /// potential query instability, such as iterating over a `HashMap`.
62    ///
63    /// Due to the [incremental compilation](https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation.html) model,
64    /// queries must return deterministic, stable results. `HashMap` iteration order can change
65    /// between compilations, and will introduce instability if query results expose the order.
66    pub rustc::POTENTIAL_QUERY_INSTABILITY,
67    Allow,
68    "require explicit opt-in when using potentially unstable methods or functions",
69    report_in_external_macro: true
70}
71
72declare_tool_lint! {
73    /// The `untracked_query_information` lint detects use of methods which leak information not
74    /// tracked by the query system, such as whether a `Steal<T>` value has already been stolen. In
75    /// order not to break incremental compilation, such methods must be used very carefully or not
76    /// at all.
77    pub rustc::UNTRACKED_QUERY_INFORMATION,
78    Allow,
79    "require explicit opt-in when accessing information not tracked by the query system",
80    report_in_external_macro: true
81}
82
83declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY, UNTRACKED_QUERY_INFORMATION]);
84
85impl<'tcx> LateLintPass<'tcx> for QueryStability {
86    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
87        if let Some((callee_def_id, span, generic_args, _recv, _args)) =
88            get_callee_span_generic_args_and_args(cx, expr)
89            && let Ok(Some(instance)) =
90                ty::Instance::try_resolve(cx.tcx, cx.typing_env(), callee_def_id, generic_args)
91        {
92            let def_id = instance.def_id();
93            if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
94                cx.emit_span_lint(
95                    POTENTIAL_QUERY_INSTABILITY,
96                    span,
97                    QueryInstability { query: cx.tcx.item_name(def_id) },
98                );
99            } else if has_unstable_into_iter_predicate(cx, callee_def_id, generic_args) {
100                let call_span = span.with_hi(expr.span.hi());
101                cx.emit_span_lint(
102                    POTENTIAL_QUERY_INSTABILITY,
103                    call_span,
104                    QueryInstability { query: sym::into_iter },
105                );
106            }
107
108            if cx.tcx.has_attr(def_id, sym::rustc_lint_untracked_query_information) {
109                cx.emit_span_lint(
110                    UNTRACKED_QUERY_INFORMATION,
111                    span,
112                    QueryUntracked { method: cx.tcx.item_name(def_id) },
113                );
114            }
115        }
116    }
117}
118
119fn has_unstable_into_iter_predicate<'tcx>(
120    cx: &LateContext<'tcx>,
121    callee_def_id: DefId,
122    generic_args: GenericArgsRef<'tcx>,
123) -> bool {
124    let Some(into_iterator_def_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else {
125        return false;
126    };
127    let Some(into_iter_fn_def_id) = cx.tcx.lang_items().into_iter_fn() else {
128        return false;
129    };
130    let predicates = cx.tcx.predicates_of(callee_def_id).instantiate(cx.tcx, generic_args);
131    for (predicate, _) in predicates {
132        let ClauseKind::Trait(TraitPredicate { trait_ref, polarity: PredicatePolarity::Positive }) =
133            predicate.kind().skip_binder()
134        else {
135            continue;
136        };
137        // Does the function or method require any of its arguments to implement `IntoIterator`?
138        if trait_ref.def_id != into_iterator_def_id {
139            continue;
140        }
141        let Ok(Some(instance)) =
142            ty::Instance::try_resolve(cx.tcx, cx.typing_env(), into_iter_fn_def_id, trait_ref.args)
143        else {
144            continue;
145        };
146        // Does the input type's `IntoIterator` implementation have the
147        // `rustc_lint_query_instability` attribute on its `into_iter` method?
148        if cx.tcx.has_attr(instance.def_id(), sym::rustc_lint_query_instability) {
149            return true;
150        }
151    }
152    false
153}
154
155/// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
156/// `Span`, `GenericArgs`, and arguments. This is a slight augmentation of a similarly named Clippy
157/// function, `get_callee_generic_args_and_args`.
158fn get_callee_span_generic_args_and_args<'tcx>(
159    cx: &LateContext<'tcx>,
160    expr: &'tcx Expr<'tcx>,
161) -> Option<(DefId, Span, GenericArgsRef<'tcx>, Option<&'tcx Expr<'tcx>>, &'tcx [Expr<'tcx>])> {
162    if let ExprKind::Call(callee, args) = expr.kind
163        && let callee_ty = cx.typeck_results().expr_ty(callee)
164        && let ty::FnDef(callee_def_id, generic_args) = callee_ty.kind()
165    {
166        return Some((*callee_def_id, callee.span, generic_args, None, args));
167    }
168    if let ExprKind::MethodCall(segment, recv, args, _) = expr.kind
169        && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
170    {
171        let generic_args = cx.typeck_results().node_args(expr.hir_id);
172        return Some((method_def_id, segment.ident.span, generic_args, Some(recv), args));
173    }
174    None
175}
176
177declare_tool_lint! {
178    /// The `usage_of_ty_tykind` lint detects usages of `ty::TyKind::<kind>`,
179    /// where `ty::<kind>` would suffice.
180    pub rustc::USAGE_OF_TY_TYKIND,
181    Allow,
182    "usage of `ty::TyKind` outside of the `ty::sty` module",
183    report_in_external_macro: true
184}
185
186declare_tool_lint! {
187    /// The `usage_of_qualified_ty` lint detects usages of `ty::TyKind`,
188    /// where `Ty` should be used instead.
189    pub rustc::USAGE_OF_QUALIFIED_TY,
190    Allow,
191    "using `ty::{Ty,TyCtxt}` instead of importing it",
192    report_in_external_macro: true
193}
194
195declare_lint_pass!(TyTyKind => [
196    USAGE_OF_TY_TYKIND,
197    USAGE_OF_QUALIFIED_TY,
198]);
199
200impl<'tcx> LateLintPass<'tcx> for TyTyKind {
201    fn check_path(
202        &mut self,
203        cx: &LateContext<'tcx>,
204        path: &rustc_hir::Path<'tcx>,
205        _: rustc_hir::HirId,
206    ) {
207        if let Some(segment) = path.segments.iter().nth_back(1)
208            && lint_ty_kind_usage(cx, &segment.res)
209        {
210            let span =
211                path.span.with_hi(segment.args.map_or(segment.ident.span, |a| a.span_ext).hi());
212            cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span });
213        }
214    }
215
216    fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
217        match &ty.kind {
218            hir::TyKind::Path(hir::QPath::Resolved(_, path)) => {
219                if lint_ty_kind_usage(cx, &path.res) {
220                    let span = match cx.tcx.parent_hir_node(ty.hir_id) {
221                        hir::Node::PatExpr(hir::PatExpr {
222                            kind: hir::PatExprKind::Path(qpath),
223                            ..
224                        })
225                        | hir::Node::Pat(hir::Pat {
226                            kind:
227                                hir::PatKind::TupleStruct(qpath, ..) | hir::PatKind::Struct(qpath, ..),
228                            ..
229                        })
230                        | hir::Node::Expr(
231                            hir::Expr { kind: hir::ExprKind::Path(qpath), .. }
232                            | &hir::Expr { kind: hir::ExprKind::Struct(qpath, ..), .. },
233                        ) => {
234                            if let hir::QPath::TypeRelative(qpath_ty, ..) = qpath
235                                && qpath_ty.hir_id == ty.hir_id
236                            {
237                                Some(path.span)
238                            } else {
239                                None
240                            }
241                        }
242                        _ => None,
243                    };
244
245                    match span {
246                        Some(span) => {
247                            cx.emit_span_lint(
248                                USAGE_OF_TY_TYKIND,
249                                path.span,
250                                TykindKind { suggestion: span },
251                            );
252                        }
253                        None => cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag),
254                    }
255                } else if !ty.span.from_expansion()
256                    && path.segments.len() > 1
257                    && let Some(ty) = is_ty_or_ty_ctxt(cx, path)
258                {
259                    cx.emit_span_lint(
260                        USAGE_OF_QUALIFIED_TY,
261                        path.span,
262                        TyQualified { ty, suggestion: path.span },
263                    );
264                }
265            }
266            _ => {}
267        }
268    }
269}
270
271fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
272    if let Some(did) = res.opt_def_id() {
273        cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
274    } else {
275        false
276    }
277}
278
279fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &hir::Path<'_>) -> Option<String> {
280    match &path.res {
281        Res::Def(_, def_id) => {
282            if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
283                return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
284            }
285        }
286        // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
287        Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => {
288            if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind()
289                && let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
290            {
291                return Some(format!("{}<{}>", name, args[0]));
292            }
293        }
294        _ => (),
295    }
296
297    None
298}
299
300fn gen_args(segment: &hir::PathSegment<'_>) -> String {
301    if let Some(args) = &segment.args {
302        let lifetimes = args
303            .args
304            .iter()
305            .filter_map(|arg| {
306                if let hir::GenericArg::Lifetime(lt) = arg {
307                    Some(lt.ident.to_string())
308                } else {
309                    None
310                }
311            })
312            .collect::<Vec<_>>();
313
314        if !lifetimes.is_empty() {
315            return format!("<{}>", lifetimes.join(", "));
316        }
317    }
318
319    String::new()
320}
321
322declare_tool_lint! {
323    /// The `non_glob_import_of_type_ir_inherent_item` lint detects
324    /// non-glob imports of module `rustc_type_ir::inherent`.
325    pub rustc::NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT,
326    Allow,
327    "non-glob import of `rustc_type_ir::inherent`",
328    report_in_external_macro: true
329}
330
331declare_tool_lint! {
332    /// The `usage_of_type_ir_inherent` lint detects usage of `rustc_type_ir::inherent`.
333    ///
334    /// This module should only be used within the trait solver.
335    pub rustc::USAGE_OF_TYPE_IR_INHERENT,
336    Allow,
337    "usage `rustc_type_ir::inherent` outside of trait system",
338    report_in_external_macro: true
339}
340
341declare_tool_lint! {
342    /// The `usage_of_type_ir_traits` lint detects usage of `rustc_type_ir::Interner`,
343    /// or `rustc_infer::InferCtxtLike`.
344    ///
345    /// Methods of this trait should only be used within the type system abstraction layer,
346    /// and in the generic next trait solver implementation. Look for an analogously named
347    /// method on `TyCtxt` or `InferCtxt` (respectively).
348    pub rustc::USAGE_OF_TYPE_IR_TRAITS,
349    Allow,
350    "usage `rustc_type_ir`-specific abstraction traits outside of trait system",
351    report_in_external_macro: true
352}
353declare_tool_lint! {
354    /// The `direct_use_of_rustc_type_ir` lint detects usage of `rustc_type_ir`.
355    ///
356    /// This module should only be used within the trait solver and some desirable
357    /// crates like rustc_middle.
358    pub rustc::DIRECT_USE_OF_RUSTC_TYPE_IR,
359    Allow,
360    "usage `rustc_type_ir` abstraction outside of trait system",
361    report_in_external_macro: true
362}
363
364declare_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]);
365
366impl<'tcx> LateLintPass<'tcx> for TypeIr {
367    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
368        let res_def_id = match expr.kind {
369            hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => path.res.opt_def_id(),
370            hir::ExprKind::Path(hir::QPath::TypeRelative(..)) | hir::ExprKind::MethodCall(..) => {
371                cx.typeck_results().type_dependent_def_id(expr.hir_id)
372            }
373            _ => return,
374        };
375        let Some(res_def_id) = res_def_id else {
376            return;
377        };
378        if let Some(assoc_item) = cx.tcx.opt_associated_item(res_def_id)
379            && let Some(trait_def_id) = assoc_item.trait_container(cx.tcx)
380            && (cx.tcx.is_diagnostic_item(sym::type_ir_interner, trait_def_id)
381                | cx.tcx.is_diagnostic_item(sym::type_ir_infer_ctxt_like, trait_def_id))
382        {
383            cx.emit_span_lint(USAGE_OF_TYPE_IR_TRAITS, expr.span, TypeIrTraitUsage);
384        }
385    }
386
387    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
388        let rustc_hir::ItemKind::Use(path, kind) = item.kind else { return };
389
390        let is_mod_inherent = |res: Res| {
391            res.opt_def_id()
392                .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::type_ir_inherent, def_id))
393        };
394
395        // Path segments except for the final.
396        if let Some(seg) = path.segments.iter().find(|seg| is_mod_inherent(seg.res)) {
397            cx.emit_span_lint(USAGE_OF_TYPE_IR_INHERENT, seg.ident.span, TypeIrInherentUsage);
398        }
399        // Final path resolutions, like `use rustc_type_ir::inherent`
400        else if let Some(type_ns) = path.res.type_ns
401            && is_mod_inherent(type_ns)
402        {
403            cx.emit_span_lint(
404                USAGE_OF_TYPE_IR_INHERENT,
405                path.segments.last().unwrap().ident.span,
406                TypeIrInherentUsage,
407            );
408        }
409
410        let (lo, hi, snippet) = match path.segments {
411            [.., penultimate, segment] if is_mod_inherent(penultimate.res) => {
412                (segment.ident.span, item.kind.ident().unwrap().span, "*")
413            }
414            [.., segment]
415                if let Some(type_ns) = path.res.type_ns
416                    && is_mod_inherent(type_ns)
417                    && let rustc_hir::UseKind::Single(ident) = kind =>
418            {
419                let (lo, snippet) =
420                    match cx.tcx.sess.source_map().span_to_snippet(path.span).as_deref() {
421                        Ok("self") => (path.span, "*"),
422                        _ => (segment.ident.span.shrink_to_hi(), "::*"),
423                    };
424                (lo, if segment.ident == ident { lo } else { ident.span }, snippet)
425            }
426            _ => return,
427        };
428        cx.emit_span_lint(
429            NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT,
430            path.span,
431            NonGlobImportTypeIrInherent { suggestion: lo.eq_ctxt(hi).then(|| lo.to(hi)), snippet },
432        );
433    }
434
435    fn check_path(
436        &mut self,
437        cx: &LateContext<'tcx>,
438        path: &rustc_hir::Path<'tcx>,
439        _: rustc_hir::HirId,
440    ) {
441        if let Some(seg) = path.segments.iter().find(|seg| {
442            seg.res
443                .opt_def_id()
444                .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::type_ir, def_id))
445        }) {
446            cx.emit_span_lint(DIRECT_USE_OF_RUSTC_TYPE_IR, seg.ident.span, TypeIrDirectUse);
447        }
448    }
449}
450
451declare_tool_lint! {
452    /// The `lint_pass_impl_without_macro` detects manual implementations of a lint
453    /// pass, without using [`declare_lint_pass`] or [`impl_lint_pass`].
454    pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
455    Allow,
456    "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
457}
458
459declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
460
461impl EarlyLintPass for LintPassImpl {
462    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
463        if let ast::ItemKind::Impl(ast::Impl { of_trait: Some(of_trait), .. }) = &item.kind
464            && let Some(last) = of_trait.trait_ref.path.segments.last()
465            && last.ident.name == sym::LintPass
466        {
467            let expn_data = of_trait.trait_ref.path.span.ctxt().outer_expn_data();
468            let call_site = expn_data.call_site;
469            if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
470                && call_site.ctxt().outer_expn_data().kind
471                    != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
472            {
473                cx.emit_span_lint(
474                    LINT_PASS_IMPL_WITHOUT_MACRO,
475                    of_trait.trait_ref.path.span,
476                    LintPassByHand,
477                );
478            }
479        }
480    }
481}
482
483declare_tool_lint! {
484    /// The `untranslatable_diagnostic` lint detects messages passed to functions with `impl
485    /// Into<{D,Subd}iagMessage` parameters without using translatable Fluent strings.
486    ///
487    /// More details on translatable diagnostics can be found
488    /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html).
489    pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
490    Allow,
491    "prevent creation of diagnostics which cannot be translated",
492    report_in_external_macro: true,
493    @eval_always = true
494}
495
496declare_tool_lint! {
497    /// The `diagnostic_outside_of_impl` lint detects calls to functions annotated with
498    /// `#[rustc_lint_diagnostics]` that are outside an `Diagnostic`, `Subdiagnostic`, or
499    /// `LintDiagnostic` impl (either hand-written or derived).
500    ///
501    /// More details on diagnostics implementations can be found
502    /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html).
503    pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
504    Allow,
505    "prevent diagnostic creation outside of `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls",
506    report_in_external_macro: true,
507    @eval_always = true
508}
509
510declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL]);
511
512impl LateLintPass<'_> for Diagnostics {
513    fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
514        let collect_args_tys_and_spans = |args: &[hir::Expr<'_>], reserve_one_extra: bool| {
515            let mut result = Vec::with_capacity(args.len() + usize::from(reserve_one_extra));
516            result.extend(args.iter().map(|arg| (cx.typeck_results().expr_ty(arg), arg.span)));
517            result
518        };
519        // Only check function calls and method calls.
520        let Some((def_id, span, fn_gen_args, recv, args)) =
521            get_callee_span_generic_args_and_args(cx, expr)
522        else {
523            return;
524        };
525        let mut arg_tys_and_spans = collect_args_tys_and_spans(args, recv.is_some());
526        if let Some(recv) = recv {
527            arg_tys_and_spans.insert(0, (cx.tcx.types.self_param, recv.span)); // dummy inserted for `self`
528        }
529
530        Self::diagnostic_outside_of_impl(cx, span, expr.hir_id, def_id, fn_gen_args);
531        Self::untranslatable_diagnostic(cx, def_id, &arg_tys_and_spans);
532    }
533}
534
535impl Diagnostics {
536    // Is the type `{D,Subd}iagMessage`?
537    fn is_diag_message<'cx>(cx: &LateContext<'cx>, ty: Ty<'cx>) -> bool {
538        if let Some(adt_def) = ty.ty_adt_def()
539            && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
540            && matches!(name, sym::DiagMessage | sym::SubdiagMessage)
541        {
542            true
543        } else {
544            false
545        }
546    }
547
548    fn untranslatable_diagnostic<'cx>(
549        cx: &LateContext<'cx>,
550        def_id: DefId,
551        arg_tys_and_spans: &[(Ty<'cx>, Span)],
552    ) {
553        let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
554        let predicates = cx.tcx.predicates_of(def_id).instantiate_identity(cx.tcx).predicates;
555        for (i, &param_ty) in fn_sig.inputs().iter().enumerate() {
556            if let ty::Param(sig_param) = param_ty.kind() {
557                // It is a type parameter. Check if it is `impl Into<{D,Subd}iagMessage>`.
558                for pred in predicates.iter() {
559                    if let Some(trait_pred) = pred.as_trait_clause()
560                        && let trait_ref = trait_pred.skip_binder().trait_ref
561                        && trait_ref.self_ty() == param_ty // correct predicate for the param?
562                        && cx.tcx.is_diagnostic_item(sym::Into, trait_ref.def_id)
563                        && let ty1 = trait_ref.args.type_at(1)
564                        && Self::is_diag_message(cx, ty1)
565                    {
566                        // Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg
567                        // with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an
568                        // `UNTRANSLATABLE_DIAGNOSTIC` lint.
569                        let (arg_ty, arg_span) = arg_tys_and_spans[i];
570
571                        // Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`?
572                        let is_translatable = Self::is_diag_message(cx, arg_ty)
573                            || matches!(arg_ty.kind(), ty::Param(arg_param) if arg_param.name == sig_param.name);
574                        if !is_translatable {
575                            cx.emit_span_lint(
576                                UNTRANSLATABLE_DIAGNOSTIC,
577                                arg_span,
578                                UntranslatableDiag,
579                            );
580                        }
581                    }
582                }
583            }
584        }
585    }
586
587    fn diagnostic_outside_of_impl<'cx>(
588        cx: &LateContext<'cx>,
589        span: Span,
590        current_id: HirId,
591        def_id: DefId,
592        fn_gen_args: GenericArgsRef<'cx>,
593    ) {
594        // Is the callee marked with `#[rustc_lint_diagnostics]`?
595        let Some(inst) =
596            ty::Instance::try_resolve(cx.tcx, cx.typing_env(), def_id, fn_gen_args).ok().flatten()
597        else {
598            return;
599        };
600        let has_attr = cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics);
601        if !has_attr {
602            return;
603        };
604
605        for (hir_id, _parent) in cx.tcx.hir_parent_iter(current_id) {
606            if let Some(owner_did) = hir_id.as_owner()
607                && cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics)
608            {
609                // The parent method is marked with `#[rustc_lint_diagnostics]`
610                return;
611            }
612        }
613
614        // Calls to `#[rustc_lint_diagnostics]`-marked functions should only occur:
615        // - inside an impl of `Diagnostic`, `Subdiagnostic`, or `LintDiagnostic`, or
616        // - inside a parent function that is itself marked with `#[rustc_lint_diagnostics]`.
617        //
618        // Otherwise, emit a `DIAGNOSTIC_OUTSIDE_OF_IMPL` lint.
619        let mut is_inside_appropriate_impl = false;
620        for (_hir_id, parent) in cx.tcx.hir_parent_iter(current_id) {
621            debug!(?parent);
622            if let hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(impl_), .. }) = parent
623                && let Some(of_trait) = impl_.of_trait
624                && let Some(def_id) = of_trait.trait_ref.trait_def_id()
625                && let Some(name) = cx.tcx.get_diagnostic_name(def_id)
626                && matches!(name, sym::Diagnostic | sym::Subdiagnostic | sym::LintDiagnostic)
627            {
628                is_inside_appropriate_impl = true;
629                break;
630            }
631        }
632        debug!(?is_inside_appropriate_impl);
633        if !is_inside_appropriate_impl {
634            cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
635        }
636    }
637}
638
639declare_tool_lint! {
640    /// The `bad_opt_access` lint detects accessing options by field instead of
641    /// the wrapper function.
642    pub rustc::BAD_OPT_ACCESS,
643    Deny,
644    "prevent using options by field access when there is a wrapper function",
645    report_in_external_macro: true
646}
647
648declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]);
649
650impl LateLintPass<'_> for BadOptAccess {
651    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
652        let hir::ExprKind::Field(base, target) = expr.kind else { return };
653        let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return };
654        // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be
655        // avoided.
656        if !cx.tcx.has_attr(adt_def.did(), sym::rustc_lint_opt_ty) {
657            return;
658        }
659
660        for field in adt_def.all_fields() {
661            if field.name == target.name
662                && let Some(attr) =
663                    cx.tcx.get_attr(field.did, sym::rustc_lint_opt_deny_field_access)
664                && let Some(items) = attr.meta_item_list()
665                && let Some(item) = items.first()
666                && let Some(lit) = item.lit()
667                && let ast::LitKind::Str(val, _) = lit.kind
668            {
669                cx.emit_span_lint(
670                    BAD_OPT_ACCESS,
671                    expr.span,
672                    BadOptAccessDiag { msg: val.as_str() },
673                );
674            }
675        }
676    }
677}
678
679declare_tool_lint! {
680    pub rustc::SPAN_USE_EQ_CTXT,
681    Allow,
682    "forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead",
683    report_in_external_macro: true
684}
685
686declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]);
687
688impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt {
689    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
690        if let hir::ExprKind::Binary(
691            hir::BinOp { node: hir::BinOpKind::Eq | hir::BinOpKind::Ne, .. },
692            lhs,
693            rhs,
694        ) = expr.kind
695        {
696            if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) {
697                cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag);
698            }
699        }
700    }
701}
702
703fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
704    match &expr.kind {
705        hir::ExprKind::MethodCall(..) => cx
706            .typeck_results()
707            .type_dependent_def_id(expr.hir_id)
708            .is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)),
709
710        _ => false,
711    }
712}
713
714declare_tool_lint! {
715    /// The `symbol_intern_string_literal` detects `Symbol::intern` being called on a string literal
716    pub rustc::SYMBOL_INTERN_STRING_LITERAL,
717    // rustc_driver crates out of the compiler can't/shouldn't add preinterned symbols;
718    // bootstrap will deny this manually
719    Allow,
720    "Forbid uses of string literals in `Symbol::intern`, suggesting preinterning instead",
721    report_in_external_macro: true
722}
723
724declare_lint_pass!(SymbolInternStringLiteral => [SYMBOL_INTERN_STRING_LITERAL]);
725
726impl<'tcx> LateLintPass<'tcx> for SymbolInternStringLiteral {
727    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
728        if let hir::ExprKind::Call(path, [arg]) = expr.kind
729            && let hir::ExprKind::Path(ref qpath) = path.kind
730            && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
731            && cx.tcx.is_diagnostic_item(sym::SymbolIntern, def_id)
732            && let hir::ExprKind::Lit(kind) = arg.kind
733            && let rustc_ast::LitKind::Str(_, _) = kind.node
734        {
735            cx.emit_span_lint(
736                SYMBOL_INTERN_STRING_LITERAL,
737                kind.span,
738                SymbolInternStringLiteralDiag,
739            );
740        }
741    }
742}