| //! Some lints that are only useful in the compiler or crates that use compiler internals, such as | |
| //! Clippy. | |
| use rustc_ast as ast; | |
| use rustc_ast::{Pat, PatKind, Path}; | |
| use rustc_hir as hir; | |
| use rustc_hir::def::Res; | |
| use rustc_hir::def_id::DefId; | |
| use rustc_hir::{Expr, ExprKind, HirId, find_attr}; | |
| use rustc_lint_defs::{declare_lint_pass, declare_tool_lint}; | |
| use rustc_middle::ty::{self, ClausePolarity, GenericArgsRef}; | |
| use rustc_span::hygiene::{ExpnKind, MacroKind}; | |
| use rustc_span::{Span, sym}; | |
| use crate::diagnostics::{ | |
| AttributeKindInFindAttr, BadOptAccessDiag, DefaultHashTypesDiag, | |
| ImplicitSysrootCrateImportDiag, LintPassByHand, NonGlobImportTypeIrInherent, QueryInstability, | |
| QueryUntracked, RustcMustMatchExhaustivelyNotExhaustive, SpanUseEqCtxtDiag, | |
| SymbolInternStringLiteralDiag, TyQualified, TykindDiag, TykindKind, TypeIrDirectUse, | |
| TypeIrInherentUsage, TypeIrTraitUsage, | |
| }; | |
| use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; | |
| declare_tool_lint! { | |
| /// The `default_hash_type` lint detects use of [`std::collections::HashMap`] and | |
| /// [`std::collections::HashSet`], suggesting the use of `FxHashMap`/`FxHashSet`. | |
| /// | |
| /// This can help as `FxHasher` can perform better than the default hasher. DOS protection is | |
| /// not required as input is assumed to be trusted. | |
| pub rustc::DEFAULT_HASH_TYPES, | |
| Allow, | |
| "forbid HashMap and HashSet and suggest the FxHash* variants", | |
| report_in_external_macro: true | |
| } | |
| declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]); | |
| impl LateLintPass<'_> for DefaultHashTypes { | |
| fn check_path(&mut self, cx: &LateContext<'_>, path: &hir::Path<'_>, hir_id: HirId) { | |
| let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return }; | |
| if matches!( | |
| cx.tcx.hir_node(hir_id), | |
| hir::Node::Item(hir::Item { kind: hir::ItemKind::Use(..), .. }) | |
| ) { | |
| // Don't lint imports, only actual usages. | |
| return; | |
| } | |
| let preferred = match cx.tcx.get_diagnostic_name(def_id) { | |
| Some(sym::HashMap) => "FxHashMap", | |
| Some(sym::HashSet) => "FxHashSet", | |
| _ => return, | |
| }; | |
| cx.emit_span_lint( | |
| DEFAULT_HASH_TYPES, | |
| path.span, | |
| DefaultHashTypesDiag { preferred, used: cx.tcx.item_name(def_id) }, | |
| ); | |
| } | |
| } | |
| declare_tool_lint! { | |
| /// The `potential_query_instability` lint detects use of methods which can lead to | |
| /// potential query instability, such as iterating over a `HashMap`. | |
| /// | |
| /// Due to the [incremental compilation](https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation.html) model, | |
| /// queries must return deterministic, stable results. `HashMap` iteration order can change | |
| /// between compilations, and will introduce instability if query results expose the order. | |
| pub rustc::POTENTIAL_QUERY_INSTABILITY, | |
| Allow, | |
| "require explicit opt-in when using potentially unstable methods or functions", | |
| report_in_external_macro: true | |
| } | |
| declare_tool_lint! { | |
| /// The `untracked_query_information` lint detects use of methods which leak information not | |
| /// tracked by the query system, such as whether a `Steal<T>` value has already been stolen. In | |
| /// order not to break incremental compilation, such methods must be used very carefully or not | |
| /// at all. | |
| pub rustc::UNTRACKED_QUERY_INFORMATION, | |
| Allow, | |
| "require explicit opt-in when accessing information not tracked by the query system", | |
| report_in_external_macro: true | |
| } | |
| declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY, UNTRACKED_QUERY_INFORMATION]); | |
| impl<'tcx> LateLintPass<'tcx> for QueryStability { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { | |
| if let Some((callee_def_id, span, generic_args, _recv, _args)) = | |
| get_callee_span_generic_args_and_args(cx, expr) | |
| && let Ok(Some(instance)) = | |
| ty::Instance::try_resolve(cx.tcx, cx.typing_env(), callee_def_id, generic_args) | |
| { | |
| let def_id = instance.def_id(); | |
| if find_attr!(cx.tcx, def_id, RustcLintQueryInstability) { | |
| cx.emit_span_lint( | |
| POTENTIAL_QUERY_INSTABILITY, | |
| span, | |
| QueryInstability { query: cx.tcx.item_name(def_id) }, | |
| ); | |
| } else if has_unstable_into_iter_predicate(cx, callee_def_id, generic_args) { | |
| let call_span = span.with_hi(expr.span.hi()); | |
| cx.emit_span_lint( | |
| POTENTIAL_QUERY_INSTABILITY, | |
| call_span, | |
| QueryInstability { query: sym::into_iter }, | |
| ); | |
| } | |
| if find_attr!(cx.tcx, def_id, RustcLintUntrackedQueryInformation) { | |
| cx.emit_span_lint( | |
| UNTRACKED_QUERY_INFORMATION, | |
| span, | |
| QueryUntracked { method: cx.tcx.item_name(def_id) }, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| fn has_unstable_into_iter_predicate<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| callee_def_id: DefId, | |
| generic_args: GenericArgsRef<'tcx>, | |
| ) -> bool { | |
| let Some(into_iterator_def_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else { | |
| return false; | |
| }; | |
| let Some(into_iter_fn_def_id) = cx.tcx.lang_items().into_iter_fn() else { | |
| return false; | |
| }; | |
| let clauses = cx.tcx.clauses_of(callee_def_id).instantiate(cx.tcx, generic_args); | |
| for (clause, _) in clauses { | |
| let Some(trait_clause) = clause.as_trait_clause() else { | |
| continue; | |
| }; | |
| if trait_clause.def_id() != into_iterator_def_id | |
| || trait_clause.polarity() != ClausePolarity::Positive | |
| { | |
| continue; | |
| } | |
| // `IntoIterator::into_iter` has no additional method args. | |
| let into_iter_fn_args = cx | |
| .tcx | |
| .instantiate_bound_regions_with_erased(trait_clause.skip_norm_wip()) | |
| .trait_ref | |
| .args; | |
| let Ok(Some(instance)) = ty::Instance::try_resolve( | |
| cx.tcx, | |
| cx.typing_env(), | |
| into_iter_fn_def_id, | |
| into_iter_fn_args, | |
| ) else { | |
| continue; | |
| }; | |
| // Does the input type's `IntoIterator` implementation have the | |
| // `rustc_lint_query_instability` attribute on its `into_iter` method? | |
| if find_attr!(cx.tcx, instance.def_id(), RustcLintQueryInstability) { | |
| return true; | |
| } | |
| } | |
| false | |
| } | |
| /// Checks whether an expression is a function or method call and, if so, returns its `DefId`, | |
| /// `Span`, `GenericArgs`, and arguments. This is a slight augmentation of a similarly named Clippy | |
| /// function, `get_callee_generic_args_and_args`. | |
| fn get_callee_span_generic_args_and_args<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| expr: &'tcx Expr<'tcx>, | |
| ) -> Option<(DefId, Span, GenericArgsRef<'tcx>, Option<&'tcx Expr<'tcx>>, &'tcx [Expr<'tcx>])> { | |
| if let ExprKind::Call(callee, args) = expr.kind | |
| && let callee_ty = cx.typeck_results().expr_ty(callee) | |
| && let ty::FnDef(callee_def_id, generic_args) = callee_ty.kind() | |
| { | |
| return Some(( | |
| *callee_def_id, | |
| callee.span, | |
| generic_args.no_bound_vars().unwrap(), | |
| None, | |
| args, | |
| )); | |
| } | |
| if let ExprKind::MethodCall(segment, recv, args, _) = expr.kind | |
| && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) | |
| { | |
| let generic_args = cx.typeck_results().node_args(expr.hir_id); | |
| return Some((method_def_id, segment.ident.span, generic_args, Some(recv), args)); | |
| } | |
| None | |
| } | |
| declare_tool_lint! { | |
| /// The `usage_of_ty_tykind` lint detects usages of `ty::TyKind::<kind>`, | |
| /// where `ty::<kind>` would suffice. | |
| pub rustc::USAGE_OF_TY_TYKIND, | |
| Allow, | |
| "usage of `ty::TyKind` outside of the `ty::sty` module", | |
| report_in_external_macro: true | |
| } | |
| declare_tool_lint! { | |
| /// The `usage_of_qualified_ty` lint detects usages of `ty::TyKind`, | |
| /// where `Ty` should be used instead. | |
| pub rustc::USAGE_OF_QUALIFIED_TY, | |
| Allow, | |
| "using `ty::{Ty,TyCtxt}` instead of importing it", | |
| report_in_external_macro: true | |
| } | |
| declare_lint_pass!(TyTyKind => [ | |
| USAGE_OF_TY_TYKIND, | |
| USAGE_OF_QUALIFIED_TY, | |
| ]); | |
| impl<'tcx> LateLintPass<'tcx> for TyTyKind { | |
| fn check_path( | |
| &mut self, | |
| cx: &LateContext<'tcx>, | |
| path: &rustc_hir::Path<'tcx>, | |
| _: rustc_hir::HirId, | |
| ) { | |
| if let Some(segment) = path.segments.iter().nth_back(1) | |
| && lint_ty_kind_usage(cx, &segment.res) | |
| { | |
| let span = | |
| path.span.with_hi(segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()); | |
| cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span }); | |
| } | |
| } | |
| fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { | |
| match &ty.kind { | |
| hir::TyKind::Path(hir::QPath::Resolved(_, path)) => { | |
| if lint_ty_kind_usage(cx, &path.res) { | |
| let span = match cx.tcx.parent_hir_node(ty.hir_id) { | |
| hir::Node::PatExpr(hir::PatExpr { | |
| kind: hir::PatExprKind::Path(qpath), | |
| .. | |
| }) | |
| | hir::Node::Pat(hir::Pat { | |
| kind: | |
| hir::PatKind::TupleStruct(qpath, ..) | hir::PatKind::Struct(qpath, ..), | |
| .. | |
| }) | |
| | hir::Node::Expr( | |
| hir::Expr { kind: hir::ExprKind::Path(qpath), .. } | |
| | &hir::Expr { kind: hir::ExprKind::Struct(qpath, ..), .. }, | |
| ) => { | |
| if let hir::QPath::TypeRelative(qpath_ty, ..) = qpath | |
| && qpath_ty.hir_id == ty.hir_id | |
| { | |
| Some(path.span) | |
| } else { | |
| None | |
| } | |
| } | |
| _ => None, | |
| }; | |
| match span { | |
| Some(span) => { | |
| cx.emit_span_lint( | |
| USAGE_OF_TY_TYKIND, | |
| path.span, | |
| TykindKind { suggestion: span }, | |
| ); | |
| } | |
| None => cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag), | |
| } | |
| } else if !ty.span.from_expansion() | |
| && path.segments.len() > 1 | |
| && let Some(ty) = is_ty_or_ty_ctxt(cx, path) | |
| { | |
| cx.emit_span_lint( | |
| USAGE_OF_QUALIFIED_TY, | |
| path.span, | |
| TyQualified { ty, suggestion: path.span }, | |
| ); | |
| } | |
| } | |
| _ => {} | |
| } | |
| } | |
| } | |
| fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool { | |
| if let Some(did) = res.opt_def_id() { | |
| cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did) | |
| } else { | |
| false | |
| } | |
| } | |
| fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &hir::Path<'_>) -> Option<String> { | |
| match path.res { | |
| Res::Def(_, def_id) => { | |
| if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(def_id) { | |
| return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap()))); | |
| } | |
| } | |
| // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait. | |
| Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => { | |
| if let ty::Adt(adt, args) = | |
| cx.tcx.type_of(did).instantiate_identity().skip_norm_wip().kind() | |
| && let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did()) | |
| { | |
| return Some(format!("{}<{}>", name, args[0])); | |
| } | |
| } | |
| _ => (), | |
| } | |
| None | |
| } | |
| fn gen_args(segment: &hir::PathSegment<'_>) -> String { | |
| if let Some(args) = &segment.args { | |
| let lifetimes = args | |
| .args | |
| .iter() | |
| .filter_map(|arg| { | |
| if let hir::GenericArg::Lifetime(lt) = arg { | |
| Some(lt.ident.to_string()) | |
| } else { | |
| None | |
| } | |
| }) | |
| .collect::<Vec<_>>(); | |
| if !lifetimes.is_empty() { | |
| return format!("<{}>", lifetimes.join(", ")); | |
| } | |
| } | |
| String::new() | |
| } | |
| declare_tool_lint! { | |
| /// The `non_glob_import_of_type_ir_inherent_item` lint detects | |
| /// non-glob imports of module `rustc_type_ir::inherent`. | |
| pub rustc::NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT, | |
| Allow, | |
| "non-glob import of `rustc_type_ir::inherent`", | |
| report_in_external_macro: true | |
| } | |
| declare_tool_lint! { | |
| /// The `usage_of_type_ir_inherent` lint detects usage of `rustc_type_ir::inherent`. | |
| /// | |
| /// This module should only be used within the trait solver. | |
| pub rustc::USAGE_OF_TYPE_IR_INHERENT, | |
| Allow, | |
| "usage `rustc_type_ir::inherent` outside of trait system", | |
| report_in_external_macro: true | |
| } | |
| declare_tool_lint! { | |
| /// The `usage_of_type_ir_traits` lint detects usage of `rustc_type_ir::Interner`, | |
| /// or `rustc_infer::InferCtxtLike`. | |
| /// | |
| /// Methods of this trait should only be used within the type system abstraction layer, | |
| /// and in the generic next trait solver implementation. Look for an analogously named | |
| /// method on `TyCtxt` or `InferCtxt` (respectively). | |
| pub rustc::USAGE_OF_TYPE_IR_TRAITS, | |
| Allow, | |
| "usage `rustc_type_ir`-specific abstraction traits outside of trait system", | |
| report_in_external_macro: true | |
| } | |
| declare_tool_lint! { | |
| /// The `direct_use_of_rustc_type_ir` lint detects usage of `rustc_type_ir`. | |
| /// | |
| /// This module should only be used within the trait solver and some desirable | |
| /// crates like rustc_middle. | |
| pub rustc::DIRECT_USE_OF_RUSTC_TYPE_IR, | |
| Allow, | |
| "usage `rustc_type_ir` abstraction outside of trait system", | |
| report_in_external_macro: true | |
| } | |
| declare_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 | |
| ]); | |
| impl<'tcx> LateLintPass<'tcx> for TypeIr { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { | |
| let res_def_id = match expr.kind { | |
| hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => path.res.opt_def_id(), | |
| hir::ExprKind::Path(hir::QPath::TypeRelative(..)) | hir::ExprKind::MethodCall(..) => { | |
| cx.typeck_results().type_dependent_def_id(expr.hir_id) | |
| } | |
| _ => return, | |
| }; | |
| let Some(res_def_id) = res_def_id else { | |
| return; | |
| }; | |
| if let Some(assoc_item) = cx.tcx.opt_associated_item(res_def_id) | |
| && let Some(trait_def_id) = assoc_item.trait_container(cx.tcx) | |
| && (cx.tcx.is_diagnostic_item(sym::type_ir_interner, trait_def_id) | |
| | cx.tcx.is_diagnostic_item(sym::type_ir_infer_ctxt_like, trait_def_id)) | |
| { | |
| cx.emit_span_lint(USAGE_OF_TYPE_IR_TRAITS, expr.span, TypeIrTraitUsage); | |
| } | |
| } | |
| fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { | |
| let rustc_hir::ItemKind::Use(path, kind) = item.kind else { return }; | |
| let is_mod_inherent = |res: Res| { | |
| res.opt_def_id() | |
| .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::type_ir_inherent, def_id)) | |
| }; | |
| // Path segments except for the final. | |
| if let Some(seg) = path.segments.iter().find(|seg| is_mod_inherent(seg.res)) { | |
| cx.emit_span_lint(USAGE_OF_TYPE_IR_INHERENT, seg.ident.span, TypeIrInherentUsage); | |
| } | |
| // Final path resolutions, like `use rustc_type_ir::inherent` | |
| else if let Some(type_ns) = path.res.type_ns | |
| && is_mod_inherent(type_ns) | |
| { | |
| cx.emit_span_lint( | |
| USAGE_OF_TYPE_IR_INHERENT, | |
| path.segments.last().unwrap().ident.span, | |
| TypeIrInherentUsage, | |
| ); | |
| } | |
| let (lo, hi, snippet) = match path.segments { | |
| [.., penultimate, segment] if is_mod_inherent(penultimate.res) => { | |
| (segment.ident.span, item.kind.ident().unwrap().span, "*") | |
| } | |
| [.., segment] | |
| if let Some(type_ns) = path.res.type_ns | |
| && is_mod_inherent(type_ns) | |
| && let rustc_hir::UseKind::Single(ident) = kind => | |
| { | |
| let (lo, snippet) = | |
| match cx.tcx.sess.source_map().span_to_snippet(path.span).as_deref() { | |
| Ok("self") => (path.span, "*"), | |
| _ => (segment.ident.span.shrink_to_hi(), "::*"), | |
| }; | |
| (lo, if segment.ident == ident { lo } else { ident.span }, snippet) | |
| } | |
| _ => return, | |
| }; | |
| cx.emit_span_lint( | |
| NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT, | |
| path.span, | |
| NonGlobImportTypeIrInherent { suggestion: lo.eq_ctxt(hi).then(|| lo.to(hi)), snippet }, | |
| ); | |
| } | |
| fn check_path( | |
| &mut self, | |
| cx: &LateContext<'tcx>, | |
| path: &rustc_hir::Path<'tcx>, | |
| _: rustc_hir::HirId, | |
| ) { | |
| if let Some(seg) = path.segments.iter().find(|seg| { | |
| seg.res | |
| .opt_def_id() | |
| .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::type_ir, def_id)) | |
| }) { | |
| cx.emit_span_lint(DIRECT_USE_OF_RUSTC_TYPE_IR, seg.ident.span, TypeIrDirectUse); | |
| } | |
| } | |
| } | |
| declare_tool_lint! { | |
| /// The `lint_pass_impl_without_macro` detects manual implementations of a lint | |
| /// pass, without using [`declare_lint_pass`] or [`impl_lint_pass`]. | |
| pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO, | |
| Allow, | |
| "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros" | |
| } | |
| declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]); | |
| impl EarlyLintPass for LintPassImpl { | |
| fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { | |
| if let ast::ItemKind::Impl(ast::Impl { of_trait: Some(of_trait), .. }) = &item.kind | |
| && let Some(last) = of_trait.trait_ref.path.segments.last() | |
| && last.ident.name == sym::LintPass | |
| { | |
| let expn_data = of_trait.trait_ref.path.span.ctxt().outer_expn_data(); | |
| let call_site = expn_data.call_site; | |
| if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass) | |
| && call_site.ctxt().outer_expn_data().kind | |
| != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass) | |
| { | |
| cx.emit_span_lint( | |
| LINT_PASS_IMPL_WITHOUT_MACRO, | |
| of_trait.trait_ref.path.span, | |
| LintPassByHand, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| declare_tool_lint! { | |
| /// The `bad_opt_access` lint detects accessing options by field instead of | |
| /// the wrapper function. | |
| pub rustc::BAD_OPT_ACCESS, | |
| Deny, | |
| "prevent using options by field access when there is a wrapper function", | |
| report_in_external_macro: true | |
| } | |
| declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]); | |
| impl LateLintPass<'_> for BadOptAccess { | |
| fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { | |
| let hir::ExprKind::Field(base, target) = expr.kind else { return }; | |
| let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return }; | |
| // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be | |
| // avoided. | |
| if !find_attr!(cx.tcx, adt_def.did(), RustcLintOptTy) { | |
| return; | |
| } | |
| for field in adt_def.all_fields() { | |
| if field.name == target.name | |
| && let Some(lint_message) = find_attr!(cx.tcx, field.did, RustcLintOptDenyFieldAccess { lint_message, } => lint_message) | |
| { | |
| cx.emit_span_lint( | |
| BAD_OPT_ACCESS, | |
| expr.span, | |
| BadOptAccessDiag { msg: lint_message.as_str() }, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| declare_tool_lint! { | |
| pub rustc::SPAN_USE_EQ_CTXT, | |
| Allow, | |
| "forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead", | |
| report_in_external_macro: true | |
| } | |
| declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]); | |
| impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { | |
| if let hir::ExprKind::Binary( | |
| hir::BinOp { node: hir::BinOpKind::Eq | hir::BinOpKind::Ne, .. }, | |
| lhs, | |
| rhs, | |
| ) = expr.kind | |
| { | |
| if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) { | |
| cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag); | |
| } | |
| } | |
| } | |
| } | |
| fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { | |
| match &expr.kind { | |
| hir::ExprKind::MethodCall(..) => cx | |
| .typeck_results() | |
| .type_dependent_def_id(expr.hir_id) | |
| .is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)), | |
| _ => false, | |
| } | |
| } | |
| declare_tool_lint! { | |
| /// The `symbol_intern_string_literal` detects `Symbol::intern` being called on a string literal | |
| pub rustc::SYMBOL_INTERN_STRING_LITERAL, | |
| Allow, | |
| "Forbid uses of string literals in `Symbol::intern`, suggesting preinterning instead", | |
| report_in_external_macro: true | |
| } | |
| declare_lint_pass!(SymbolInternStringLiteral => [SYMBOL_INTERN_STRING_LITERAL]); | |
| impl<'tcx> LateLintPass<'tcx> for SymbolInternStringLiteral { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { | |
| if let hir::ExprKind::Call(path, [arg]) = expr.kind | |
| && let hir::ExprKind::Path(ref qpath) = path.kind | |
| && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() | |
| && cx.tcx.is_diagnostic_item(sym::SymbolIntern, def_id) | |
| && let hir::ExprKind::Lit(kind) = arg.kind | |
| && let rustc_ast::LitKind::Str(_, _) = kind.node | |
| { | |
| cx.emit_span_lint( | |
| SYMBOL_INTERN_STRING_LITERAL, | |
| kind.span, | |
| SymbolInternStringLiteralDiag, | |
| ); | |
| } | |
| } | |
| } | |
| declare_tool_lint! { | |
| /// The `implicit_sysroot_crate_import` detects use of `extern crate` to import non-sysroot crates | |
| /// (e.g. crates.io deps) from the sysroot, which is dangerous because these crates are not guaranteed | |
| /// to exist exactly once, and so may be missing entirely or appear multiple times resulting in ambiguity. | |
| pub rustc::IMPLICIT_SYSROOT_CRATE_IMPORT, | |
| Allow, | |
| "Forbid uses of non-sysroot crates in `extern crate`", | |
| report_in_external_macro: true | |
| } | |
| declare_lint_pass!(ImplicitSysrootCrateImport => [IMPLICIT_SYSROOT_CRATE_IMPORT]); | |
| impl EarlyLintPass for ImplicitSysrootCrateImport { | |
| fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { | |
| fn is_whitelisted(crate_name: &str) -> bool { | |
| // Whitelist of allowed crates. | |
| crate_name.starts_with("rustc_") | |
| || matches!( | |
| crate_name, | |
| "test" | "self" | "core" | "alloc" | "std" | "proc_macro" | "tikv_jemalloc_sys" | |
| ) | |
| } | |
| if let ast::ItemKind::ExternCrate(original_name, imported_name) = &item.kind { | |
| let name = original_name.as_ref().unwrap_or(&imported_name.name).as_str(); | |
| let externs = &cx.builder.sess().opts.externs; | |
| if externs.get(name).is_none() && !is_whitelisted(name) { | |
| cx.emit_span_lint( | |
| IMPLICIT_SYSROOT_CRATE_IMPORT, | |
| item.span, | |
| ImplicitSysrootCrateImportDiag { name }, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| declare_tool_lint! { | |
| pub rustc::BAD_USE_OF_FIND_ATTR, | |
| Allow, | |
| "Forbid `AttributeKind::` as a prefix in `find_attr!` macros.", | |
| report_in_external_macro: true | |
| } | |
| declare_lint_pass!(BadUseOfFindAttr => [BAD_USE_OF_FIND_ATTR]); | |
| impl EarlyLintPass for BadUseOfFindAttr { | |
| fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &rustc_ast::Arm) { | |
| fn path_contains_attribute_kind(cx: &EarlyContext<'_>, path: &Path) { | |
| for segment in &path.segments { | |
| if segment.ident.as_str() == "AttributeKind" { | |
| cx.emit_span_lint( | |
| BAD_USE_OF_FIND_ATTR, | |
| segment.span(), | |
| AttributeKindInFindAttr, | |
| ); | |
| } | |
| } | |
| } | |
| fn find_attr_kind_in_pat(cx: &EarlyContext<'_>, pat: &Pat) { | |
| match &pat.kind { | |
| PatKind::Struct(_, path, fields, _) => { | |
| path_contains_attribute_kind(cx, path); | |
| for field in fields { | |
| find_attr_kind_in_pat(cx, &field.pat); | |
| } | |
| } | |
| PatKind::TupleStruct(_, path, fields) => { | |
| path_contains_attribute_kind(cx, path); | |
| for field in fields { | |
| find_attr_kind_in_pat(cx, &field); | |
| } | |
| } | |
| PatKind::Or(options) => { | |
| for pat in options { | |
| find_attr_kind_in_pat(cx, pat); | |
| } | |
| } | |
| PatKind::Path(_, path) => { | |
| path_contains_attribute_kind(cx, path); | |
| } | |
| PatKind::Tuple(elems) => { | |
| for pat in elems { | |
| find_attr_kind_in_pat(cx, pat); | |
| } | |
| } | |
| PatKind::Deref(pat) => { | |
| find_attr_kind_in_pat(cx, pat); | |
| } | |
| PatKind::Ref(..) => { | |
| find_attr_kind_in_pat(cx, pat); | |
| } | |
| PatKind::Slice(elems) => { | |
| for pat in elems { | |
| find_attr_kind_in_pat(cx, pat); | |
| } | |
| } | |
| PatKind::Guard(pat, ..) => { | |
| find_attr_kind_in_pat(cx, pat); | |
| } | |
| PatKind::Paren(pat) => { | |
| find_attr_kind_in_pat(cx, pat); | |
| } | |
| PatKind::Expr(..) | |
| | PatKind::Range(..) | |
| | PatKind::MacCall(..) | |
| | PatKind::Rest | |
| | PatKind::Missing | |
| | PatKind::Err(..) | |
| | PatKind::Ident(..) | |
| | PatKind::Never | |
| | PatKind::Wild => {} | |
| } | |
| } | |
| if let Some(expn_data) = arm.span.source_callee() | |
| && let ExpnKind::Macro(_, name) = expn_data.kind | |
| && name.as_str() == "find_attr" | |
| { | |
| find_attr_kind_in_pat(cx, &arm.pat); | |
| } | |
| } | |
| } | |
| declare_tool_lint! { | |
| pub rustc::RUSTC_MUST_MATCH_EXHAUSTIVELY, | |
| Allow, | |
| "Forbids matches with wildcards, or if-let matching on enums marked with `#[rustc_must_match_exhaustively]`", | |
| report_in_external_macro: true | |
| } | |
| declare_lint_pass!(RustcMustMatchExhaustively => [RUSTC_MUST_MATCH_EXHAUSTIVELY]); | |
| fn is_rustc_must_match_exhaustively(cx: &LateContext<'_>, id: HirId) -> Option<Span> { | |
| let res = cx.typeck_results(); | |
| let ty = res.node_type(id); | |
| let ty = if let ty::Ref(_, ty, _) = ty.kind() { *ty } else { ty }; | |
| if let Some(adt_def) = ty.ty_adt_def() | |
| && adt_def.is_enum() | |
| { | |
| find_attr!(cx.tcx, adt_def.did(), RustcMustMatchExhaustively(span) => *span) | |
| } else { | |
| None | |
| } | |
| } | |
| fn pat_is_not_exhaustive_heuristic(pat: &hir::Pat<'_>) -> Option<(Span, &'static str)> { | |
| match pat.kind { | |
| hir::PatKind::Missing => None, | |
| hir::PatKind::Wild => Some((pat.span, "because of this wildcard pattern")), | |
| hir::PatKind::Binding(_, _, _, Some(pat)) => pat_is_not_exhaustive_heuristic(pat), | |
| hir::PatKind::Binding(..) => Some((pat.span, "because of this variable binding")), | |
| hir::PatKind::Struct(..) => None, | |
| hir::PatKind::TupleStruct(..) => None, | |
| hir::PatKind::Or(..) => None, | |
| hir::PatKind::Never => None, | |
| hir::PatKind::Tuple(..) => None, | |
| hir::PatKind::Deref(pat) => pat_is_not_exhaustive_heuristic(&*pat), | |
| hir::PatKind::Ref(pat, _, _) => pat_is_not_exhaustive_heuristic(&*pat), | |
| hir::PatKind::Expr(..) => None, | |
| hir::PatKind::Guard(..) => None, | |
| hir::PatKind::Range(..) => None, | |
| hir::PatKind::Slice(..) => None, | |
| hir::PatKind::Err(..) => None, | |
| } | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for RustcMustMatchExhaustively { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { | |
| match expr.kind { | |
| // This is not perfect exhaustiveness checking, that's why this is just a rustc internal | |
| // attribute. But it catches most reasonable cases | |
| hir::ExprKind::Match(expr, arms, _) => { | |
| if let Some(attr_span) = is_rustc_must_match_exhaustively(cx, expr.hir_id) { | |
| for arm in arms { | |
| if let Some((span, message)) = pat_is_not_exhaustive_heuristic(arm.pat) { | |
| cx.emit_span_lint( | |
| RUSTC_MUST_MATCH_EXHAUSTIVELY, | |
| expr.span, | |
| RustcMustMatchExhaustivelyNotExhaustive { | |
| attr_span, | |
| pat_span: span, | |
| message, | |
| }, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| hir::ExprKind::Let(expr, ..) => { | |
| if let Some(attr_span) = is_rustc_must_match_exhaustively(cx, expr.init.hir_id) { | |
| cx.emit_span_lint( | |
| RUSTC_MUST_MATCH_EXHAUSTIVELY, | |
| expr.span, | |
| RustcMustMatchExhaustivelyNotExhaustive { | |
| attr_span, | |
| pat_span: expr.span, | |
| message: "using `if let` only matches on one variant (try using `match`)", | |
| }, | |
| ); | |
| } | |
| } | |
| _ => {} | |
| } | |
| } | |
| fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx rustc_hir::Stmt<'tcx>) { | |
| match stmt.kind { | |
| rustc_hir::StmtKind::Let(let_stmt) => { | |
| if let_stmt.els.is_some() | |
| && let Some(attr_span) = | |
| is_rustc_must_match_exhaustively(cx, let_stmt.pat.hir_id) | |
| { | |
| cx.emit_span_lint( | |
| RUSTC_MUST_MATCH_EXHAUSTIVELY, | |
| let_stmt.span, | |
| RustcMustMatchExhaustivelyNotExhaustive { | |
| attr_span, | |
| pat_span: let_stmt.pat.span, | |
| message: "using `let else` only matches on one variant (try using `match`)", | |
| }, | |
| ); | |
| } | |
| } | |
| _ => {} | |
| } | |
| } | |
| } |