Add disallowed_macros lint

This commit is contained in:
Alex Macleod 2022-10-05 13:44:01 +00:00
parent 425e1ea73d
commit 86c86c3742
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_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

View file

@ -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<DisallowedType>,
def_ids: FxHashMap<DefId, DisallowedType>,
conf_invalid_types: Vec<DisallowedPath>,
def_ids: FxHashMap<DefId, DisallowedPath>,
}
impl AwaitHolding {
pub(crate) fn new(conf_invalid_types: Vec<DisallowedType>) -> Self {
pub(crate) fn new(conf_invalid_types: Vec<DisallowedPath>) -> 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);
}
},
);

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::{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::DisallowedMethod>,
conf_disallowed: Vec<conf::DisallowedPath>,
disallowed: DefIdMap<usize>,
}
impl DisallowedMethods {
pub fn new(conf_disallowed: Vec<conf::DisallowedMethod>) -> Self {
pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> 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)"));
}
});

View file

@ -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::DisallowedType>,
conf_disallowed: Vec<conf::DisallowedPath>,
def_ids: FxHashMap<DefId, Option<String>>,
prim_tys: FxHashMap<PrimTy, Option<String>>,
}
impl DisallowedTypes {
pub fn new(conf_disallowed: Vec<conf::DisallowedType>) -> Self {
pub fn new(conf_disallowed: Vec<conf::DisallowedPath>) -> 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);
},

View file

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

View file

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

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_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),

View file

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

View file

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

View file

@ -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<String> },
}
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<String> },
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<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: DISALLOWED_METHODS.
///
/// 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.
///
/// 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.
///
/// 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<crate::utils::conf::DisallowedType> = Vec::new()),
(await_holding_invalid_types: Vec<crate::utils::conf::DisallowedPath> = Vec::new()),
/// Lint: LARGE_INCLUDE_FILE.
///
/// 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_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;

View file

@ -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,33 +522,7 @@ 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`.
/// 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 {
fn find_primitive<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx {
let single = |ty| tcx.incoherent_impls(ty).iter().copied();
let empty = || [].iter().copied();
match name {
@ -578,7 +552,37 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
"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], 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> {
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<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),
_ => None,
}

View file

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

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

View file

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

View file

@ -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::<i32>();
| ^^^^^^^^^^^^^^^^^^^^^
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, regex::Error> = 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

View file

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