Skip to content

Commit d017b2c

Browse files
Add new disallowed_fields lint
1 parent 961ea1c commit d017b2c

File tree

11 files changed

+239
-0
lines changed

11 files changed

+239
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6374,6 +6374,7 @@ Released 2018-09-13
63746374
[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
63756375
[`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq
63766376
[`derived_hash_with_manual_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derived_hash_with_manual_eq
6377+
[`disallowed_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_fields
63776378
[`disallowed_macros`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros
63786379
[`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method
63796380
[`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
@@ -7189,6 +7190,7 @@ Released 2018-09-13
71897190
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
71907191
[`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold
71917192
[`const-literal-digits-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#const-literal-digits-threshold
7193+
[`disallowed-fields`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-fields
71927194
[`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros
71937195
[`disallowed-methods`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods
71947196
[`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names

book/src/lint_configuration.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,23 @@ The minimum digits a const float literal must have to supress the `excessive_pre
505505
* [`excessive_precision`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision)
506506

507507

508+
## `disallowed-fields`
509+
The list of disallowed fields, written as fully qualified paths.
510+
511+
**Fields:**
512+
- `path` (required): the fully qualified path to the field that should be disallowed
513+
- `reason` (optional): explanation why this field is disallowed
514+
- `replacement` (optional): suggested alternative method
515+
- `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
516+
if the path doesn't exist, instead of emitting an error
517+
518+
**Default Value:** `[]`
519+
520+
---
521+
**Affected lints:**
522+
* [`disallowed_fields`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_fields)
523+
524+
508525
## `disallowed-macros`
509526
The list of disallowed macros, written as fully qualified paths.
510527

clippy_config/src/conf.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,17 @@ define_Conf! {
581581
/// Use the Cognitive Complexity lint instead.
582582
#[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
583583
cyclomatic_complexity_threshold: u64 = 25,
584+
/// The list of disallowed fields, written as fully qualified paths.
585+
///
586+
/// **Fields:**
587+
/// - `path` (required): the fully qualified path to the field that should be disallowed
588+
/// - `reason` (optional): explanation why this field is disallowed
589+
/// - `replacement` (optional): suggested alternative method
590+
/// - `allow-invalid` (optional, `false` by default): when set to `true`, it will ignore this entry
591+
/// if the path doesn't exist, instead of emitting an error
592+
#[disallowed_paths_allow_replacements = true]
593+
#[lints(disallowed_fields)]
594+
disallowed_fields: Vec<DisallowedPath> = Vec::new(),
584595
/// The list of disallowed macros, written as fully qualified paths.
585596
///
586597
/// **Fields:**

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
105105
crate::derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ_INFO,
106106
crate::derive::EXPL_IMPL_CLONE_ON_COPY_INFO,
107107
crate::derive::UNSAFE_DERIVE_DESERIALIZE_INFO,
108+
crate::disallowed_fields::DISALLOWED_FIELDS_INFO,
108109
crate::disallowed_macros::DISALLOWED_MACROS_INFO,
109110
crate::disallowed_methods::DISALLOWED_METHODS_INFO,
110111
crate::disallowed_names::DISALLOWED_NAMES_INFO,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use clippy_config::Conf;
2+
use clippy_config::types::{DisallowedPath, create_disallowed_map};
3+
use clippy_utils::diagnostics::span_lint_and_then;
4+
use clippy_utils::paths::PathNS;
5+
use rustc_hir::def::{DefKind, Res};
6+
use rustc_hir::def_id::DefIdMap;
7+
use rustc_hir::{Expr, ExprKind};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::ty::{Adt, TyCtxt};
10+
use rustc_session::impl_lint_pass;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Denies the configured fields in clippy.toml
15+
///
16+
/// Note: Even though this lint is warn-by-default, it will only trigger if
17+
/// fields are defined in the clippy.toml file.
18+
///
19+
/// ### Why is this bad?
20+
/// Some fields are undesirable in certain contexts, and it's beneficial to
21+
/// lint for them as needed.
22+
///
23+
/// ### Example
24+
/// An example clippy.toml configuration:
25+
/// ```toml
26+
/// # clippy.toml
27+
/// disallowed-fields = [
28+
/// # Can use a string as the path of the disallowed field.
29+
/// "std::ops::Range::start",
30+
/// # Can also use an inline table with a `path` key.
31+
/// { path = "std::ops::Range::start" },
32+
/// # When using an inline table, can add a `reason` for why the field
33+
/// # is disallowed.
34+
/// { path = "std::ops::Range::start", reason = "The start of the range is not used" },
35+
/// ]
36+
/// ```
37+
///
38+
/// ```rust
39+
/// use std::ops::Range;
40+
///
41+
/// let range = Range { start: 0, end: 1 };
42+
/// println!("{}", range.start); // `start` is disallowed in the config.
43+
/// ```
44+
///
45+
/// Use instead:
46+
/// ```rust
47+
/// use std::ops::Range;
48+
///
49+
/// let range = Range { start: 0, end: 1 };
50+
/// println!("{}", range.end); // `end` is _not_ disallowed in the config.
51+
/// ```
52+
#[clippy::version = "1.93.0"]
53+
pub DISALLOWED_FIELDS,
54+
style,
55+
"declaration of a disallowed field use"
56+
}
57+
58+
pub struct DisallowedFields {
59+
disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>,
60+
}
61+
62+
impl DisallowedFields {
63+
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
64+
let (disallowed, _) = create_disallowed_map(
65+
tcx,
66+
&conf.disallowed_fields,
67+
PathNS::Value,
68+
|def_kind| matches!(def_kind, DefKind::Field),
69+
"field",
70+
false,
71+
);
72+
Self { disallowed }
73+
}
74+
}
75+
76+
impl_lint_pass!(DisallowedFields => [DISALLOWED_FIELDS]);
77+
78+
impl<'tcx> LateLintPass<'tcx> for DisallowedFields {
79+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
80+
let (id, span) = match &expr.kind {
81+
ExprKind::Path(path) if let Res::Def(_, id) = cx.qpath_res(path, expr.hir_id) => (id, expr.span),
82+
ExprKind::Field(e, ident) => {
83+
// Very round-about way to get the field `DefId` from the expr: first we get its
84+
// parent `Ty`. Then we go through all its fields to find the one with the expected
85+
// name and get the `DefId` from it.
86+
if let Some(parent_ty) = cx.typeck_results().expr_ty_opt(e)
87+
&& let Adt(adt_def, ..) = parent_ty.kind()
88+
&& let Some(field_def_id) = adt_def.all_fields().find_map(|field| {
89+
if field.name == ident.name {
90+
Some(field.did)
91+
} else {
92+
None
93+
}
94+
})
95+
{
96+
(field_def_id, ident.span)
97+
} else {
98+
return;
99+
}
100+
},
101+
_ => return,
102+
};
103+
if let Some(&(path, disallowed_path)) = self.disallowed.get(&id) {
104+
span_lint_and_then(
105+
cx,
106+
DISALLOWED_FIELDS,
107+
span,
108+
format!("use of a disallowed field `{path}`"),
109+
disallowed_path.diag_amendment(span),
110+
);
111+
}
112+
}
113+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ mod default_union_representation;
104104
mod dereference;
105105
mod derivable_impls;
106106
mod derive;
107+
mod disallowed_fields;
107108
mod disallowed_macros;
108109
mod disallowed_methods;
109110
mod disallowed_names;
@@ -851,6 +852,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
851852
Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)),
852853
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
853854
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
855+
Box::new(move |tcx| Box::new(disallowed_fields::DisallowedFields::new(tcx, conf))),
854856
Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))),
855857
// add late passes here, used by `cargo dev new_lint`
856858
];

clippy_utils/src/paths.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,13 @@ fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, n
318318
.filter_by_name_unhygienic(name)
319319
.find(|assoc_item| ns.matches(Some(assoc_item.namespace())))
320320
.map(|assoc_item| assoc_item.def_id),
321+
ItemKind::Struct(_, _, rustc_hir::VariantData::Struct { fields, .. }) => fields.iter().find_map(|field| {
322+
if field.ident.name == name {
323+
Some(field.def_id.to_def_id())
324+
} else {
325+
None
326+
}
327+
}),
321328
_ => None,
322329
}
323330
}
@@ -336,6 +343,11 @@ fn non_local_item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, ns: PathNS, name
336343
.iter()
337344
.copied()
338345
.find(|assoc_def_id| tcx.item_name(*assoc_def_id) == name && ns.matches(tcx.def_kind(assoc_def_id).ns())),
346+
DefKind::Struct => tcx
347+
.associated_item_def_ids(def_id)
348+
.iter()
349+
.copied()
350+
.find(|assoc_def_id| tcx.item_name(*assoc_def_id) == name),
339351
_ => None,
340352
}
341353
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
disallowed-fields = [
2+
# just a string is shorthand for path only
3+
"std::ops::Range::start",
4+
# can give path and reason with an inline table
5+
{ path = "std::ops::Range::end", reason = "no end allowed" },
6+
# can use an inline table but omit reason
7+
{ path = "std::ops::RangeTo::end" },
8+
# local paths
9+
"conf_disallowed_fields::X::y",
10+
# re-exports
11+
"conf_disallowed_fields::Y::y",
12+
]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![warn(clippy::disallowed_fields)]
2+
3+
use std::ops::{Range, RangeTo};
4+
5+
struct X {
6+
y: u32,
7+
}
8+
9+
use crate::X as Y;
10+
11+
fn main() {
12+
let x = X { y: 0 };
13+
let _ = x.y;
14+
//~^ disallowed_fields
15+
16+
let x = Y { y: 0 };
17+
let _ = x.y;
18+
//~^ disallowed_fields
19+
20+
let x = Range { start: 0, end: 0 };
21+
let _ = x.start;
22+
//~^ disallowed_fields
23+
let _ = x.end;
24+
//~^ disallowed_fields
25+
26+
let x = RangeTo { end: 0 };
27+
let _ = x.end;
28+
//~^ disallowed_fields
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
error: use of a disallowed field `conf_disallowed_fields::Y::y`
2+
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:13:15
3+
|
4+
LL | let _ = x.y;
5+
| ^
6+
|
7+
= note: `-D clippy::disallowed-fields` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::disallowed_fields)]`
9+
10+
error: use of a disallowed field `conf_disallowed_fields::Y::y`
11+
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:17:15
12+
|
13+
LL | let _ = x.y;
14+
| ^
15+
16+
error: use of a disallowed field `std::ops::Range::start`
17+
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:21:15
18+
|
19+
LL | let _ = x.start;
20+
| ^^^^^
21+
22+
error: use of a disallowed field `std::ops::Range::end`
23+
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:23:15
24+
|
25+
LL | let _ = x.end;
26+
| ^^^
27+
|
28+
= note: no end allowed
29+
30+
error: use of a disallowed field `std::ops::RangeTo::end`
31+
--> tests/ui-toml/toml_disallowed_fields/conf_disallowed_fields.rs:27:15
32+
|
33+
LL | let _ = x.end;
34+
| ^^^
35+
36+
error: aborting due to 5 previous errors
37+

0 commit comments

Comments
 (0)