-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add new disallowed_fields lint
#16218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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}; | ||
| 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) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's |
||
| { | ||
| (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), | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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| { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically added support to look for fields in So to link to a field (for example with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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, | ||
| } | ||
| } | ||
| 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", | ||
| ] |
| 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 | ||
| } |
| 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 | ||
|
|
There was a problem hiding this comment.
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
TyKindvariants and usety::Adtinstead.