Auto merge of #9495 - Alexendoo:disallowed-macros, r=Jarcho

Add `disallowed_macros` lint

Closes #7790
Fixes #9558

`clippy_utils::def_path_res` already resolved macro definitions which is nice, it just needed a tweak to be able to disambiguate e.g. `std::vec` the module & `std::vec` the macro, and `serde::Serialize` the trait & `serde::Serialize` the derive macro

changelog: new lint: [`disallowed_macros`]
changelog: [`disallowed_methods`], [`disallowed_types`]: Fix false negative when a type/fn/macro share the same path
This commit is contained in:
bors 2022-10-05 14:49:31 +00:00
commit e687bedac6
23 changed files with 500 additions and 110 deletions

View File

@ -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_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_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 [`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_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_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 [`disallowed_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names

View File

@ -1,14 +1,15 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{match_def_path, paths}; use clippy_utils::{match_def_path, paths};
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{Namespace, Res};
use rustc_hir::def_id::DefId; 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_lint::{LateContext, LateLintPass};
use rustc_middle::ty::GeneratorInteriorTypeCause; use rustc_middle::ty::GeneratorInteriorTypeCause;
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span}; use rustc_span::{sym, Span};
use crate::utils::conf::DisallowedType; use crate::utils::conf::DisallowedPath;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -171,12 +172,12 @@ impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF,
#[derive(Debug)] #[derive(Debug)]
pub struct AwaitHolding { pub struct AwaitHolding {
conf_invalid_types: Vec<DisallowedType>, conf_invalid_types: Vec<DisallowedPath>,
def_ids: FxHashMap<DefId, DisallowedType>, def_ids: FxHashMap<DefId, DisallowedPath>,
} }
impl AwaitHolding { impl AwaitHolding {
pub(crate) fn new(conf_invalid_types: Vec<DisallowedType>) -> Self { pub(crate) fn new(conf_invalid_types: Vec<DisallowedPath>) -> Self {
Self { Self {
conf_invalid_types, conf_invalid_types,
def_ids: FxHashMap::default(), def_ids: FxHashMap::default(),
@ -187,11 +188,8 @@ impl AwaitHolding {
impl LateLintPass<'_> for AwaitHolding { impl LateLintPass<'_> for AwaitHolding {
fn check_crate(&mut self, cx: &LateContext<'_>) { fn check_crate(&mut self, cx: &LateContext<'_>) {
for conf in &self.conf_invalid_types { for conf in &self.conf_invalid_types {
let path = match conf { let segs: Vec<_> = conf.path().split("::").collect();
DisallowedType::Simple(path) | DisallowedType::WithReason { path, .. } => path, if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) {
};
let segs: Vec<_> = path.split("::").collect();
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
self.def_ids.insert(id, conf.clone()); self.def_ids.insert(id, conf.clone());
} }
} }
@ -256,20 +254,18 @@ impl AwaitHolding {
} }
} }
fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedType) { fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedPath) {
let (type_name, reason) = match disallowed {
DisallowedType::Simple(path) => (path, &None),
DisallowedType::WithReason { path, reason } => (path, reason),
};
span_lint_and_then( span_lint_and_then(
cx, cx,
AWAIT_HOLDING_INVALID_TYPE, AWAIT_HOLDING_INVALID_TYPE,
span, 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| { |diag| {
if let Some(reason) = reason { if let Some(reason) = disallowed.reason() {
diag.note(reason.clone()); diag.note(reason);
} }
}, },
); );

View File

@ -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<conf::DisallowedPath>,
disallowed: DefIdMap<usize>,
seen: FxHashSet<ExpnId>,
}
impl DisallowedMacros {
pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> 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);
}
}

View File

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{fn_def_id, get_parent_expr, path_def_id}; 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_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -58,12 +60,12 @@ declare_clippy_lint! {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DisallowedMethods { pub struct DisallowedMethods {
conf_disallowed: Vec<conf::DisallowedMethod>, conf_disallowed: Vec<conf::DisallowedPath>,
disallowed: DefIdMap<usize>, disallowed: DefIdMap<usize>,
} }
impl DisallowedMethods { impl DisallowedMethods {
pub fn new(conf_disallowed: Vec<conf::DisallowedMethod>) -> Self { pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> Self {
Self { Self {
conf_disallowed, conf_disallowed,
disallowed: DefIdMap::default(), disallowed: DefIdMap::default(),
@ -77,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
fn check_crate(&mut self, cx: &LateContext<'_>) { fn check_crate(&mut self, cx: &LateContext<'_>) {
for (index, conf) in self.conf_disallowed.iter().enumerate() { for (index, conf) in self.conf_disallowed.iter().enumerate() {
let segs: Vec<_> = conf.path().split("::").collect(); 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); 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()); let msg = format!("use of a disallowed method `{}`", conf.path());
span_lint_and_then(cx, DISALLOWED_METHODS, expr.span, &msg, |diag| { span_lint_and_then(cx, DISALLOWED_METHODS, expr.span, &msg, |diag| {
if let conf::DisallowedMethod::WithReason { if let Some(reason) = conf.reason() {
reason: Some(reason), ..
} = conf
{
diag.note(&format!("{reason} (from clippy.toml)")); diag.note(&format!("{reason} (from clippy.toml)"));
} }
}); });

View File

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use rustc_data_structures::fx::FxHashMap; 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_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span; use rustc_span::Span;
@ -50,13 +52,13 @@ declare_clippy_lint! {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DisallowedTypes { pub struct DisallowedTypes {
conf_disallowed: Vec<conf::DisallowedType>, conf_disallowed: Vec<conf::DisallowedPath>,
def_ids: FxHashMap<DefId, Option<String>>, def_ids: FxHashMap<DefId, Option<String>>,
prim_tys: FxHashMap<PrimTy, Option<String>>, prim_tys: FxHashMap<PrimTy, Option<String>>,
} }
impl DisallowedTypes { impl DisallowedTypes {
pub fn new(conf_disallowed: Vec<conf::DisallowedType>) -> Self { pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> Self {
Self { Self {
conf_disallowed, conf_disallowed,
def_ids: FxHashMap::default(), def_ids: FxHashMap::default(),
@ -86,15 +88,9 @@ impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]);
impl<'tcx> LateLintPass<'tcx> for DisallowedTypes { impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
fn check_crate(&mut self, cx: &LateContext<'_>) { fn check_crate(&mut self, cx: &LateContext<'_>) {
for conf in &self.conf_disallowed { for conf in &self.conf_disallowed {
let (path, reason) = match conf { let segs: Vec<_> = conf.path().split("::").collect();
conf::DisallowedType::Simple(path) => (path, None), let reason = conf.reason().map(|reason| format!("{reason} (from clippy.toml)"));
conf::DisallowedType::WithReason { path, reason } => ( match clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) {
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) {
Res::Def(_, id) => { Res::Def(_, id) => {
self.def_ids.insert(id, reason); self.def_ids.insert(id, reason);
}, },

View File

@ -45,6 +45,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(derivable_impls::DERIVABLE_IMPLS), LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(derive::DERIVE_HASH_XOR_EQ), LintId::of(derive::DERIVE_HASH_XOR_EQ),
LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD), LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
LintId::of(disallowed_macros::DISALLOWED_MACROS),
LintId::of(disallowed_methods::DISALLOWED_METHODS), LintId::of(disallowed_methods::DISALLOWED_METHODS),
LintId::of(disallowed_names::DISALLOWED_NAMES), LintId::of(disallowed_names::DISALLOWED_NAMES),
LintId::of(disallowed_types::DISALLOWED_TYPES), LintId::of(disallowed_types::DISALLOWED_TYPES),

View File

@ -114,6 +114,7 @@ store.register_lints(&[
derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ, derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ,
derive::EXPL_IMPL_CLONE_ON_COPY, derive::EXPL_IMPL_CLONE_ON_COPY,
derive::UNSAFE_DERIVE_DESERIALIZE, derive::UNSAFE_DERIVE_DESERIALIZE,
disallowed_macros::DISALLOWED_MACROS,
disallowed_methods::DISALLOWED_METHODS, disallowed_methods::DISALLOWED_METHODS,
disallowed_names::DISALLOWED_NAMES, disallowed_names::DISALLOWED_NAMES,
disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS, disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS,

View File

@ -15,6 +15,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT), LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY), LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY),
LintId::of(dereference::NEEDLESS_BORROW), LintId::of(dereference::NEEDLESS_BORROW),
LintId::of(disallowed_macros::DISALLOWED_MACROS),
LintId::of(disallowed_methods::DISALLOWED_METHODS), LintId::of(disallowed_methods::DISALLOWED_METHODS),
LintId::of(disallowed_names::DISALLOWED_NAMES), LintId::of(disallowed_names::DISALLOWED_NAMES),
LintId::of(disallowed_types::DISALLOWED_TYPES), LintId::of(disallowed_types::DISALLOWED_TYPES),

View File

@ -199,6 +199,7 @@ mod default_union_representation;
mod dereference; mod dereference;
mod derivable_impls; mod derivable_impls;
mod derive; mod derive;
mod disallowed_macros;
mod disallowed_methods; mod disallowed_methods;
mod disallowed_names; mod disallowed_names;
mod disallowed_script_idents; 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(unwrap_in_result::UnwrapInResult));
store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); 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(); let disallowed_methods = conf.disallowed_methods.clone();
store.register_late_pass(move |_| Box::new(disallowed_methods::DisallowedMethods::new(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)); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));

View File

@ -58,7 +58,8 @@ impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]);
impl LateLintPass<'_> for ImportRename { impl LateLintPass<'_> for ImportRename {
fn check_crate(&mut self, cx: &LateContext<'_>) { fn check_crate(&mut self, cx: &LateContext<'_>) {
for Rename { path, rename } in &self.conf_renames { for Rename { path, rename } in &self.conf_renames {
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &path.split("::").collect::<Vec<_>>()) { let segs = path.split("::").collect::<Vec<_>>();
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, None) {
self.renames.insert(id, Symbol::intern(rename)); self.renames.insert(id, Symbol::intern(rename));
} }
} }

View File

@ -39,28 +39,28 @@ pub struct Rename {
pub rename: String, pub rename: String,
} }
/// A single disallowed method, used by the `DISALLOWED_METHODS` lint.
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum DisallowedMethod { pub enum DisallowedPath {
Simple(String), Simple(String),
WithReason { path: String, reason: Option<String> }, WithReason { path: String, reason: Option<String> },
} }
impl DisallowedMethod { impl DisallowedPath {
pub fn path(&self) -> &str { pub fn path(&self) -> &str {
let (Self::Simple(path) | Self::WithReason { path, .. }) = self; let (Self::Simple(path) | Self::WithReason { path, .. }) = self;
path path
} }
}
/// A single disallowed type, used by the `DISALLOWED_TYPES` lint. pub fn reason(&self) -> Option<&str> {
#[derive(Clone, Debug, Deserialize)] match self {
#[serde(untagged)] Self::WithReason {
pub enum DisallowedType { reason: Some(reason), ..
Simple(String), } => Some(reason),
WithReason { path: String, reason: Option<String> }, _ => None,
}
}
} }
/// Conf with parse errors /// Conf with parse errors
@ -315,14 +315,18 @@ define_Conf! {
/// ///
/// Whether to allow certain wildcard imports (prelude, super in tests). /// Whether to allow certain wildcard imports (prelude, super in tests).
(warn_on_all_wildcard_imports: bool = false), (warn_on_all_wildcard_imports: bool = false),
/// Lint: DISALLOWED_MACROS.
///
/// The list of disallowed macros, written as fully qualified paths.
(disallowed_macros: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: DISALLOWED_METHODS. /// Lint: DISALLOWED_METHODS.
/// ///
/// The list of disallowed methods, written as fully qualified paths. /// The list of disallowed methods, written as fully qualified paths.
(disallowed_methods: Vec<crate::utils::conf::DisallowedMethod> = Vec::new()), (disallowed_methods: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: DISALLOWED_TYPES. /// Lint: DISALLOWED_TYPES.
/// ///
/// The list of disallowed types, written as fully qualified paths. /// The list of disallowed types, written as fully qualified paths.
(disallowed_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()), (disallowed_types: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: UNREADABLE_LITERAL. /// Lint: UNREADABLE_LITERAL.
/// ///
/// Should the fraction of a decimal be linted to include separators. /// 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. /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
(max_suggested_slice_pattern_length: u64 = 3), (max_suggested_slice_pattern_length: u64 = 3),
/// Lint: AWAIT_HOLDING_INVALID_TYPE /// Lint: AWAIT_HOLDING_INVALID_TYPE
(await_holding_invalid_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()), (await_holding_invalid_types: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: LARGE_INCLUDE_FILE. /// Lint: LARGE_INCLUDE_FILE.
/// ///
/// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes /// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes

View File

@ -15,7 +15,7 @@ use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; 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::def_id::DefId;
use rustc_hir::hir_id::CRATE_HIR_ID; use rustc_hir::hir_id::CRATE_HIR_ID;
use rustc_hir::intravisit::Visitor; use rustc_hir::intravisit::Visitor;
@ -920,7 +920,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
// Extract the path to the matched type // Extract the path to the matched type
if let Some(segments) = path_to_matched_type(cx, item_arg); if let Some(segments) = path_to_matched_type(cx, item_arg);
let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect(); 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 { then {
// def_path_res will match field names before anything else, but for this we want to match // def_path_res will match field names before anything else, but for this we want to match
// inherent functions first. // inherent functions first.
@ -952,7 +952,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath {
Item::DiagnosticItem(*item_name), Item::DiagnosticItem(*item_name),
) )
} else if let Some(lang_item) = cx.tcx.lang_items().items().iter().position(|id| *id == Some(def_id)) { } 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; let item_name = cx.tcx.adt_def(lang_items).variants().iter().nth(lang_item).unwrap().name;
( (
"use of a def path to a `LangItem`", "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 // 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. // module. That's all it does and all it needs to do.
pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool { 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; return true;
} }
@ -1206,7 +1206,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
} }
for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] { 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() { for item in cx.tcx.module_children(def_id).iter() {
if_chain! { if_chain! {
if let Res::Def(DefKind::Const, item_def_id) = item.res; if let Res::Def(DefKind::Const, item_def_id) = item.res;

View File

@ -78,7 +78,7 @@ use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::unhash::UnhashMap; use rustc_data_structures::unhash::UnhashMap;
use rustc_hir as hir; 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::def_id::{CrateNum, DefId, LocalDefId};
use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
@ -522,33 +522,7 @@ pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>
path_res(cx, maybe_path).opt_def_id() 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<Item = DefId> + 'tcx {
/// 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<Res> {
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)
.map(|child| child.res.expect_non_local()),
DefKind::Impl => tcx
.associated_item_def_ids(def_id)
.iter()
.copied()
.find(|assoc_def_id| tcx.item_name(*assoc_def_id).as_str() == name)
.map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id)),
DefKind::Struct | DefKind::Union => tcx
.adt_def(def_id)
.non_enum_variant()
.fields
.iter()
.find(|f| f.name.as_str() == name)
.map(|f| Res::Def(DefKind::Field, f.did)),
_ => None,
}
}
fn find_primitive<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx {
let single = |ty| tcx.incoherent_impls(ty).iter().copied(); let single = |ty| tcx.incoherent_impls(ty).iter().copied();
let empty = || [].iter().copied(); let empty = || [].iter().copied();
match name { match name {
@ -578,7 +552,37 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
"f64" => single(FloatSimplifiedType(FloatTy::F64)), "f64" => single(FloatSimplifiedType(FloatTy::F64)),
_ => empty(), _ => 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], namespace_hint: Option<Namespace>) -> Res {
fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: &str, matches_ns: impl Fn(Res) -> bool) -> Option<Res> {
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 && matches_ns(item.res.expect_non_local()))
.map(|child| child.res.expect_non_local()),
DefKind::Impl => tcx
.associated_item_def_ids(def_id)
.iter()
.copied()
.find(|assoc_def_id| tcx.item_name(*assoc_def_id).as_str() == name)
.map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id)),
DefKind::Struct | DefKind::Union => tcx
.adt_def(def_id)
.non_enum_variant()
.fields
.iter()
.find(|f| f.name.as_str() == name)
.map(|f| Res::Def(DefKind::Field, f.did)),
_ => None,
} }
}
fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> { fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> {
tcx.crates(()) tcx.crates(())
.iter() .iter()
@ -587,32 +591,45 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
.map(CrateNum::as_def_id) .map(CrateNum::as_def_id)
} }
let (base, first, path) = match *path { let (base, path) = match *path {
[base, first, ref path @ ..] => (base, first, path),
[primitive] => { [primitive] => {
return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy); return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy);
}, },
[base, ref path @ ..] => (base, path),
_ => return Res::Err, _ => return Res::Err,
}; };
let tcx = cx.tcx; let tcx = cx.tcx;
let starts = find_primitive(tcx, base) let starts = find_primitive(tcx, base)
.chain(find_crate(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 { for first in starts {
let last = path let last = path
.iter() .iter()
.copied() .copied()
.enumerate()
// for each segment, find the child item // 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(); 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) Some(item)
} else if matches!(res, Res::Def(DefKind::Enum | DefKind::Struct, _)) { } else if matches!(res, Res::Def(DefKind::Enum | DefKind::Struct, _)) {
// it is not a child item so check inherent impl items // it is not a child item so check inherent impl items
tcx.inherent_impls(def_id) tcx.inherent_impls(def_id)
.iter() .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 { } else {
None 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. /// Convenience function to get the `DefId` of a trait by path.
/// It could be a trait or trait alias. /// 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<DefId> { pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
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), Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
_ => None, _ => None,
} }

View File

@ -106,6 +106,7 @@ docs! {
"derive_hash_xor_eq", "derive_hash_xor_eq",
"derive_ord_xor_partial_ord", "derive_ord_xor_partial_ord",
"derive_partial_eq_without_eq", "derive_partial_eq_without_eq",
"disallowed_macros",
"disallowed_methods", "disallowed_methods",
"disallowed_names", "disallowed_names",
"disallowed_script_idents", "disallowed_script_idents",

View File

@ -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,
}
```

View File

@ -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;
};
}

View File

@ -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",
]

View File

@ -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!();
}

View File

@ -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

View File

@ -3,6 +3,7 @@ disallowed-methods = [
"std::iter::Iterator::sum", "std::iter::Iterator::sum",
"f32::clamp", "f32::clamp",
"slice::sort_unstable", "slice::sort_unstable",
"futures::stream::select_all",
# can give path and reason with an inline table # can give path and reason with an inline table
{ path = "regex::Regex::is_match", reason = "no matching allowed" }, { path = "regex::Regex::is_match", reason = "no matching allowed" },
# can use an inline table but omit reason # can use an inline table but omit reason

View File

@ -1,6 +1,9 @@
#![warn(clippy::disallowed_methods)] #![warn(clippy::disallowed_methods)]
extern crate futures;
extern crate regex; extern crate regex;
use futures::stream::{empty, select_all};
use regex::Regex; use regex::Regex;
fn main() { fn main() {
@ -20,4 +23,7 @@ fn main() {
let in_call = Box::new(f32::clamp); let in_call = Box::new(f32::clamp);
let in_method_call = ["^", "$"].into_iter().map(Regex::new); 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::<()>()]);
} }

View File

@ -1,5 +1,5 @@
error: use of a disallowed method `regex::Regex::new` 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(); 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` = note: `-D clippy::disallowed-methods` implied by `-D warnings`
error: use of a disallowed method `regex::Regex::is_match` 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"); LL | re.is_match("abc");
| ^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^
@ -15,40 +15,46 @@ LL | re.is_match("abc");
= note: no matching allowed (from clippy.toml) = note: no matching allowed (from clippy.toml)
error: use of a disallowed method `std::iter::Iterator::sum` 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::<i32>(); LL | a.iter().sum::<i32>();
| ^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^
error: use of a disallowed method `slice::sort_unstable` 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(); LL | a.sort_unstable();
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
error: use of a disallowed method `f32::clamp` 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); LL | let _ = 2.0f32.clamp(3.0f32, 4.0f32);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: use of a disallowed method `regex::Regex::new` 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, regex::Error> = Regex::new; LL | let indirect: fn(&str) -> Result<Regex, regex::Error> = Regex::new;
| ^^^^^^^^^^ | ^^^^^^^^^^
error: use of a disallowed method `f32::clamp` 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); LL | let in_call = Box::new(f32::clamp);
| ^^^^^^^^^^ | ^^^^^^^^^^
error: use of a disallowed method `regex::Regex::new` 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); 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

View File

@ -11,6 +11,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
cargo-ignore-publish cargo-ignore-publish
cognitive-complexity-threshold cognitive-complexity-threshold
cyclomatic-complexity-threshold cyclomatic-complexity-threshold
disallowed-macros
disallowed-methods disallowed-methods
disallowed-names disallowed-names
disallowed-types disallowed-types