diff --git a/CHANGELOG.md b/CHANGELOG.md index bfeb009727b..42615179f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3820,6 +3820,7 @@ Released 2018-09-13 [`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq [`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord [`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq +[`disallowed_macros`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros [`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method [`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods [`disallowed_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 24a3588ecf1..34717811866 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -1,14 +1,15 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::{match_def_path, paths}; use rustc_data_structures::fx::FxHashMap; +use rustc_hir::def::{Namespace, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{def::Res, AsyncGeneratorKind, Body, BodyId, GeneratorKind}; +use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::GeneratorInteriorTypeCause; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, Span}; -use crate::utils::conf::DisallowedType; +use crate::utils::conf::DisallowedPath; declare_clippy_lint! { /// ### What it does @@ -171,12 +172,12 @@ impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, #[derive(Debug)] pub struct AwaitHolding { - conf_invalid_types: Vec, - def_ids: FxHashMap, + conf_invalid_types: Vec, + def_ids: FxHashMap, } impl AwaitHolding { - pub(crate) fn new(conf_invalid_types: Vec) -> Self { + pub(crate) fn new(conf_invalid_types: Vec) -> Self { Self { conf_invalid_types, def_ids: FxHashMap::default(), @@ -187,11 +188,8 @@ impl AwaitHolding { impl LateLintPass<'_> for AwaitHolding { fn check_crate(&mut self, cx: &LateContext<'_>) { for conf in &self.conf_invalid_types { - let path = match conf { - DisallowedType::Simple(path) | DisallowedType::WithReason { path, .. } => path, - }; - let segs: Vec<_> = path.split("::").collect(); - if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) { + let segs: Vec<_> = conf.path().split("::").collect(); + if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) { self.def_ids.insert(id, conf.clone()); } } @@ -256,20 +254,18 @@ impl AwaitHolding { } } -fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedType) { - let (type_name, reason) = match disallowed { - DisallowedType::Simple(path) => (path, &None), - DisallowedType::WithReason { path, reason } => (path, reason), - }; - +fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedPath) { span_lint_and_then( cx, AWAIT_HOLDING_INVALID_TYPE, span, - &format!("`{type_name}` may not be held across an `await` point per `clippy.toml`",), + &format!( + "`{}` may not be held across an `await` point per `clippy.toml`", + disallowed.path() + ), |diag| { - if let Some(reason) = reason { - diag.note(reason.clone()); + if let Some(reason) = disallowed.reason() { + diag.note(reason); } }, ); diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs new file mode 100644 index 00000000000..5ab7144e290 --- /dev/null +++ b/clippy_lints/src/disallowed_macros.rs @@ -0,0 +1,151 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::macro_backtrace; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def::{Namespace, Res}; +use rustc_hir::def_id::DefIdMap; +use rustc_hir::{Expr, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{ExpnId, Span}; + +use crate::utils::conf; + +declare_clippy_lint! { + /// ### What it does + /// Denies the configured macros in clippy.toml + /// + /// Note: Even though this lint is warn-by-default, it will only trigger if + /// macros are defined in the clippy.toml file. + /// + /// ### Why is this bad? + /// Some macros are undesirable in certain contexts, and it's beneficial to + /// lint for them as needed. + /// + /// ### Example + /// An example clippy.toml configuration: + /// ```toml + /// # clippy.toml + /// disallowed-macros = [ + /// # Can use a string as the path of the disallowed macro. + /// "std::print", + /// # Can also use an inline table with a `path` key. + /// { path = "std::println" }, + /// # When using an inline table, can add a `reason` for why the macro + /// # is disallowed. + /// { path = "serde::Serialize", reason = "no serializing" }, + /// ] + /// ``` + /// ``` + /// use serde::Serialize; + /// + /// // Example code where clippy issues a warning + /// println!("warns"); + /// + /// // The diagnostic will contain the message "no serializing" + /// #[derive(Serialize)] + /// struct Data { + /// name: String, + /// value: usize, + /// } + /// ``` + #[clippy::version = "1.65.0"] + pub DISALLOWED_MACROS, + style, + "use of a disallowed macro" +} + +pub struct DisallowedMacros { + conf_disallowed: Vec, + disallowed: DefIdMap, + seen: FxHashSet, +} + +impl DisallowedMacros { + pub fn new(conf_disallowed: Vec) -> Self { + Self { + conf_disallowed, + disallowed: DefIdMap::default(), + seen: FxHashSet::default(), + } + } + + fn check(&mut self, cx: &LateContext<'_>, span: Span) { + if self.conf_disallowed.is_empty() { + return; + } + + for mac in macro_backtrace(span) { + if !self.seen.insert(mac.expn) { + return; + } + + if let Some(&index) = self.disallowed.get(&mac.def_id) { + let conf = &self.conf_disallowed[index]; + + span_lint_and_then( + cx, + DISALLOWED_MACROS, + mac.span, + &format!("use of a disallowed macro `{}`", conf.path()), + |diag| { + if let Some(reason) = conf.reason() { + diag.note(&format!("{reason} (from clippy.toml)")); + } + }, + ); + } + } + } +} + +impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); + +impl LateLintPass<'_> for DisallowedMacros { + fn check_crate(&mut self, cx: &LateContext<'_>) { + for (index, conf) in self.conf_disallowed.iter().enumerate() { + let segs: Vec<_> = conf.path().split("::").collect(); + if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::MacroNS)) { + self.disallowed.insert(id, index); + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + self.check(cx, expr.span); + } + + fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) { + self.check(cx, stmt.span); + } + + fn check_ty(&mut self, cx: &LateContext<'_>, ty: &Ty<'_>) { + self.check(cx, ty.span); + } + + fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) { + self.check(cx, pat.span); + } + + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + self.check(cx, item.span); + self.check(cx, item.vis_span); + } + + fn check_foreign_item(&mut self, cx: &LateContext<'_>, item: &ForeignItem<'_>) { + self.check(cx, item.span); + self.check(cx, item.vis_span); + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) { + self.check(cx, item.span); + self.check(cx, item.vis_span); + } + + fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { + self.check(cx, item.span); + } + + fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) { + self.check(cx, path.span); + } +} diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index b02f87c07db..1a381f92c03 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -1,7 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::{fn_def_id, get_parent_expr, path_def_id}; -use rustc_hir::{def::Res, def_id::DefIdMap, Expr, ExprKind}; +use rustc_hir::def::{Namespace, Res}; +use rustc_hir::def_id::DefIdMap; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -58,12 +60,12 @@ declare_clippy_lint! { #[derive(Clone, Debug)] pub struct DisallowedMethods { - conf_disallowed: Vec, + conf_disallowed: Vec, disallowed: DefIdMap, } impl DisallowedMethods { - pub fn new(conf_disallowed: Vec) -> Self { + pub fn new(conf_disallowed: Vec) -> Self { Self { conf_disallowed, disallowed: DefIdMap::default(), @@ -77,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { fn check_crate(&mut self, cx: &LateContext<'_>) { for (index, conf) in self.conf_disallowed.iter().enumerate() { let segs: Vec<_> = conf.path().split("::").collect(); - if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) { + if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::ValueNS)) { self.disallowed.insert(id, index); } } @@ -102,10 +104,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { }; let msg = format!("use of a disallowed method `{}`", conf.path()); span_lint_and_then(cx, DISALLOWED_METHODS, expr.span, &msg, |diag| { - if let conf::DisallowedMethod::WithReason { - reason: Some(reason), .. - } = conf - { + if let Some(reason) = conf.reason() { diag.note(&format!("{reason} (from clippy.toml)")); } }); diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index debcd75ae75..c7131fc164d 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -1,7 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::{def::Res, def_id::DefId, Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind}; +use rustc_hir::def::{Namespace, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::{Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::Span; @@ -50,13 +52,13 @@ declare_clippy_lint! { } #[derive(Clone, Debug)] pub struct DisallowedTypes { - conf_disallowed: Vec, + conf_disallowed: Vec, def_ids: FxHashMap>, prim_tys: FxHashMap>, } impl DisallowedTypes { - pub fn new(conf_disallowed: Vec) -> Self { + pub fn new(conf_disallowed: Vec) -> Self { Self { conf_disallowed, def_ids: FxHashMap::default(), @@ -86,15 +88,9 @@ impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]); impl<'tcx> LateLintPass<'tcx> for DisallowedTypes { fn check_crate(&mut self, cx: &LateContext<'_>) { for conf in &self.conf_disallowed { - let (path, reason) = match conf { - conf::DisallowedType::Simple(path) => (path, None), - conf::DisallowedType::WithReason { path, reason } => ( - path, - reason.as_ref().map(|reason| format!("{reason} (from clippy.toml)")), - ), - }; - let segs: Vec<_> = path.split("::").collect(); - match clippy_utils::def_path_res(cx, &segs) { + let segs: Vec<_> = conf.path().split("::").collect(); + let reason = conf.reason().map(|reason| format!("{reason} (from clippy.toml)")); + match clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) { Res::Def(_, id) => { self.def_ids.insert(id, reason); }, diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 642d7070fb6..5d26e4b3360 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -45,6 +45,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(derivable_impls::DERIVABLE_IMPLS), LintId::of(derive::DERIVE_HASH_XOR_EQ), LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD), + LintId::of(disallowed_macros::DISALLOWED_MACROS), LintId::of(disallowed_methods::DISALLOWED_METHODS), LintId::of(disallowed_names::DISALLOWED_NAMES), LintId::of(disallowed_types::DISALLOWED_TYPES), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 08eb6c2b39b..05d927dbea7 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -114,6 +114,7 @@ store.register_lints(&[ derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ, derive::EXPL_IMPL_CLONE_ON_COPY, derive::UNSAFE_DERIVE_DESERIALIZE, + disallowed_macros::DISALLOWED_MACROS, disallowed_methods::DISALLOWED_METHODS, disallowed_names::DISALLOWED_NAMES, disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS, diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index 6d5483bc701..8e1390167dc 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -15,6 +15,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT), LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY), LintId::of(dereference::NEEDLESS_BORROW), + LintId::of(disallowed_macros::DISALLOWED_MACROS), LintId::of(disallowed_methods::DISALLOWED_METHODS), LintId::of(disallowed_names::DISALLOWED_NAMES), LintId::of(disallowed_types::DISALLOWED_TYPES), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e5d26ac22a7..2dcefd78763 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -199,6 +199,7 @@ mod default_union_representation; mod dereference; mod derivable_impls; mod derive; +mod disallowed_macros; mod disallowed_methods; mod disallowed_names; mod disallowed_script_idents; @@ -820,6 +821,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult)); store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); + let disallowed_macros = conf.disallowed_macros.clone(); + store.register_late_pass(move |_| Box::new(disallowed_macros::DisallowedMacros::new(disallowed_macros.clone()))); let disallowed_methods = conf.disallowed_methods.clone(); store.register_late_pass(move |_| Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone()))); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)); diff --git a/clippy_lints/src/missing_enforced_import_rename.rs b/clippy_lints/src/missing_enforced_import_rename.rs index 697e6fd24dd..872679f25ab 100644 --- a/clippy_lints/src/missing_enforced_import_rename.rs +++ b/clippy_lints/src/missing_enforced_import_rename.rs @@ -58,7 +58,8 @@ impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]); impl LateLintPass<'_> for ImportRename { fn check_crate(&mut self, cx: &LateContext<'_>) { for Rename { path, rename } in &self.conf_renames { - if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &path.split("::").collect::>()) { + let segs = path.split("::").collect::>(); + if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, None) { self.renames.insert(id, Symbol::intern(rename)); } } diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 2b336e87ef7..668123e4d6e 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -39,28 +39,28 @@ pub struct Rename { pub rename: String, } -/// A single disallowed method, used by the `DISALLOWED_METHODS` lint. #[derive(Clone, Debug, Deserialize)] #[serde(untagged)] -pub enum DisallowedMethod { +pub enum DisallowedPath { Simple(String), WithReason { path: String, reason: Option }, } -impl DisallowedMethod { +impl DisallowedPath { pub fn path(&self) -> &str { let (Self::Simple(path) | Self::WithReason { path, .. }) = self; path } -} -/// A single disallowed type, used by the `DISALLOWED_TYPES` lint. -#[derive(Clone, Debug, Deserialize)] -#[serde(untagged)] -pub enum DisallowedType { - Simple(String), - WithReason { path: String, reason: Option }, + pub fn reason(&self) -> Option<&str> { + match self { + Self::WithReason { + reason: Some(reason), .. + } => Some(reason), + _ => None, + } + } } /// Conf with parse errors @@ -315,14 +315,18 @@ define_Conf! { /// /// Whether to allow certain wildcard imports (prelude, super in tests). (warn_on_all_wildcard_imports: bool = false), + /// Lint: DISALLOWED_MACROS. + /// + /// The list of disallowed macros, written as fully qualified paths. + (disallowed_macros: Vec = Vec::new()), /// Lint: DISALLOWED_METHODS. /// /// The list of disallowed methods, written as fully qualified paths. - (disallowed_methods: Vec = Vec::new()), + (disallowed_methods: Vec = Vec::new()), /// Lint: DISALLOWED_TYPES. /// /// The list of disallowed types, written as fully qualified paths. - (disallowed_types: Vec = Vec::new()), + (disallowed_types: Vec = Vec::new()), /// Lint: UNREADABLE_LITERAL. /// /// Should the fraction of a decimal be linted to include separators. @@ -362,7 +366,7 @@ define_Conf! { /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. (max_suggested_slice_pattern_length: u64 = 3), /// Lint: AWAIT_HOLDING_INVALID_TYPE - (await_holding_invalid_types: Vec = Vec::new()), + (await_holding_invalid_types: Vec = Vec::new()), /// Lint: LARGE_INCLUDE_FILE. /// /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index 6309a04c73d..85bcbc7ad23 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -15,7 +15,7 @@ use rustc_ast::visit::FnKind; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def::{DefKind, Namespace, Res}; use rustc_hir::def_id::DefId; use rustc_hir::hir_id::CRATE_HIR_ID; use rustc_hir::intravisit::Visitor; @@ -920,7 +920,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath { // Extract the path to the matched type if let Some(segments) = path_to_matched_type(cx, item_arg); let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect(); - if let Some(def_id) = def_path_res(cx, &segments[..]).opt_def_id(); + if let Some(def_id) = def_path_res(cx, &segments[..], None).opt_def_id(); then { // def_path_res will match field names before anything else, but for this we want to match // inherent functions first. @@ -952,7 +952,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath { Item::DiagnosticItem(*item_name), ) } else if let Some(lang_item) = cx.tcx.lang_items().items().iter().position(|id| *id == Some(def_id)) { - let lang_items = def_path_res(cx, &["rustc_hir", "lang_items", "LangItem"]).def_id(); + let lang_items = def_path_res(cx, &["rustc_hir", "lang_items", "LangItem"], Some(Namespace::TypeNS)).def_id(); let item_name = cx.tcx.adt_def(lang_items).variants().iter().nth(lang_item).unwrap().name; ( "use of a def path to a `LangItem`", @@ -1115,7 +1115,7 @@ fn read_mir_alloc_def_path<'tcx>(cx: &LateContext<'tcx>, alloc: &'tcx Allocation // This is not a complete resolver for paths. It works on all the paths currently used in the paths // module. That's all it does and all it needs to do. pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool { - if def_path_res(cx, path) != Res::Err { + if def_path_res(cx, path, None) != Res::Err { return true; } @@ -1206,7 +1206,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol { } for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] { - if let Some(def_id) = def_path_res(cx, module).opt_def_id() { + if let Some(def_id) = def_path_res(cx, module, None).opt_def_id() { for item in cx.tcx.module_children(def_id).iter() { if_chain! { if let Res::Def(DefKind::Const, item_def_id) = item.res; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 3caa6380e09..39198519329 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -78,7 +78,7 @@ use rustc_ast::Attribute; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::unhash::UnhashMap; use rustc_hir as hir; -use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def::{DefKind, Namespace, Res}; use rustc_hir::def_id::{CrateNum, DefId, LocalDefId}; use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; @@ -522,15 +522,49 @@ pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx> path_res(cx, maybe_path).opt_def_id() } -/// Resolves a def path like `std::vec::Vec`. +fn find_primitive<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator + 'tcx { + let single = |ty| tcx.incoherent_impls(ty).iter().copied(); + let empty = || [].iter().copied(); + match name { + "bool" => single(BoolSimplifiedType), + "char" => single(CharSimplifiedType), + "str" => single(StrSimplifiedType), + "array" => single(ArraySimplifiedType), + "slice" => single(SliceSimplifiedType), + // FIXME: rustdoc documents these two using just `pointer`. + // + // Maybe this is something we should do here too. + "const_ptr" => single(PtrSimplifiedType(Mutability::Not)), + "mut_ptr" => single(PtrSimplifiedType(Mutability::Mut)), + "isize" => single(IntSimplifiedType(IntTy::Isize)), + "i8" => single(IntSimplifiedType(IntTy::I8)), + "i16" => single(IntSimplifiedType(IntTy::I16)), + "i32" => single(IntSimplifiedType(IntTy::I32)), + "i64" => single(IntSimplifiedType(IntTy::I64)), + "i128" => single(IntSimplifiedType(IntTy::I128)), + "usize" => single(UintSimplifiedType(UintTy::Usize)), + "u8" => single(UintSimplifiedType(UintTy::U8)), + "u16" => single(UintSimplifiedType(UintTy::U16)), + "u32" => single(UintSimplifiedType(UintTy::U32)), + "u64" => single(UintSimplifiedType(UintTy::U64)), + "u128" => single(UintSimplifiedType(UintTy::U128)), + "f32" => single(FloatSimplifiedType(FloatTy::F32)), + "f64" => single(FloatSimplifiedType(FloatTy::F64)), + _ => empty(), + } +} + +/// Resolves a def path like `std::vec::Vec`. `namespace_hint` can be supplied to disambiguate +/// between `std::vec` the module and `std::vec` the macro +/// /// This function is expensive and should be used sparingly. -pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res { - fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: &str) -> Option { +pub fn def_path_res(cx: &LateContext<'_>, path: &[&str], namespace_hint: Option) -> Res { + fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: &str, matches_ns: impl Fn(Res) -> bool) -> Option { match tcx.def_kind(def_id) { DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx .module_children(def_id) .iter() - .find(|item| item.ident.name.as_str() == name) + .find(|item| item.ident.name.as_str() == name && matches_ns(item.res.expect_non_local())) .map(|child| child.res.expect_non_local()), DefKind::Impl => tcx .associated_item_def_ids(def_id) @@ -548,37 +582,7 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res { _ => None, } } - fn find_primitive<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator + 'tcx { - let single = |ty| tcx.incoherent_impls(ty).iter().copied(); - let empty = || [].iter().copied(); - match name { - "bool" => single(BoolSimplifiedType), - "char" => single(CharSimplifiedType), - "str" => single(StrSimplifiedType), - "array" => single(ArraySimplifiedType), - "slice" => single(SliceSimplifiedType), - // FIXME: rustdoc documents these two using just `pointer`. - // - // Maybe this is something we should do here too. - "const_ptr" => single(PtrSimplifiedType(Mutability::Not)), - "mut_ptr" => single(PtrSimplifiedType(Mutability::Mut)), - "isize" => single(IntSimplifiedType(IntTy::Isize)), - "i8" => single(IntSimplifiedType(IntTy::I8)), - "i16" => single(IntSimplifiedType(IntTy::I16)), - "i32" => single(IntSimplifiedType(IntTy::I32)), - "i64" => single(IntSimplifiedType(IntTy::I64)), - "i128" => single(IntSimplifiedType(IntTy::I128)), - "usize" => single(UintSimplifiedType(UintTy::Usize)), - "u8" => single(UintSimplifiedType(UintTy::U8)), - "u16" => single(UintSimplifiedType(UintTy::U16)), - "u32" => single(UintSimplifiedType(UintTy::U32)), - "u64" => single(UintSimplifiedType(UintTy::U64)), - "u128" => single(UintSimplifiedType(UintTy::U128)), - "f32" => single(FloatSimplifiedType(FloatTy::F32)), - "f64" => single(FloatSimplifiedType(FloatTy::F64)), - _ => empty(), - } - } + fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option { tcx.crates(()) .iter() @@ -587,32 +591,45 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res { .map(CrateNum::as_def_id) } - let (base, first, path) = match *path { - [base, first, ref path @ ..] => (base, first, path), + let (base, path) = match *path { [primitive] => { return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy); }, + [base, ref path @ ..] => (base, path), _ => return Res::Err, }; let tcx = cx.tcx; let starts = find_primitive(tcx, base) .chain(find_crate(tcx, base)) - .filter_map(|id| item_child_by_name(tcx, id, first)); + .map(|id| Res::Def(tcx.def_kind(id), id)); for first in starts { let last = path .iter() .copied() + .enumerate() // for each segment, find the child item - .try_fold(first, |res, segment| { + .try_fold(first, |res, (idx, segment)| { + let matches_ns = |res: Res| { + // If at the last segment in the path, respect the namespace hint + if idx == path.len() - 1 { + match namespace_hint { + Some(ns) => res.matches_ns(ns), + None => true, + } + } else { + res.matches_ns(Namespace::TypeNS) + } + }; + let def_id = res.def_id(); - if let Some(item) = item_child_by_name(tcx, def_id, segment) { + if let Some(item) = item_child_by_name(tcx, def_id, segment, matches_ns) { Some(item) } else if matches!(res, Res::Def(DefKind::Enum | DefKind::Struct, _)) { // it is not a child item so check inherent impl items tcx.inherent_impls(def_id) .iter() - .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment)) + .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment, matches_ns)) } else { None } @@ -628,8 +645,10 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res { /// Convenience function to get the `DefId` of a trait by path. /// It could be a trait or trait alias. +/// +/// This function is expensive and should be used sparingly. pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option { - match def_path_res(cx, path) { + match def_path_res(cx, path, Some(Namespace::TypeNS)) { Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id), _ => None, } diff --git a/src/docs.rs b/src/docs.rs index 1b727e1a100..3bf488ab477 100644 --- a/src/docs.rs +++ b/src/docs.rs @@ -106,6 +106,7 @@ docs! { "derive_hash_xor_eq", "derive_ord_xor_partial_ord", "derive_partial_eq_without_eq", + "disallowed_macros", "disallowed_methods", "disallowed_names", "disallowed_script_idents", diff --git a/src/docs/disallowed_macros.txt b/src/docs/disallowed_macros.txt new file mode 100644 index 00000000000..96fa15afabf --- /dev/null +++ b/src/docs/disallowed_macros.txt @@ -0,0 +1,36 @@ +### What it does +Denies the configured macros in clippy.toml + +Note: Even though this lint is warn-by-default, it will only trigger if +macros are defined in the clippy.toml file. + +### Why is this bad? +Some macros are undesirable in certain contexts, and it's beneficial to +lint for them as needed. + +### Example +An example clippy.toml configuration: +``` +disallowed-macros = [ + # Can use a string as the path of the disallowed macro. + "std::print", + # Can also use an inline table with a `path` key. + { path = "std::println" }, + # When using an inline table, can add a `reason` for why the macro + # is disallowed. + { path = "serde::Serialize", reason = "no serializing" }, +] +``` +``` +use serde::Serialize; + +// Example code where clippy issues a warning +println!("warns"); + +// The diagnostic will contain the message "no serializing" +#[derive(Serialize)] +struct Data { + name: String, + value: usize, +} +``` \ No newline at end of file diff --git a/tests/ui-toml/disallowed_macros/auxiliary/macros.rs b/tests/ui-toml/disallowed_macros/auxiliary/macros.rs new file mode 100644 index 00000000000..fcaeace0e98 --- /dev/null +++ b/tests/ui-toml/disallowed_macros/auxiliary/macros.rs @@ -0,0 +1,32 @@ +#[macro_export] +macro_rules! expr { + () => { + 1 + }; +} + +#[macro_export] +macro_rules! stmt { + () => { + let _x = (); + }; +} + +#[macro_export] +macro_rules! ty { + () => { &'static str }; +} + +#[macro_export] +macro_rules! pat { + () => { + _ + }; +} + +#[macro_export] +macro_rules! item { + () => { + const ITEM: usize = 1; + }; +} diff --git a/tests/ui-toml/disallowed_macros/clippy.toml b/tests/ui-toml/disallowed_macros/clippy.toml new file mode 100644 index 00000000000..c8fe8be9a77 --- /dev/null +++ b/tests/ui-toml/disallowed_macros/clippy.toml @@ -0,0 +1,11 @@ +disallowed-macros = [ + "std::println", + "std::vec", + { path = "std::cfg" }, + { path = "serde::Serialize", reason = "no serializing" }, + "macros::expr", + "macros::stmt", + "macros::ty", + "macros::pat", + "macros::item", +] diff --git a/tests/ui-toml/disallowed_macros/disallowed_macros.rs b/tests/ui-toml/disallowed_macros/disallowed_macros.rs new file mode 100644 index 00000000000..2bb5376076e --- /dev/null +++ b/tests/ui-toml/disallowed_macros/disallowed_macros.rs @@ -0,0 +1,39 @@ +// aux-build:macros.rs + +#![allow(unused)] + +extern crate macros; + +use serde::Serialize; + +fn main() { + println!("one"); + println!("two"); + cfg!(unix); + vec![1, 2, 3]; + + #[derive(Serialize)] + struct Derive; + + let _ = macros::expr!(); + macros::stmt!(); + let macros::pat!() = 1; + let _: macros::ty!() = ""; + macros::item!(); + + eprintln!("allowed"); +} + +struct S; + +impl S { + macros::item!(); +} + +trait Y { + macros::item!(); +} + +impl Y for S { + macros::item!(); +} diff --git a/tests/ui-toml/disallowed_macros/disallowed_macros.stderr b/tests/ui-toml/disallowed_macros/disallowed_macros.stderr new file mode 100644 index 00000000000..aed9feb6f79 --- /dev/null +++ b/tests/ui-toml/disallowed_macros/disallowed_macros.stderr @@ -0,0 +1,84 @@ +error: use of a disallowed macro `std::println` + --> $DIR/disallowed_macros.rs:10:5 + | +LL | println!("one"); + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::disallowed-macros` implied by `-D warnings` + +error: use of a disallowed macro `std::println` + --> $DIR/disallowed_macros.rs:11:5 + | +LL | println!("two"); + | ^^^^^^^^^^^^^^^ + +error: use of a disallowed macro `std::cfg` + --> $DIR/disallowed_macros.rs:12:5 + | +LL | cfg!(unix); + | ^^^^^^^^^^ + +error: use of a disallowed macro `std::vec` + --> $DIR/disallowed_macros.rs:13:5 + | +LL | vec![1, 2, 3]; + | ^^^^^^^^^^^^^ + +error: use of a disallowed macro `serde::Serialize` + --> $DIR/disallowed_macros.rs:15:14 + | +LL | #[derive(Serialize)] + | ^^^^^^^^^ + | + = note: no serializing (from clippy.toml) + +error: use of a disallowed macro `macros::expr` + --> $DIR/disallowed_macros.rs:18:13 + | +LL | let _ = macros::expr!(); + | ^^^^^^^^^^^^^^^ + +error: use of a disallowed macro `macros::stmt` + --> $DIR/disallowed_macros.rs:19:5 + | +LL | macros::stmt!(); + | ^^^^^^^^^^^^^^^ + +error: use of a disallowed macro `macros::pat` + --> $DIR/disallowed_macros.rs:20:9 + | +LL | let macros::pat!() = 1; + | ^^^^^^^^^^^^^^ + +error: use of a disallowed macro `macros::ty` + --> $DIR/disallowed_macros.rs:21:12 + | +LL | let _: macros::ty!() = ""; + | ^^^^^^^^^^^^^ + +error: use of a disallowed macro `macros::item` + --> $DIR/disallowed_macros.rs:22:5 + | +LL | macros::item!(); + | ^^^^^^^^^^^^^^^ + +error: use of a disallowed macro `macros::item` + --> $DIR/disallowed_macros.rs:30:5 + | +LL | macros::item!(); + | ^^^^^^^^^^^^^^^ + +error: use of a disallowed macro `macros::item` + --> $DIR/disallowed_macros.rs:34:5 + | +LL | macros::item!(); + | ^^^^^^^^^^^^^^^ + +error: use of a disallowed macro `macros::item` + --> $DIR/disallowed_macros.rs:38:5 + | +LL | macros::item!(); + | ^^^^^^^^^^^^^^^ + +error: aborting due to 13 previous errors + diff --git a/tests/ui-toml/toml_disallowed_methods/clippy.toml b/tests/ui-toml/toml_disallowed_methods/clippy.toml index c902d21123d..28774db625b 100644 --- a/tests/ui-toml/toml_disallowed_methods/clippy.toml +++ b/tests/ui-toml/toml_disallowed_methods/clippy.toml @@ -3,6 +3,7 @@ disallowed-methods = [ "std::iter::Iterator::sum", "f32::clamp", "slice::sort_unstable", + "futures::stream::select_all", # can give path and reason with an inline table { path = "regex::Regex::is_match", reason = "no matching allowed" }, # can use an inline table but omit reason diff --git a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs index 3397fa1ec69..b483f160028 100644 --- a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs +++ b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs @@ -1,6 +1,9 @@ #![warn(clippy::disallowed_methods)] +extern crate futures; extern crate regex; + +use futures::stream::{empty, select_all}; use regex::Regex; fn main() { @@ -20,4 +23,7 @@ fn main() { let in_call = Box::new(f32::clamp); let in_method_call = ["^", "$"].into_iter().map(Regex::new); + + // resolve ambiguity between `futures::stream::select_all` the module and the function + let same_name_as_module = select_all(vec![empty::<()>()]); } diff --git a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr index 5cbb567546c..6d78c32e127 100644 --- a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr +++ b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr @@ -1,5 +1,5 @@ error: use of a disallowed method `regex::Regex::new` - --> $DIR/conf_disallowed_methods.rs:7:14 + --> $DIR/conf_disallowed_methods.rs:10:14 | LL | let re = Regex::new(r"ab.*c").unwrap(); | ^^^^^^^^^^^^^^^^^^^^ @@ -7,7 +7,7 @@ LL | let re = Regex::new(r"ab.*c").unwrap(); = note: `-D clippy::disallowed-methods` implied by `-D warnings` error: use of a disallowed method `regex::Regex::is_match` - --> $DIR/conf_disallowed_methods.rs:8:5 + --> $DIR/conf_disallowed_methods.rs:11:5 | LL | re.is_match("abc"); | ^^^^^^^^^^^^^^^^^^ @@ -15,40 +15,46 @@ LL | re.is_match("abc"); = note: no matching allowed (from clippy.toml) error: use of a disallowed method `std::iter::Iterator::sum` - --> $DIR/conf_disallowed_methods.rs:11:5 + --> $DIR/conf_disallowed_methods.rs:14:5 | LL | a.iter().sum::(); | ^^^^^^^^^^^^^^^^^^^^^ error: use of a disallowed method `slice::sort_unstable` - --> $DIR/conf_disallowed_methods.rs:13:5 + --> $DIR/conf_disallowed_methods.rs:16:5 | LL | a.sort_unstable(); | ^^^^^^^^^^^^^^^^^ error: use of a disallowed method `f32::clamp` - --> $DIR/conf_disallowed_methods.rs:15:13 + --> $DIR/conf_disallowed_methods.rs:18:13 | LL | let _ = 2.0f32.clamp(3.0f32, 4.0f32); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: use of a disallowed method `regex::Regex::new` - --> $DIR/conf_disallowed_methods.rs:18:61 + --> $DIR/conf_disallowed_methods.rs:21:61 | LL | let indirect: fn(&str) -> Result = Regex::new; | ^^^^^^^^^^ error: use of a disallowed method `f32::clamp` - --> $DIR/conf_disallowed_methods.rs:21:28 + --> $DIR/conf_disallowed_methods.rs:24:28 | LL | let in_call = Box::new(f32::clamp); | ^^^^^^^^^^ error: use of a disallowed method `regex::Regex::new` - --> $DIR/conf_disallowed_methods.rs:22:53 + --> $DIR/conf_disallowed_methods.rs:25:53 | LL | let in_method_call = ["^", "$"].into_iter().map(Regex::new); | ^^^^^^^^^^ -error: aborting due to 8 previous errors +error: use of a disallowed method `futures::stream::select_all` + --> $DIR/conf_disallowed_methods.rs:28:31 + | +LL | let same_name_as_module = select_all(vec![empty::<()>()]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 9 previous errors diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index f27f78d15d3..82ee8054132 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -11,6 +11,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie cargo-ignore-publish cognitive-complexity-threshold cyclomatic-complexity-threshold + disallowed-macros disallowed-methods disallowed-names disallowed-types