Add new disallowed_fields lint

This commit is contained in:
Guillaume Gomez
2025-12-11 21:08:01 +01:00
parent bddcea71d0
commit 72ff51ac77
11 changed files with 293 additions and 0 deletions
+2
View File
@@ -6374,6 +6374,7 @@ Released 2018-09-13
[`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
[`derived_hash_with_manual_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derived_hash_with_manual_eq
[`disallowed_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_fields
[`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
@@ -7190,6 +7191,7 @@ Released 2018-09-13
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
[`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold
[`const-literal-digits-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#const-literal-digits-threshold
[`disallowed-fields`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-fields
[`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros
[`disallowed-methods`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods
[`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names
+17
View File
@@ -505,6 +505,23 @@ The minimum digits a const float literal must have to supress the `excessive_pre
* [`excessive_precision`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision)
## `disallowed-fields`
The list of disallowed fields, written as fully qualified paths.
**Fields:**
- `path` (required): the fully qualified path to the field that should be disallowed
- `reason` (optional): explanation why this field is disallowed
- `replacement` (optional): suggested alternative method
- `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
if the path doesn't exist, instead of emitting an error
**Default Value:** `[]`
---
**Affected lints:**
* [`disallowed_fields`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_fields)
## `disallowed-macros`
The list of disallowed macros, written as fully qualified paths.
+11
View File
@@ -581,6 +581,17 @@ fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
/// Use the Cognitive Complexity lint instead.
#[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
cyclomatic_complexity_threshold: u64 = 25,
/// The list of disallowed fields, written as fully qualified paths.
///
/// **Fields:**
/// - `path` (required): the fully qualified path to the field that should be disallowed
/// - `reason` (optional): explanation why this field is disallowed
/// - `replacement` (optional): suggested alternative method
/// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
/// if the path doesn't exist, instead of emitting an error
#[disallowed_paths_allow_replacements = true]
#[lints(disallowed_fields)]
disallowed_fields: Vec<DisallowedPath> = Vec::new(),
/// The list of disallowed macros, written as fully qualified paths.
///
/// **Fields:**
+1
View File
@@ -105,6 +105,7 @@
crate::derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ_INFO,
crate::derive::EXPL_IMPL_CLONE_ON_COPY_INFO,
crate::derive::UNSAFE_DERIVE_DESERIALIZE_INFO,
crate::disallowed_fields::DISALLOWED_FIELDS_INFO,
crate::disallowed_macros::DISALLOWED_MACROS_INFO,
crate::disallowed_methods::DISALLOWED_METHODS_INFO,
crate::disallowed_names::DISALLOWED_NAMES_INFO,
+139
View File
@@ -0,0 +1,139 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::paths::PathNS;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, TyCtxt};
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Denies the configured fields in clippy.toml
///
/// Note: Even though this lint is warn-by-default, it will only trigger if
/// fields are defined in the clippy.toml file.
///
/// ### Why is this bad?
/// Some fields 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-fields = [
/// # Can use a string as the path of the disallowed field.
/// "std::ops::Range::start",
/// # Can also use an inline table with a `path` key.
/// { path = "std::ops::Range::start" },
/// # When using an inline table, can add a `reason` for why the field
/// # is disallowed.
/// { path = "std::ops::Range::start", reason = "The start of the range is not used" },
/// ]
/// ```
///
/// ```rust
/// use std::ops::Range;
///
/// let range = Range { start: 0, end: 1 };
/// println!("{}", range.start); // `start` is disallowed in the config.
/// ```
///
/// Use instead:
/// ```rust
/// use std::ops::Range;
///
/// let range = Range { start: 0, end: 1 };
/// println!("{}", range.end); // `end` is _not_ disallowed in the config.
/// ```
#[clippy::version = "1.93.0"]
pub DISALLOWED_FIELDS,
style,
"declaration of a disallowed field use"
}
pub struct DisallowedFields {
disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>,
}
impl DisallowedFields {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_fields,
PathNS::Field,
|def_kind| matches!(def_kind, DefKind::Field),
"field",
false,
);
Self { disallowed }
}
}
impl_lint_pass!(DisallowedFields => [DISALLOWED_FIELDS]);
impl<'tcx> LateLintPass<'tcx> for DisallowedFields {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (id, span) = match &expr.kind {
ExprKind::Path(path) if let Res::Def(_, id) = cx.qpath_res(path, expr.hir_id) => (id, expr.span),
ExprKind::Field(e, ident) => {
// Very round-about way to get the field `DefId` from the expr: first we get its
// parent `Ty`. Then we go through all its fields to find the one with the expected
// name and get the `DefId` from it.
if let Some(parent_ty) = cx.typeck_results().expr_ty_adjusted_opt(e)
&& let ty::Adt(adt_def, ..) = parent_ty.kind()
&& let Some(field_def_id) = adt_def.all_fields().find_map(|field| {
if field.name == ident.name {
Some(field.did)
} else {
None
}
})
{
(field_def_id, ident.span)
} else {
return;
}
},
_ => return,
};
if let Some(&(path, disallowed_path)) = self.disallowed.get(&id) {
span_lint_and_then(
cx,
DISALLOWED_FIELDS,
span,
format!("use of a disallowed field `{path}`"),
disallowed_path.diag_amendment(span),
);
}
}
fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
if let PatKind::Struct(struct_path, pat_fields, _) = pat.kind
&& let Res::Def(DefKind::Struct, struct_def_id) = cx.typeck_results().qpath_res(&struct_path, pat.hir_id)
{
let adt_def = cx.tcx.adt_def(struct_def_id);
for field in pat_fields {
if let Some(def_id) = adt_def.all_fields().find_map(|adt_field| {
if field.ident.name == adt_field.name {
Some(adt_field.did)
} else {
None
}
}) && let Some(&(path, disallowed_path)) = self.disallowed.get(&def_id)
{
span_lint_and_then(
cx,
DISALLOWED_FIELDS,
field.span,
format!("use of a disallowed field `{path}`"),
disallowed_path.diag_amendment(field.span),
);
}
}
}
}
}
+2
View File
@@ -104,6 +104,7 @@
mod dereference;
mod derivable_impls;
mod derive;
mod disallowed_fields;
mod disallowed_macros;
mod disallowed_methods;
mod disallowed_names;
@@ -855,6 +856,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)),
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
Box::new(move |tcx| Box::new(disallowed_fields::DisallowedFields::new(tcx, conf))),
Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))),
Box::new(|_| Box::new(same_length_and_capacity::SameLengthAndCapacity)),
// add late passes here, used by `cargo dev new_lint`
+12
View File
@@ -318,6 +318,13 @@ fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, n
.filter_by_name_unhygienic(name)
.find(|assoc_item| ns.matches(Some(assoc_item.namespace())))
.map(|assoc_item| assoc_item.def_id),
ItemKind::Struct(_, _, rustc_hir::VariantData::Struct { fields, .. }) => fields.iter().find_map(|field| {
if field.ident.name == name {
Some(field.def_id.to_def_id())
} else {
None
}
}),
_ => None,
}
}
@@ -336,6 +343,11 @@ fn non_local_item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, ns: PathNS, name
.iter()
.copied()
.find(|assoc_def_id| tcx.item_name(*assoc_def_id) == name && ns.matches(tcx.def_kind(assoc_def_id).ns())),
DefKind::Struct => tcx
.associated_item_def_ids(def_id)
.iter()
.copied()
.find(|assoc_def_id| tcx.item_name(*assoc_def_id) == name),
_ => None,
}
}
@@ -0,0 +1,12 @@
disallowed-fields = [
# just a string is shorthand for path only
"std::ops::Range::start",
# can give path and reason with an inline table
{ path = "std::ops::Range::end", reason = "no end allowed" },
# can use an inline table but omit reason
{ path = "std::ops::RangeTo::end" },
# local paths
"conf_disallowed_fields::X::y",
# re-exports
"conf_disallowed_fields::Y::y",
]
@@ -0,0 +1,39 @@
#![warn(clippy::disallowed_fields)]
#![allow(clippy::match_single_binding)]
use std::ops::{Range, RangeTo};
struct X {
y: u32,
}
use crate::X as Y;
fn b(X { y }: X) {}
//~^ disallowed_fields
fn main() {
let x = X { y: 0 };
let _ = x.y;
//~^ disallowed_fields
let x = Y { y: 0 };
let _ = x.y;
//~^ disallowed_fields
let x = Range { start: 0, end: 0 };
let _ = x.start;
//~^ disallowed_fields
let _ = x.end;
//~^ disallowed_fields
let Range { start, .. } = x;
//~^ disallowed_fields
let x = RangeTo { end: 0 };
let _ = x.end;
//~^ disallowed_fields
match x {
RangeTo { end } => {}, //~ disallowed_fields
}
}
@@ -0,0 +1,55 @@
error: use of a disallowed field `conf_disallowed_fields::Y::y`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:12:10
|
LL | fn b(X { y }: X) {}
| ^
|
= note: `-D clippy::disallowed-fields` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::disallowed_fields)]`
error: use of a disallowed field `conf_disallowed_fields::Y::y`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:17:15
|
LL | let _ = x.y;
| ^
error: use of a disallowed field `conf_disallowed_fields::Y::y`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:21:15
|
LL | let _ = x.y;
| ^
error: use of a disallowed field `std::ops::Range::start`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:25:15
|
LL | let _ = x.start;
| ^^^^^
error: use of a disallowed field `std::ops::Range::end`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:27:15
|
LL | let _ = x.end;
| ^^^
|
= note: no end allowed
error: use of a disallowed field `std::ops::Range::start`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:29:17
|
LL | let Range { start, .. } = x;
| ^^^^^
error: use of a disallowed field `std::ops::RangeTo::end`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:33:15
|
LL | let _ = x.end;
| ^^^
error: use of a disallowed field `std::ops::RangeTo::end`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:37:19
|
LL | RangeTo { end } => {},
| ^^^
error: aborting due to 8 previous errors
@@ -37,6 +37,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
check-private-items
cognitive-complexity-threshold
const-literal-digits-threshold
disallowed-fields
disallowed-macros
disallowed-methods
disallowed-names
@@ -136,6 +137,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
check-private-items
cognitive-complexity-threshold
const-literal-digits-threshold
disallowed-fields
disallowed-macros
disallowed-methods
disallowed-names
@@ -235,6 +237,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
check-private-items
cognitive-complexity-threshold
const-literal-digits-threshold
disallowed-fields
disallowed-macros
disallowed-methods
disallowed-names