Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -7189,6 +7190,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
Expand Down
17 changes: 17 additions & 0 deletions book/src/lint_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
11 changes: 11 additions & 0 deletions clippy_config/src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,17 @@ define_Conf! {
/// 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:**
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
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,
Expand Down
113 changes: 113 additions & 0 deletions clippy_lints/src/disallowed_fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{Adt, TyCtxt};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We don't normally import TyKind variants and use ty::Adt instead.

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::Value,
|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_opt(e)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not sure this is the best way to get the DefId from Expr::Field but couldn't find another way. I'd love to know if there is one!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to use the adjusted type.

&& let 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
}
})
Comment on lines +83 to +94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's get_field_by_name in clippy_utils for this.

{
(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),
);
}
}
}
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ mod default_union_representation;
mod dereference;
mod derivable_impls;
mod derive;
mod disallowed_fields;
mod disallowed_macros;
mod disallowed_methods;
mod disallowed_names;
Expand Down Expand Up @@ -851,6 +852,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))),
// add late passes here, used by `cargo dev new_lint`
];
Expand Down
12 changes: 12 additions & 0 deletions clippy_utils/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically added support to look for fields in lookup_path (above in this file). it's used to check if the paths are valid in the config file.

So to link to a field (for example with struct X { y: u8 }: X::y.

Copy link
Contributor

@Jarcho Jarcho Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to cause a problem. Fields and associated items can have the same name. You can add a Field variant to PathNS and constrain matches to that.

if field.ident.name == name {
Some(field.def_id.to_def_id())
} else {
None
}
}),
_ => None,
}
}
Expand All @@ -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,
}
}
12 changes: 12 additions & 0 deletions tests/ui-toml/toml_disallowed_fields/clippy.toml
Original file line number Diff line number Diff line change
@@ -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",
]
29 changes: 29 additions & 0 deletions tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#![warn(clippy::disallowed_fields)]

use std::ops::{Range, RangeTo};

struct X {
y: u32,
}

use crate::X as Y;

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 x = RangeTo { end: 0 };
let _ = x.end;
//~^ disallowed_fields
}
37 changes: 37 additions & 0 deletions tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
error: use of a disallowed field `conf_disallowed_fields::Y::y`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:13:15
|
LL | let _ = x.y;
| ^
|
= 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 `std::ops::Range::start`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:21: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:23:15
|
LL | let _ = x.end;
| ^^^
|
= note: no end allowed

error: use of a disallowed field `std::ops::RangeTo::end`
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:27:15
|
LL | let _ = x.end;
| ^^^

error: aborting due to 5 previous errors

3 changes: 3 additions & 0 deletions tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down