use clippy_config::types::PubUnderscoreFieldsBehaviour; use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::is_path_lang_item; use rustc_hir::{FieldDef, Item, ItemKind, LangItem}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks whether any field of the struct is prefixed with an `_` (underscore) and also marked /// `pub` (public) /// /// ### Why is this bad? /// Fields prefixed with an `_` are inferred as unused, which suggests it should not be marked /// as `pub`, because marking it as `pub` infers it will be used. /// /// ### Example /// ```rust /// struct FileHandle { /// pub _descriptor: usize, /// } /// ``` /// Use instead: /// ```rust /// struct FileHandle { /// _descriptor: usize, /// } /// ``` /// /// OR /// /// ```rust /// struct FileHandle { /// pub descriptor: usize, /// } /// ``` #[clippy::version = "1.77.0"] pub PUB_UNDERSCORE_FIELDS, pedantic, "struct field prefixed with underscore and marked public" } pub struct PubUnderscoreFields { pub behavior: PubUnderscoreFieldsBehaviour, } impl_lint_pass!(PubUnderscoreFields => [PUB_UNDERSCORE_FIELDS]); impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { // This lint only pertains to structs. let ItemKind::Struct(variant_data, _) = &item.kind else { return; }; let is_visible = |field: &FieldDef<'_>| match self.behavior { PubUnderscoreFieldsBehaviour::PubliclyExported => cx.effective_visibilities.is_reachable(field.def_id), PubUnderscoreFieldsBehaviour::AllPubFields => { // If there is a visibility span then the field is marked pub in some way. !field.vis_span.is_empty() }, }; for field in variant_data.fields() { // Only pertains to fields that start with an underscore, and are public. if field.ident.as_str().starts_with('_') && is_visible(field) // We ignore fields that have `#[doc(hidden)]`. && !is_doc_hidden(cx.tcx.hir().attrs(field.hir_id)) // We ignore fields that are `PhantomData`. && !is_path_lang_item(cx, field.ty, LangItem::PhantomData) { span_lint_hir_and_then( cx, PUB_UNDERSCORE_FIELDS, field.hir_id, field.vis_span.to(field.ident.span), "field marked as public but also inferred as unused because it's prefixed with `_`", |diag| { diag.help("consider removing the underscore, or making the field private"); }, ); } } } }