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