From f736c1efbe1fd4b7202bacb0255ffeb16894c576 Mon Sep 17 00:00:00 2001 From: Lallapallooza Date: Mon, 29 Sep 2025 15:43:30 +0100 Subject: [PATCH 1/5] Add profile-specific configuration for disallowed methods and types --- CHANGELOG.md | 2 + book/src/lint_configuration.md | 43 +++++ clippy_config/src/conf.rs | 178 +++++++++++++++++- clippy_lints/src/disallowed_methods.rs | 124 +++++++++++- clippy_lints/src/disallowed_types.rs | 168 ++++++++++++++--- clippy_utils/src/attrs.rs | 2 + clippy_utils/src/disallowed_profiles.rs | 167 ++++++++++++++++ clippy_utils/src/lib.rs | 1 + clippy_utils/src/sym.rs | 2 + .../disallowed_profiles_methods/clippy.toml | 13 ++ .../disallowed_profiles_methods/main.rs | 51 +++++ .../disallowed_profiles_methods/main.stderr | 57 ++++++ .../disallowed_profiles_types/clippy.toml | 13 ++ .../ui-toml/disallowed_profiles_types/main.rs | 43 +++++ .../disallowed_profiles_types/main.stderr | 63 +++++++ .../toml_unknown_key/conf_unknown_key.stderr | 6 + 16 files changed, 899 insertions(+), 34 deletions(-) create mode 100644 clippy_utils/src/disallowed_profiles.rs create mode 100644 tests/ui-toml/disallowed_profiles_methods/clippy.toml create mode 100644 tests/ui-toml/disallowed_profiles_methods/main.rs create mode 100644 tests/ui-toml/disallowed_profiles_methods/main.stderr create mode 100644 tests/ui-toml/disallowed_profiles_types/clippy.toml create mode 100644 tests/ui-toml/disallowed_profiles_types/main.rs create mode 100644 tests/ui-toml/disallowed_profiles_types/main.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f26ee09bbe..8a30a2a5c609 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7578,8 +7578,10 @@ Released 2018-09-13 [`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-methods-profiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods-profiles [`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names [`disallowed-types`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-types +[`disallowed-types-profiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-types-profiles [`doc-valid-idents`]: https://doc.rust-lang.org/clippy/lint_configuration.html#doc-valid-idents [`enable-raw-pointer-heuristic-for-send`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enable-raw-pointer-heuristic-for-send [`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 70e8a9fe4e9f..c3be96374dbc 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -573,6 +573,30 @@ The list of disallowed methods, written as fully qualified paths. * [`disallowed_methods`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods) +## `disallowed-methods-profiles` +Named profiles of disallowed methods, keyed by profile name. + +#### Example + +```toml +[disallowed-methods-profiles.forward_pass] +paths = [ + { path = "crate::io::DeviceBuffer::copy_to_host", reason = "Forward code stays on the device" } +] + +[disallowed-methods-profiles.export] +paths = [ + { path = "crate::io::DeviceBuffer::into_host_slice" } +] +``` + +**Default Value:** `{}` + +--- +**Affected lints:** +* [`disallowed_methods`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods) + + ## `disallowed-names` The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value `".."` can be used as part of the list to indicate that the configured values should be appended to the @@ -602,6 +626,25 @@ The list of disallowed types, written as fully qualified paths. * [`disallowed_types`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types) +## `disallowed-types-profiles` +Named profiles of disallowed types, keyed by profile name. + +#### Example + +```toml +[disallowed-types-profiles.forward_pass] +paths = [ + { path = "crate::io::HostBuffer", reason = "Prefer device buffers" } +] +``` + +**Default Value:** `{}` + +--- +**Affected lints:** +* [`disallowed_types`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types) + + ## `doc-valid-idents` The list of words this lint should not consider as identifiers needing ticks. The value `".."` can be used as part of the list to indicate that the configured values should be appended to the diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 89800addc5c3..f95534546629 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -7,6 +7,7 @@ use crate::types::{ }; use clippy_utils::msrvs::Msrv; use itertools::Itertools; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_session::Session; use rustc_span::edit_distance::edit_distance; @@ -221,12 +222,71 @@ macro_rules! deserialize { }}; } +macro_rules! parse_conf_value { + ( + $map:expr, + $ty:ty, + $errors:expr, + $file:expr, + $field_span:expr, + profile @[$($profile:expr)?], + disallowed @[$($disallowed:expr)?] + ) => { + parse_conf_value_impl!( + $map, + $ty, + $errors, + $file, + $field_span, + ($($profile)?), + ($($disallowed)?) + ) + }; +} + +macro_rules! parse_conf_value_impl { + ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ()) => { + deserialize!($map, $ty, $errors, $file) + }; + ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profile:expr), ()) => { + deserialize_profiles!($map, $errors, $file, $field_span, $profile) + }; + ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ($disallowed:expr)) => { + deserialize!($map, $ty, $errors, $file, $disallowed) + }; + ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profile:expr), ($disallowed:expr)) => { + compile_error!("field cannot specify both disallowed profile table and disallowed path attributes") + }; +} + +macro_rules! deserialize_profiles { + ($map:expr, $errors:expr, $file:expr, $field_span:expr, $replacements_allowed:expr) => {{ + let raw_value = $map.next_value::()?; + let value_span = $field_span.clone(); + let toml::Value::Table(table) = raw_value else { + $errors.push(ConfError::spanned( + $file, + "expected table with named profiles", + None, + value_span.clone(), + )); + continue; + }; + + let map = + parse_disallowed_profiles::<{ $replacements_allowed }>(table, $file, value_span.clone(), &mut $errors); + + (map, value_span) + }}; +} + macro_rules! define_Conf { ($( $(#[doc = $doc:literal])+ $(#[conf_deprecated($dep:literal, $new_conf:ident)])? $(#[default_text = $default_text:expr])? $(#[disallowed_paths_allow_replacements = $replacements_allowed:expr])? + $(#[disallowed_paths_profile(replacements_allowed = $profile_replacements_allowed:expr)])? $(#[lints($($for_lints:ident),* $(,)?)])? $name:ident: $ty:ty = $default:expr, )*) => { @@ -281,10 +341,18 @@ macro_rules! define_Conf { match field { $(Field::$name => { + let _field_span = name.span(); // Is this a deprecated field, i.e., is `$dep` set? If so, push a warning. $(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)? - let (value, value_span) = - deserialize!(map, $ty, errors, self.0 $(, $replacements_allowed)?); + let (value, value_span) = parse_conf_value!( + map, + $ty, + errors, + self.0, + _field_span, + profile @[$($profile_replacements_allowed)?], + disallowed @[$($replacements_allowed)?] + ); // Was this field set previously? if $name.is_some() { errors.push(ConfError::spanned(self.0, format!("duplicate field `{}`", name.get_ref()), None, name.span())); @@ -341,6 +409,81 @@ fn span_from_toml_range(file: &SourceFile, span: Range) -> Span { ) } +fn parse_disallowed_profiles( + table: toml::value::Table, + file: &SourceFile, + value_span: Range, + errors: &mut Vec, +) -> FxHashMap>> { + let mut profiles = FxHashMap::default(); + let config_span = span_from_toml_range(file, value_span.clone()); + + for (profile_name, profile_value) in table { + let toml::Value::Table(mut profile_table) = profile_value else { + errors.push(ConfError::spanned( + file, + format!("invalid profile `{profile_name}`: expected table with `paths` entry"), + None, + value_span.clone(), + )); + continue; + }; + + let Some(paths_value) = profile_table.remove("paths") else { + errors.push(ConfError::spanned( + file, + format!("profile `{profile_name}` missing `paths` entry"), + None, + value_span.clone(), + )); + continue; + }; + + if !profile_table.is_empty() { + let keys = profile_table.keys().map(String::as_str).collect::>().join(", "); + errors.push(ConfError::spanned( + file, + format!("profile `{profile_name}` has unknown keys: {keys}"), + None, + value_span.clone(), + )); + } + + let toml::Value::Array(entries) = paths_value else { + errors.push(ConfError::spanned( + file, + format!("profile `{profile_name}`: `paths` must be an array"), + None, + value_span.clone(), + )); + continue; + }; + + let mut disallowed = Vec::with_capacity(entries.len()); + for entry in entries { + match DisallowedPath::::deserialize(entry.clone()) { + Ok(mut path) => { + path.set_span(config_span); + disallowed.push(path); + }, + Err(err) => errors.push(ConfError::spanned( + file, + format!( + "profile `{profile_name}`: {}", + err.to_string().replace('\n', " ").trim() + ), + None, + value_span.clone(), + )), + } + } + + profiles.insert(profile_name, disallowed); + } + + profiles +} + define_Conf! { /// Which crates to allow absolute paths from #[lints(absolute_paths)] @@ -623,6 +766,24 @@ define_Conf! { #[disallowed_paths_allow_replacements = true] #[lints(disallowed_methods)] disallowed_methods: Vec = Vec::new(), + /// Named profiles of disallowed methods, keyed by profile name. + /// + /// #### Example + /// + /// ```toml + /// [disallowed-methods-profiles.forward_pass] + /// paths = [ + /// { path = "crate::io::DeviceBuffer::copy_to_host", reason = "Forward code stays on the device" } + /// ] + /// + /// [disallowed-methods-profiles.export] + /// paths = [ + /// { path = "crate::io::DeviceBuffer::into_host_slice" } + /// ] + /// ``` + #[disallowed_paths_profile(replacements_allowed = true)] + #[lints(disallowed_methods)] + disallowed_methods_profiles: FxHashMap> = FxHashMap::default(), /// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value /// `".."` can be used as part of the list to indicate that the configured values should be appended to the /// default configuration of Clippy. By default, any configuration will replace the default value. @@ -639,6 +800,19 @@ define_Conf! { #[disallowed_paths_allow_replacements = true] #[lints(disallowed_types)] disallowed_types: Vec = Vec::new(), + /// Named profiles of disallowed types, keyed by profile name. + /// + /// #### Example + /// + /// ```toml + /// [disallowed-types-profiles.forward_pass] + /// paths = [ + /// { path = "crate::io::HostBuffer", reason = "Prefer device buffers" } + /// ] + /// ``` + #[disallowed_paths_profile(replacements_allowed = true)] + #[lints(disallowed_types)] + disallowed_types_profiles: FxHashMap> = FxHashMap::default(), /// The list of words this lint should not consider as identifiers needing ticks. The value /// `".."` can be used as part of the list to indicate that the configured values should be appended to the /// default configuration of Clippy. By default, any configuration will replace the default value. For example: diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index e2fd71b7d990..79a6a5f6adaa 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -1,13 +1,18 @@ use clippy_config::Conf; use clippy_config::types::{DisallowedPath, create_disallowed_map}; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::disallowed_profiles::{ProfileEntry, ProfileResolver}; use clippy_utils::paths::PathNS; +use clippy_utils::sym; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefIdMap; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; +use rustc_span::{Span, Symbol}; +use smallvec::SmallVec; declare_clippy_lint! { /// ### What it does @@ -55,6 +60,21 @@ declare_clippy_lint! { /// let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config. /// xs.push(123); // Vec::push is _not_ disallowed in the config. /// ``` + /// + /// Profiles allow scoping different disallow lists: + /// ```toml + /// [disallowed-methods-profiles.forward_pass] + /// paths = [ + /// { path = "crate::devices::Buffer::copy_to_host", reason = "Forward code must not touch host buffers" } + /// ] + /// ``` + /// + /// ```rust,ignore + /// #[clippy::disallowed_profile("forward_pass")] + /// fn evaluate() { + /// // Method calls in this function use the `forward_pass` profile. + /// } + /// ``` #[clippy::version = "1.49.0"] pub DISALLOWED_METHODS, style, @@ -64,12 +84,17 @@ declare_clippy_lint! { impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); pub struct DisallowedMethods { - disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, + default: DefIdMap<(&'static str, &'static DisallowedPath)>, + profiles: FxHashMap>, + known_profiles: FxHashSet, + profile_cache: ProfileResolver, + warned_unknown_profiles: FxHashSet, } impl DisallowedMethods { + #[allow(rustc::potential_query_instability)] // Profiles are sorted for deterministic iteration. pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { - let (disallowed, _) = create_disallowed_map( + let (default, _) = create_disallowed_map( tcx, &conf.disallowed_methods, PathNS::Value, @@ -82,7 +107,69 @@ impl DisallowedMethods { "function", false, ); - Self { disallowed } + + let mut profiles = FxHashMap::default(); + let mut names: Vec<_> = conf.disallowed_methods_profiles.keys().collect(); + names.sort(); + for name in names { + let symbol = Symbol::intern(name.as_str()); + let paths = conf + .disallowed_methods_profiles + .get(name) + .expect("profile entry must exist"); + let (map, _) = create_disallowed_map( + tcx, + paths, + PathNS::Value, + |def_kind| { + matches!( + def_kind, + DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn + ) + }, + "function", + false, + ); + profiles.insert(symbol, map); + } + + let mut known_profiles = FxHashSet::default(); + for name in conf + .disallowed_methods_profiles + .keys() + .chain(conf.disallowed_types_profiles.keys()) + { + known_profiles.insert(Symbol::intern(name.as_str())); + } + + Self { + default, + profiles, + known_profiles, + profile_cache: ProfileResolver::default(), + warned_unknown_profiles: FxHashSet::default(), + } + } + + fn warn_unknown_profile(&mut self, cx: &LateContext<'_>, entry: &ProfileEntry) { + if self.warned_unknown_profiles.insert(entry.span) { + let attr_name = if entry.attr_name == sym::disallowed_profiles { + "clippy::disallowed_profiles" + } else { + "clippy::disallowed_profile" + }; + cx.tcx + .sess + .dcx() + .struct_span_warn( + entry.span, + format!( + "`{attr_name}` references unknown profile `{}` for `clippy::disallowed_methods`", + entry.name + ), + ) + .emit(); + } } } @@ -98,7 +185,36 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { }, _ => return, }; - if let Some(&(path, disallowed_path)) = self.disallowed.get(&id) { + let mut active_profiles = SmallVec::<[Symbol; 2]>::new(); + let mut unknown_profiles = SmallVec::<[ProfileEntry; 2]>::new(); + if let Some(selection) = self.profile_cache.active_profiles(cx, expr.hir_id) { + for entry in selection.iter() { + let is_active = self.profiles.contains_key(&entry.name); + if is_active { + active_profiles.push(entry.name); + } else if !self.known_profiles.contains(&entry.name) { + unknown_profiles.push(entry.clone()); + } + } + } + + for entry in unknown_profiles { + self.warn_unknown_profile(cx, &entry); + } + + if let Some((profile, &(path, disallowed_path))) = active_profiles.iter().find_map(|symbol| { + self.profiles + .get(symbol) + .and_then(|map| map.get(&id).map(|info| (*symbol, info))) + }) { + span_lint_and_then( + cx, + DISALLOWED_METHODS, + span, + format!("use of a disallowed method `{path}` (profile: {profile})"), + disallowed_path.diag_amendment(span), + ); + } else if let Some(&(path, disallowed_path)) = self.default.get(&id) { span_lint_and_then( cx, DISALLOWED_METHODS, diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index 2c520d053f43..be79b6c7d3db 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -1,15 +1,18 @@ use clippy_config::Conf; use clippy_config::types::{DisallowedPath, create_disallowed_map}; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::disallowed_profiles::{ProfileEntry, ProfileResolver}; use clippy_utils::paths::PathNS; -use rustc_data_structures::fx::FxHashMap; +use clippy_utils::sym; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefIdMap; use rustc_hir::{AmbigArg, Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; +use smallvec::SmallVec; declare_clippy_lint! { /// ### What it does @@ -51,6 +54,19 @@ declare_clippy_lint! { /// // A similar type that is allowed by the config /// use std::collections::HashMap; /// ``` + /// + /// Profiles can scope lists to specific modules: + /// ```toml + /// [disallowed-types-profiles.forward_pass] + /// paths = [ + /// { path = "crate::buffers::HostBuffer", reason = "Prefer device buffers in forward computations" } + /// ] + /// ``` + /// + /// ```rust,ignore + /// #[clippy::disallowed_profile("forward_pass")] + /// fn forward_step(buffer: crate::buffers::DeviceBuffer) { /* ... */ } + /// ``` #[clippy::version = "1.55.0"] pub DISALLOWED_TYPES, style, @@ -59,37 +75,128 @@ declare_clippy_lint! { impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]); -pub struct DisallowedTypes { +struct TypeLookup { def_ids: DefIdMap<(&'static str, &'static DisallowedPath)>, prim_tys: FxHashMap, } +impl TypeLookup { + fn from_config(tcx: TyCtxt<'_>, paths: &'static [DisallowedPath]) -> Self { + let (def_ids, prim_tys) = create_disallowed_map(tcx, paths, PathNS::Type, def_kind_predicate, "type", true); + Self { def_ids, prim_tys } + } + + fn find(&self, res: &Res) -> Option<(&'static str, &'static DisallowedPath)> { + match res { + Res::Def(_, did) => self.def_ids.get(did).copied(), + Res::PrimTy(prim) => self.prim_tys.get(prim).copied(), + _ => None, + } + } +} + +pub struct DisallowedTypes { + default: TypeLookup, + profiles: FxHashMap, + known_profiles: FxHashSet, + profile_cache: ProfileResolver, + warned_unknown_profiles: FxHashSet, +} + impl DisallowedTypes { + #[allow(rustc::potential_query_instability)] // Profiles are sorted for deterministic iteration. pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { - let (def_ids, prim_tys) = create_disallowed_map( - tcx, - &conf.disallowed_types, - PathNS::Type, - def_kind_predicate, - "type", - true, - ); - Self { def_ids, prim_tys } + let default = TypeLookup::from_config(tcx, &conf.disallowed_types); + + let mut profiles = FxHashMap::default(); + let mut names: Vec<_> = conf.disallowed_types_profiles.keys().collect(); + names.sort(); + for name in names { + let symbol = Symbol::intern(name.as_str()); + let paths = conf + .disallowed_types_profiles + .get(name) + .expect("profile entry must exist"); + profiles.insert(symbol, TypeLookup::from_config(tcx, paths)); + } + + let mut known_profiles = FxHashSet::default(); + for name in conf + .disallowed_types_profiles + .keys() + .chain(conf.disallowed_methods_profiles.keys()) + { + known_profiles.insert(Symbol::intern(name.as_str())); + } + + Self { + default, + profiles, + known_profiles, + profile_cache: ProfileResolver::default(), + warned_unknown_profiles: FxHashSet::default(), + } } - fn check_res_emit(&self, cx: &LateContext<'_>, res: &Res, span: Span) { - let (path, disallowed_path) = match res { - Res::Def(_, did) if let Some(&x) = self.def_ids.get(did) => x, - Res::PrimTy(prim) if let Some(&x) = self.prim_tys.get(prim) => x, - _ => return, - }; - span_lint_and_then( - cx, - DISALLOWED_TYPES, - span, - format!("use of a disallowed type `{path}`"), - disallowed_path.diag_amendment(span), - ); + fn warn_unknown_profile(&mut self, cx: &LateContext<'_>, entry: &ProfileEntry) { + if self.warned_unknown_profiles.insert(entry.span) { + let attr_name = if entry.attr_name == sym::disallowed_profiles { + "clippy::disallowed_profiles" + } else { + "clippy::disallowed_profile" + }; + cx.tcx + .sess + .dcx() + .struct_span_warn( + entry.span, + format!( + "`{attr_name}` references unknown profile `{}` for `clippy::disallowed_types`", + entry.name + ), + ) + .emit(); + } + } + + fn check_res_emit(&mut self, cx: &LateContext<'_>, hir_id: rustc_hir::HirId, res: &Res, span: Span) { + let mut active_profiles = SmallVec::<[Symbol; 2]>::new(); + let mut unknown_profiles = SmallVec::<[ProfileEntry; 2]>::new(); + if let Some(selection) = self.profile_cache.active_profiles(cx, hir_id) { + for entry in selection.iter() { + if self.profiles.contains_key(&entry.name) { + active_profiles.push(entry.name); + } else if !self.known_profiles.contains(&entry.name) { + unknown_profiles.push(entry.clone()); + } + } + } + + for entry in unknown_profiles { + self.warn_unknown_profile(cx, &entry); + } + + if let Some((profile, (path, disallowed_path))) = active_profiles.iter().find_map(|symbol| { + self.profiles + .get(symbol) + .and_then(|lookup| lookup.find(res).map(|info| (*symbol, info))) + }) { + span_lint_and_then( + cx, + DISALLOWED_TYPES, + span, + format!("use of a disallowed type `{path}` (profile: {profile})"), + disallowed_path.diag_amendment(span), + ); + } else if let Some((path, disallowed_path)) = self.default.find(res) { + span_lint_and_then( + cx, + DISALLOWED_TYPES, + span, + format!("use of a disallowed type `{path}`"), + disallowed_path.diag_amendment(span), + ); + } } } @@ -111,17 +218,22 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedTypes { if let ItemKind::Use(path, UseKind::Single(_)) = &item.kind && let Some(res) = path.res.type_ns { - self.check_res_emit(cx, &res, item.span); + self.check_res_emit(cx, item.hir_id(), &res, item.span); } } fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx, AmbigArg>) { if let TyKind::Path(path) = &ty.kind { - self.check_res_emit(cx, &cx.qpath_res(path, ty.hir_id), ty.span); + self.check_res_emit(cx, ty.hir_id, &cx.qpath_res(path, ty.hir_id), ty.span); } } fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTraitRef<'tcx>) { - self.check_res_emit(cx, &poly.trait_ref.path.res, poly.trait_ref.path.span); + self.check_res_emit( + cx, + poly.trait_ref.hir_ref_id, + &poly.trait_ref.path.res, + poly.trait_ref.path.span, + ); } } diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index ff9454680d91..8df82489d6a3 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -37,6 +37,8 @@ pub fn check_clippy_attr(sess: &Session, attr: &A) { | sym::version | sym::cognitive_complexity | sym::dump + | sym::disallowed_profile + | sym::disallowed_profiles | sym::msrv | sym::has_significant_drop | sym::format_args => {}, diff --git a/clippy_utils/src/disallowed_profiles.rs b/clippy_utils/src/disallowed_profiles.rs new file mode 100644 index 000000000000..7958aab1423f --- /dev/null +++ b/clippy_utils/src/disallowed_profiles.rs @@ -0,0 +1,167 @@ +use crate::sym; +use rustc_ast::ast::{LitKind, MetaItemInner}; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{Attribute, HirId}; +use rustc_lint::LateContext; +use rustc_span::{Span, Symbol}; +use smallvec::SmallVec; + +#[derive(Clone)] +pub struct ProfileEntry { + pub attr_name: Symbol, + pub name: Symbol, + pub span: Span, +} + +#[derive(Clone)] +pub struct ProfileSelection { + entries: SmallVec<[ProfileEntry; 2]>, +} + +impl ProfileSelection { + pub fn new(entries: SmallVec<[ProfileEntry; 2]>) -> Self { + Self { entries } + } + + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub fn iter(&self) -> impl Iterator { + self.entries.iter() + } +} + +#[derive(Default)] +pub struct ProfileResolver { + cache: FxHashMap>, +} + +impl ProfileResolver { + pub fn active_profiles(&mut self, cx: &LateContext<'_>, hir_id: HirId) -> Option<&ProfileSelection> { + if self.cache.contains_key(&hir_id) { + return self.cache.get(&hir_id).and_then(|selection| selection.as_ref()); + } + + let (resolved, visited) = self.resolve(cx, hir_id); + + for id in visited { + self.cache.entry(id).or_insert_with(|| resolved.clone()); + } + self.cache.insert(hir_id, resolved); + + self.cache.get(&hir_id).and_then(|selection| selection.as_ref()) + } + + fn resolve(&self, cx: &LateContext<'_>, start: HirId) -> (Option, SmallVec<[HirId; 8]>) { + let mut visited = SmallVec::<[HirId; 8]>::new(); + let mut current = Some(start); + + while let Some(id) = current { + if let Some(cached) = self.cache.get(&id) { + return (cached.clone(), visited); + } + + visited.push(id); + + if let Some(selection) = profiles_from_attrs(cx, cx.tcx.hir_attrs(id)) { + return (Some(selection), visited); + } + + if id == rustc_hir::CRATE_HIR_ID { + current = None; + } else { + current = Some(cx.tcx.parent_hir_id(id)); + } + } + + (None, visited) + } +} + +fn profiles_from_attrs(cx: &LateContext<'_>, attrs: &[Attribute]) -> Option { + let mut entries = SmallVec::<[ProfileEntry; 2]>::new(); + + for attr in attrs { + let Some(path) = attr.ident_path() else { continue }; + if path.len() != 2 || path[0].name != sym::clippy { + continue; + } + + let name = path[1].name; + if name != sym::disallowed_profile && name != sym::disallowed_profiles { + continue; + } + + let attr_label = if name == sym::disallowed_profiles { + "`clippy::disallowed_profiles`" + } else { + "`clippy::disallowed_profile`" + }; + + let Some(items) = attr.meta_item_list() else { + cx.tcx + .sess + .dcx() + .struct_span_err(attr.span(), format!("{attr_label} expects string arguments")) + .emit(); + continue; + }; + + if items.is_empty() { + cx.tcx + .sess + .dcx() + .struct_span_err(attr.span(), format!("{attr_label} expects at least one profile name")) + .emit(); + continue; + } + + if name == sym::disallowed_profile && items.len() != 1 { + cx.tcx + .sess + .dcx() + .struct_span_err(attr.span(), "use `clippy::disallowed_profiles` for multiple profiles") + .emit(); + } + + for item in items { + match literal_symbol(&item) { + Some((symbol, span)) => entries.push(ProfileEntry { + attr_name: name, + name: symbol, + span, + }), + None => emit_string_error(cx, &item), + } + } + } + + if entries.is_empty() { + None + } else { + Some(ProfileSelection::new(entries)) + } +} + +fn literal_symbol(item: &MetaItemInner) -> Option<(Symbol, Span)> { + match item { + MetaItemInner::Lit(lit) => { + let LitKind::Str(symbol, _) = lit.kind else { return None }; + Some((symbol, lit.span)) + }, + MetaItemInner::MetaItem(_) => None, + } +} + +fn emit_string_error(cx: &LateContext<'_>, item: &MetaItemInner) { + let span = match item { + MetaItemInner::Lit(lit) => lit.span, + MetaItemInner::MetaItem(meta) => meta.span, + }; + cx.tcx + .sess + .dcx() + .struct_span_err(span, "expected string literal profile name") + .emit(); +} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 1ad12da6e62c..fc80378c18aa 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -46,6 +46,7 @@ mod check_proc_macro; pub mod comparisons; pub mod consts; pub mod diagnostics; +pub mod disallowed_profiles; pub mod eager_or_lazy; pub mod higher; mod hir_utils; diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 2661be807c75..be2ed9b07f0e 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -208,6 +208,8 @@ generate! { deprecated_in_future, deref_mut_method, diagnostics, + disallowed_profile, + disallowed_profiles, disallowed_types, drain, dump, diff --git a/tests/ui-toml/disallowed_profiles_methods/clippy.toml b/tests/ui-toml/disallowed_profiles_methods/clippy.toml new file mode 100644 index 000000000000..52b36099e0ad --- /dev/null +++ b/tests/ui-toml/disallowed_profiles_methods/clippy.toml @@ -0,0 +1,13 @@ +disallowed-methods = [ + { path = "std::mem::drop" } +] + +[disallowed-methods-profiles.forward_pass] +paths = [ + { path = "alloc::vec::Vec::push", reason = "push is forbidden in forward profile" } +] + +[disallowed-methods-profiles.export] +paths = [ + { path = "core::option::Option::unwrap" } +] diff --git a/tests/ui-toml/disallowed_profiles_methods/main.rs b/tests/ui-toml/disallowed_profiles_methods/main.rs new file mode 100644 index 000000000000..b1c04a20ad59 --- /dev/null +++ b/tests/ui-toml/disallowed_profiles_methods/main.rs @@ -0,0 +1,51 @@ +#![warn(clippy::disallowed_methods)] +#![allow( + unused, + clippy::no_effect, + clippy::needless_borrow, + clippy::vec_init_then_push, + clippy::unnecessary_literal_unwrap +)] + +fn default_violation() { + let value = String::from("test"); + std::mem::drop(value); //~ ERROR: use of a disallowed method `std::mem::drop` +} + +#[clippy::disallowed_profile("forward_pass")] +fn forward_profile() { + let mut values = Vec::new(); + values.push(1); //~ ERROR: use of a disallowed method `alloc::vec::Vec::push` (profile: forward_pass) +} + +#[clippy::disallowed_profile("export")] +fn export_profile() { + let value = Some(1); + value.unwrap(); //~ ERROR: use of a disallowed method `core::option::Option::unwrap` (profile: export) +} + +#[clippy::disallowed_profile("unknown_profile")] +//~^ WARN: unknown profile `unknown_profile` for +//~| WARN: unknown profile `unknown_profile` for +fn unknown_profile() { + let mut values = Vec::new(); + values.push(1); + // unknown profile falls back to the default list + std::mem::drop(values); //~ ERROR: use of a disallowed method `std::mem::drop` +} + +#[clippy::disallowed_profiles("forward_pass", "export")] +fn merged_profiles() { + let mut values = Vec::new(); + values.push(1); //~ ERROR: use of a disallowed method `alloc::vec::Vec::push` (profile: forward_pass) + let value = Some(1); + value.unwrap(); //~ ERROR: use of a disallowed method `core::option::Option::unwrap` (profile: export) +} + +fn main() { + default_violation(); + forward_profile(); + export_profile(); + unknown_profile(); + merged_profiles(); +} diff --git a/tests/ui-toml/disallowed_profiles_methods/main.stderr b/tests/ui-toml/disallowed_profiles_methods/main.stderr new file mode 100644 index 000000000000..ee4f4af32069 --- /dev/null +++ b/tests/ui-toml/disallowed_profiles_methods/main.stderr @@ -0,0 +1,57 @@ +error: use of a disallowed method `std::mem::drop` + --> tests/ui-toml/disallowed_profiles_methods/main.rs:12:5 + | +LL | std::mem::drop(value); + | ^^^^^^^^^^^^^^ + | + = note: `-D clippy::disallowed-methods` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` + +error: use of a disallowed method `alloc::vec::Vec::push` (profile: forward_pass) + --> tests/ui-toml/disallowed_profiles_methods/main.rs:18:12 + | +LL | values.push(1); + | ^^^^ + | + = note: push is forbidden in forward profile + +error: use of a disallowed method `core::option::Option::unwrap` (profile: export) + --> tests/ui-toml/disallowed_profiles_methods/main.rs:24:11 + | +LL | value.unwrap(); + | ^^^^^^ + +warning: `clippy::disallowed_profile` references unknown profile `unknown_profile` for `clippy::disallowed_methods` + --> tests/ui-toml/disallowed_profiles_methods/main.rs:27:30 + | +LL | #[clippy::disallowed_profile("unknown_profile")] + | ^^^^^^^^^^^^^^^^^ + +warning: `clippy::disallowed_profile` references unknown profile `unknown_profile` for `clippy::disallowed_types` + --> tests/ui-toml/disallowed_profiles_methods/main.rs:27:30 + | +LL | #[clippy::disallowed_profile("unknown_profile")] + | ^^^^^^^^^^^^^^^^^ + +error: use of a disallowed method `std::mem::drop` + --> tests/ui-toml/disallowed_profiles_methods/main.rs:34:5 + | +LL | std::mem::drop(values); + | ^^^^^^^^^^^^^^ + +error: use of a disallowed method `alloc::vec::Vec::push` (profile: forward_pass) + --> tests/ui-toml/disallowed_profiles_methods/main.rs:40:12 + | +LL | values.push(1); + | ^^^^ + | + = note: push is forbidden in forward profile + +error: use of a disallowed method `core::option::Option::unwrap` (profile: export) + --> tests/ui-toml/disallowed_profiles_methods/main.rs:42:11 + | +LL | value.unwrap(); + | ^^^^^^ + +error: aborting due to 6 previous errors; 2 warnings emitted + diff --git a/tests/ui-toml/disallowed_profiles_types/clippy.toml b/tests/ui-toml/disallowed_profiles_types/clippy.toml new file mode 100644 index 000000000000..f43b04bd6b33 --- /dev/null +++ b/tests/ui-toml/disallowed_profiles_types/clippy.toml @@ -0,0 +1,13 @@ +disallowed-types = [ + { path = "std::rc::Rc" } +] + +[disallowed-types-profiles.forward_pass] +paths = [ + { path = "std::cell::RefCell", reason = "Prefer shared references" } +] + +[disallowed-types-profiles.export] +paths = [ + { path = "std::sync::Mutex" } +] diff --git a/tests/ui-toml/disallowed_profiles_types/main.rs b/tests/ui-toml/disallowed_profiles_types/main.rs new file mode 100644 index 000000000000..9598fe9f8029 --- /dev/null +++ b/tests/ui-toml/disallowed_profiles_types/main.rs @@ -0,0 +1,43 @@ +#![warn(clippy::disallowed_types)] +#![allow(dead_code)] + +use std::rc::Rc; //~ ERROR: use of a disallowed type `std::rc::Rc` +use std::sync::Mutex; + +struct Wrapper; + +fn default_type() { + let _value: Rc = todo!(); //~ ERROR: use of a disallowed type `std::rc::Rc` +} + +#[clippy::disallowed_profile("forward_pass")] +fn forward_profile() { + let _value: std::cell::RefCell = todo!(); //~ ERROR: use of a disallowed type `std::cell::RefCell` (profile: forward_pass) +} + +#[clippy::disallowed_profile("export")] +fn export_profile() { + let _value: Mutex = todo!(); //~ ERROR: use of a disallowed type `std::sync::Mutex` (profile: export) +} + +#[clippy::disallowed_profile("unknown_type_profile")] +//~^ WARN: unknown profile `unknown_type_profile` for +//~| WARN: unknown profile `unknown_type_profile` for +fn unknown_profile() { + let _other = 1u32; + let _fallback: Rc = todo!(); //~ ERROR: use of a disallowed type `std::rc::Rc` +} + +#[clippy::disallowed_profiles("forward_pass", "export")] +fn merged_profiles() { + let _value: std::cell::RefCell = todo!(); //~ ERROR: use of a disallowed type `std::cell::RefCell` (profile: forward_pass) + let _other: Mutex = todo!(); //~ ERROR: use of a disallowed type `std::sync::Mutex` (profile: export) +} + +fn main() { + default_type(); + forward_profile(); + export_profile(); + unknown_profile(); + merged_profiles(); +} diff --git a/tests/ui-toml/disallowed_profiles_types/main.stderr b/tests/ui-toml/disallowed_profiles_types/main.stderr new file mode 100644 index 000000000000..cee93ae8c260 --- /dev/null +++ b/tests/ui-toml/disallowed_profiles_types/main.stderr @@ -0,0 +1,63 @@ +error: use of a disallowed type `std::rc::Rc` + --> tests/ui-toml/disallowed_profiles_types/main.rs:4:1 + | +LL | use std::rc::Rc; + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::disallowed-types` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::disallowed_types)]` + +error: use of a disallowed type `std::rc::Rc` + --> tests/ui-toml/disallowed_profiles_types/main.rs:10:17 + | +LL | let _value: Rc = todo!(); + | ^^^^^^^ + +error: use of a disallowed type `std::cell::RefCell` (profile: forward_pass) + --> tests/ui-toml/disallowed_profiles_types/main.rs:15:17 + | +LL | let _value: std::cell::RefCell = todo!(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: Prefer shared references + +error: use of a disallowed type `std::sync::Mutex` (profile: export) + --> tests/ui-toml/disallowed_profiles_types/main.rs:20:17 + | +LL | let _value: Mutex = todo!(); + | ^^^^^^^^^^ + +warning: `clippy::disallowed_profile` references unknown profile `unknown_type_profile` for `clippy::disallowed_methods` + --> tests/ui-toml/disallowed_profiles_types/main.rs:23:30 + | +LL | #[clippy::disallowed_profile("unknown_type_profile")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +warning: `clippy::disallowed_profile` references unknown profile `unknown_type_profile` for `clippy::disallowed_types` + --> tests/ui-toml/disallowed_profiles_types/main.rs:23:30 + | +LL | #[clippy::disallowed_profile("unknown_type_profile")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: use of a disallowed type `std::rc::Rc` + --> tests/ui-toml/disallowed_profiles_types/main.rs:28:20 + | +LL | let _fallback: Rc = todo!(); + | ^^^^^^^ + +error: use of a disallowed type `std::cell::RefCell` (profile: forward_pass) + --> tests/ui-toml/disallowed_profiles_types/main.rs:33:17 + | +LL | let _value: std::cell::RefCell = todo!(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: Prefer shared references + +error: use of a disallowed type `std::sync::Mutex` (profile: export) + --> tests/ui-toml/disallowed_profiles_types/main.rs:34:17 + | +LL | let _other: Mutex = todo!(); + | ^^^^^^^^^^ + +error: aborting due to 7 previous errors; 2 warnings emitted + diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 6bb3db8db67f..76110ba9f208 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -41,8 +41,10 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect disallowed-fields disallowed-macros disallowed-methods + disallowed-methods-profiles disallowed-names disallowed-types + disallowed-types-profiles doc-valid-idents enable-raw-pointer-heuristic-for-send enforce-iter-loop-reborrow @@ -142,8 +144,10 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect disallowed-fields disallowed-macros disallowed-methods + disallowed-methods-profiles disallowed-names disallowed-types + disallowed-types-profiles doc-valid-idents enable-raw-pointer-heuristic-for-send enforce-iter-loop-reborrow @@ -243,8 +247,10 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni disallowed-fields disallowed-macros disallowed-methods + disallowed-methods-profiles disallowed-names disallowed-types + disallowed-types-profiles doc-valid-idents enable-raw-pointer-heuristic-for-send enforce-iter-loop-reborrow From 286f01efbf6ddab7744e6d10c3328fc5fb0ad0cc Mon Sep 17 00:00:00 2001 From: Lallapallooza Date: Mon, 17 Nov 2025 14:54:06 +0000 Subject: [PATCH 2/5] fix review --- clippy_lints/src/disallowed_methods.rs | 8 +++++--- clippy_lints/src/disallowed_types.rs | 8 +++++--- clippy_utils/src/disallowed_profiles.rs | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 79a6a5f6adaa..639a50231845 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -5,6 +5,7 @@ use clippy_utils::disallowed_profiles::{ProfileEntry, ProfileResolver}; use clippy_utils::paths::PathNS; use clippy_utils::sym; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::smallvec::SmallVec; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefIdMap; use rustc_hir::{Expr, ExprKind}; @@ -12,7 +13,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; -use smallvec::SmallVec; declare_clippy_lint! { /// ### What it does @@ -207,20 +207,22 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { .get(symbol) .and_then(|map| map.get(&id).map(|info| (*symbol, info))) }) { + let diag_amendment = disallowed_path.diag_amendment(span); span_lint_and_then( cx, DISALLOWED_METHODS, span, format!("use of a disallowed method `{path}` (profile: {profile})"), - disallowed_path.diag_amendment(span), + |diag| diag_amendment(diag), ); } else if let Some(&(path, disallowed_path)) = self.default.get(&id) { + let diag_amendment = disallowed_path.diag_amendment(span); span_lint_and_then( cx, DISALLOWED_METHODS, span, format!("use of a disallowed method `{path}`"), - disallowed_path.diag_amendment(span), + |diag| diag_amendment(diag), ); } } diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index be79b6c7d3db..01dddb1fe4cb 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -5,6 +5,7 @@ use clippy_utils::disallowed_profiles::{ProfileEntry, ProfileResolver}; use clippy_utils::paths::PathNS; use clippy_utils::sym; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::smallvec::SmallVec; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefIdMap; use rustc_hir::{AmbigArg, Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind}; @@ -12,7 +13,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; -use smallvec::SmallVec; declare_clippy_lint! { /// ### What it does @@ -181,20 +181,22 @@ impl DisallowedTypes { .get(symbol) .and_then(|lookup| lookup.find(res).map(|info| (*symbol, info))) }) { + let diag_amendment = disallowed_path.diag_amendment(span); span_lint_and_then( cx, DISALLOWED_TYPES, span, format!("use of a disallowed type `{path}` (profile: {profile})"), - disallowed_path.diag_amendment(span), + |diag| diag_amendment(diag), ); } else if let Some((path, disallowed_path)) = self.default.find(res) { + let diag_amendment = disallowed_path.diag_amendment(span); span_lint_and_then( cx, DISALLOWED_TYPES, span, format!("use of a disallowed type `{path}`"), - disallowed_path.diag_amendment(span), + |diag| diag_amendment(diag), ); } } diff --git a/clippy_utils/src/disallowed_profiles.rs b/clippy_utils/src/disallowed_profiles.rs index 7958aab1423f..17b99b136d6d 100644 --- a/clippy_utils/src/disallowed_profiles.rs +++ b/clippy_utils/src/disallowed_profiles.rs @@ -1,10 +1,10 @@ use crate::sym; use rustc_ast::ast::{LitKind, MetaItemInner}; use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::smallvec::SmallVec; use rustc_hir::{Attribute, HirId}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; -use smallvec::SmallVec; #[derive(Clone)] pub struct ProfileEntry { From 74f3823b56d17c8c0a05a1c4797988abf0759022 Mon Sep 17 00:00:00 2001 From: Lallapallooza Date: Sun, 14 Dec 2025 05:12:16 +0000 Subject: [PATCH 3/5] config: add [profiles.*] disallowed lists --- CHANGELOG.md | 3 +- book/src/lint_configuration.md | 65 ++---- clippy_config/src/conf.rs | 207 ++++++++++-------- clippy_config/src/types.rs | 9 + clippy_lints/src/disallowed_methods.rs | 25 +-- clippy_lints/src/disallowed_types.rs | 25 +-- .../disallowed_profiles_methods/clippy.toml | 8 +- .../disallowed_profiles_types/clippy.toml | 8 +- .../toml_unknown_key/conf_unknown_key.stderr | 9 +- 9 files changed, 184 insertions(+), 175 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a30a2a5c609..5527b3e9ee3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7578,10 +7578,8 @@ Released 2018-09-13 [`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-methods-profiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods-profiles [`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names [`disallowed-types`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-types -[`disallowed-types-profiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-types-profiles [`doc-valid-idents`]: https://doc.rust-lang.org/clippy/lint_configuration.html#doc-valid-idents [`enable-raw-pointer-heuristic-for-send`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enable-raw-pointer-heuristic-for-send [`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow @@ -7609,6 +7607,7 @@ Released 2018-09-13 [`module-items-ordered-within-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-items-ordered-within-groupings [`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv [`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit +[`profiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#profiles [`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior [`recursive-self-in-type-definitions`]: https://doc.rust-lang.org/clippy/lint_configuration.html#recursive-self-in-type-definitions [`semicolon-inside-block-ignore-singleline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-inside-block-ignore-singleline diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index c3be96374dbc..c44c137a139b 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -573,30 +573,6 @@ The list of disallowed methods, written as fully qualified paths. * [`disallowed_methods`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods) -## `disallowed-methods-profiles` -Named profiles of disallowed methods, keyed by profile name. - -#### Example - -```toml -[disallowed-methods-profiles.forward_pass] -paths = [ - { path = "crate::io::DeviceBuffer::copy_to_host", reason = "Forward code stays on the device" } -] - -[disallowed-methods-profiles.export] -paths = [ - { path = "crate::io::DeviceBuffer::into_host_slice" } -] -``` - -**Default Value:** `{}` - ---- -**Affected lints:** -* [`disallowed_methods`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods) - - ## `disallowed-names` The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value `".."` can be used as part of the list to indicate that the configured values should be appended to the @@ -626,25 +602,6 @@ The list of disallowed types, written as fully qualified paths. * [`disallowed_types`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types) -## `disallowed-types-profiles` -Named profiles of disallowed types, keyed by profile name. - -#### Example - -```toml -[disallowed-types-profiles.forward_pass] -paths = [ - { path = "crate::io::HostBuffer", reason = "Prefer device buffers" } -] -``` - -**Default Value:** `{}` - ---- -**Affected lints:** -* [`disallowed_types`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types) - - ## `doc-valid-idents` The list of words this lint should not consider as identifiers needing ticks. The value `".."` can be used as part of the list to indicate that the configured values should be appended to the @@ -1029,6 +986,28 @@ The minimum size (in bytes) to consider a type for passing by reference instead * [`large_types_passed_by_value`](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value) +## `profiles` +Named profiles of disallowed items (unrelated to Cargo build profiles). + +#### Example + +```toml +[profiles.persistent] +disallowed-methods = [{ path = "std::env::temp_dir" }] +disallowed-types = [{ path = "std::time::Instant", reason = "use our custom time API" }] + +[profiles.single_threaded] +disallowed-methods = [{ path = "std::thread::spawn" }] +``` + +**Default Value:** `{}` + +--- +**Affected lints:** +* [`disallowed_methods`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods) +* [`disallowed_types`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types) + + ## `pub-underscore-fields-behavior` Lint "public" fields in a struct that are prefixed with an underscore based on their exported visibility, or whether they are marked as "pub". diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index f95534546629..b51aa54aa190 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -1,7 +1,7 @@ use crate::ClippyConfiguration; use crate::types::{ - DisallowedPath, DisallowedPathWithoutReplacement, InherentImplLintScope, MacroMatcher, MatchLintBehaviour, - PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering, SourceItemOrderingCategory, + DisallowedPath, DisallowedPathWithoutReplacement, DisallowedProfile, InherentImplLintScope, MacroMatcher, + MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering, SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds, SourceItemOrderingWithinModuleItemGroupings, }; @@ -229,7 +229,7 @@ macro_rules! parse_conf_value { $errors:expr, $file:expr, $field_span:expr, - profile @[$($profile:expr)?], + profiles @[$($profiles:expr)?], disallowed @[$($disallowed:expr)?] ) => { parse_conf_value_impl!( @@ -238,29 +238,39 @@ macro_rules! parse_conf_value { $errors, $file, $field_span, - ($($profile)?), + ($($profiles)?), ($($disallowed)?) ) }; } macro_rules! parse_conf_value_impl { - ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ()) => { + ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ()) => {{ + let _ = &$field_span; deserialize!($map, $ty, $errors, $file) + }}; + ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profiles:expr), ()) => { + deserialize_profiles!($map, $errors, $file, $field_span) }; - ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profile:expr), ()) => { - deserialize_profiles!($map, $errors, $file, $field_span, $profile) - }; - ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ($disallowed:expr)) => { + ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ($disallowed:expr)) => {{ + let _ = &$field_span; deserialize!($map, $ty, $errors, $file, $disallowed) - }; - ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profile:expr), ($disallowed:expr)) => { - compile_error!("field cannot specify both disallowed profile table and disallowed path attributes") + }}; + ( + $map:expr, + $ty:ty, + $errors:expr, + $file:expr, + $field_span:expr, + ($profiles:expr), + ($disallowed:expr) + ) => { + compile_error!("field cannot specify both profiles and disallowed-paths attributes") }; } macro_rules! deserialize_profiles { - ($map:expr, $errors:expr, $file:expr, $field_span:expr, $replacements_allowed:expr) => {{ + ($map:expr, $errors:expr, $file:expr, $field_span:expr) => {{ let raw_value = $map.next_value::()?; let value_span = $field_span.clone(); let toml::Value::Table(table) = raw_value else { @@ -273,8 +283,7 @@ macro_rules! deserialize_profiles { continue; }; - let map = - parse_disallowed_profiles::<{ $replacements_allowed }>(table, $file, value_span.clone(), &mut $errors); + let map = parse_profiles(table, $file, value_span.clone(), &mut $errors); (map, value_span) }}; @@ -286,7 +295,7 @@ macro_rules! define_Conf { $(#[conf_deprecated($dep:literal, $new_conf:ident)])? $(#[default_text = $default_text:expr])? $(#[disallowed_paths_allow_replacements = $replacements_allowed:expr])? - $(#[disallowed_paths_profile(replacements_allowed = $profile_replacements_allowed:expr)])? + $(#[profiles = $profiles:expr])? $(#[lints($($for_lints:ident),* $(,)?)])? $name:ident: $ty:ty = $default:expr, )*) => { @@ -341,7 +350,7 @@ macro_rules! define_Conf { match field { $(Field::$name => { - let _field_span = name.span(); + let field_span = name.span(); // Is this a deprecated field, i.e., is `$dep` set? If so, push a warning. $(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)? let (value, value_span) = parse_conf_value!( @@ -349,8 +358,10 @@ macro_rules! define_Conf { $ty, errors, self.0, - _field_span, - profile @[$($profile_replacements_allowed)?], + field_span, + // Disallowed-profile table parsing is special-cased to preserve spans for + // diagnostics in disallowed-path entries. + profiles @[$($profiles)?], disallowed @[$($replacements_allowed)?] ); // Was this field set previously? @@ -409,12 +420,12 @@ fn span_from_toml_range(file: &SourceFile, span: Range) -> Span { ) } -fn parse_disallowed_profiles( +fn parse_profiles( table: toml::value::Table, file: &SourceFile, value_span: Range, errors: &mut Vec, -) -> FxHashMap>> { +) -> FxHashMap { let mut profiles = FxHashMap::default(); let config_span = span_from_toml_range(file, value_span.clone()); @@ -422,21 +433,43 @@ fn parse_disallowed_profiles( let toml::Value::Table(mut profile_table) = profile_value else { errors.push(ConfError::spanned( file, - format!("invalid profile `{profile_name}`: expected table with `paths` entry"), + format!("invalid profile `{profile_name}`: expected table"), None, value_span.clone(), )); continue; }; - let Some(paths_value) = profile_table.remove("paths") else { - errors.push(ConfError::spanned( + let disallowed_methods = match profile_table + .remove("disallowed-methods") + .or_else(|| profile_table.remove("disallowed_methods")) + { + Some(value) => parse_profile_list( file, - format!("profile `{profile_name}` missing `paths` entry"), - None, + &profile_name, + "disallowed-methods", + value, value_span.clone(), - )); - continue; + config_span, + errors, + ), + None => Vec::new(), + }; + + let disallowed_types = match profile_table + .remove("disallowed-types") + .or_else(|| profile_table.remove("disallowed_types")) + { + Some(value) => parse_profile_list( + file, + &profile_name, + "disallowed-types", + value, + value_span.clone(), + config_span, + errors, + ), + None => Vec::new(), }; if !profile_table.is_empty() { @@ -449,39 +482,57 @@ fn parse_disallowed_profiles( )); } - let toml::Value::Array(entries) = paths_value else { - errors.push(ConfError::spanned( + profiles.insert( + profile_name, + DisallowedProfile { + disallowed_methods, + disallowed_types, + }, + ); + } + + profiles +} + +fn parse_profile_list( + file: &SourceFile, + profile_name: &str, + key_name: &str, + value: toml::Value, + value_span: Range, + config_span: Span, + errors: &mut Vec, +) -> Vec { + let toml::Value::Array(entries) = value else { + errors.push(ConfError::spanned( + file, + format!("profile `{profile_name}`: `{key_name}` must be an array"), + None, + value_span, + )); + return Vec::new(); + }; + + let mut disallowed = Vec::with_capacity(entries.len()); + for entry in entries { + match DisallowedPath::deserialize(entry.clone()) { + Ok(mut path) => { + path.set_span(config_span); + disallowed.push(path); + }, + Err(err) => errors.push(ConfError::spanned( file, - format!("profile `{profile_name}`: `paths` must be an array"), + format!( + "profile `{profile_name}`: {}", + err.to_string().replace('\n', " ").trim() + ), None, value_span.clone(), - )); - continue; - }; - - let mut disallowed = Vec::with_capacity(entries.len()); - for entry in entries { - match DisallowedPath::::deserialize(entry.clone()) { - Ok(mut path) => { - path.set_span(config_span); - disallowed.push(path); - }, - Err(err) => errors.push(ConfError::spanned( - file, - format!( - "profile `{profile_name}`: {}", - err.to_string().replace('\n', " ").trim() - ), - None, - value_span.clone(), - )), - } + )), } - - profiles.insert(profile_name, disallowed); } - profiles + disallowed } define_Conf! { @@ -766,24 +817,6 @@ define_Conf! { #[disallowed_paths_allow_replacements = true] #[lints(disallowed_methods)] disallowed_methods: Vec = Vec::new(), - /// Named profiles of disallowed methods, keyed by profile name. - /// - /// #### Example - /// - /// ```toml - /// [disallowed-methods-profiles.forward_pass] - /// paths = [ - /// { path = "crate::io::DeviceBuffer::copy_to_host", reason = "Forward code stays on the device" } - /// ] - /// - /// [disallowed-methods-profiles.export] - /// paths = [ - /// { path = "crate::io::DeviceBuffer::into_host_slice" } - /// ] - /// ``` - #[disallowed_paths_profile(replacements_allowed = true)] - #[lints(disallowed_methods)] - disallowed_methods_profiles: FxHashMap> = FxHashMap::default(), /// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value /// `".."` can be used as part of the list to indicate that the configured values should be appended to the /// default configuration of Clippy. By default, any configuration will replace the default value. @@ -800,19 +833,6 @@ define_Conf! { #[disallowed_paths_allow_replacements = true] #[lints(disallowed_types)] disallowed_types: Vec = Vec::new(), - /// Named profiles of disallowed types, keyed by profile name. - /// - /// #### Example - /// - /// ```toml - /// [disallowed-types-profiles.forward_pass] - /// paths = [ - /// { path = "crate::io::HostBuffer", reason = "Prefer device buffers" } - /// ] - /// ``` - #[disallowed_paths_profile(replacements_allowed = true)] - #[lints(disallowed_types)] - disallowed_types_profiles: FxHashMap> = FxHashMap::default(), /// The list of words this lint should not consider as identifiers needing ticks. The value /// `".."` can be used as part of the list to indicate that the configured values should be appended to the /// default configuration of Clippy. By default, any configuration will replace the default value. For example: @@ -1014,6 +1034,21 @@ define_Conf! { /// The minimum size (in bytes) to consider a type for passing by reference instead of by value. #[lints(large_types_passed_by_value)] pass_by_value_size_limit: u64 = 256, + /// Named profiles of disallowed items (unrelated to Cargo build profiles). + /// + /// #### Example + /// + /// ```toml + /// [profiles.persistent] + /// disallowed-methods = [{ path = "std::env::temp_dir" }] + /// disallowed-types = [{ path = "std::time::Instant", reason = "use our custom time API" }] + /// + /// [profiles.single_threaded] + /// disallowed-methods = [{ path = "std::thread::spawn" }] + /// ``` + #[profiles = true] + #[lints(disallowed_methods, disallowed_types)] + profiles: FxHashMap = FxHashMap::default(), /// Lint "public" fields in a struct that are prefixed with an underscore based on their /// exported visibility, or whether they are marked as "pub". #[lints(pub_underscore_fields)] diff --git a/clippy_config/src/types.rs b/clippy_config/src/types.rs index 8d9326a904b1..5eaa44ddd51d 100644 --- a/clippy_config/src/types.rs +++ b/clippy_config/src/types.rs @@ -57,6 +57,15 @@ impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath, + #[serde(default, alias = "disallowed_types")] + pub disallowed_types: Vec, +} + // `DisallowedPathEnum` is an implementation detail to enable the `Deserialize` implementation just // above. `DisallowedPathEnum` is not meant to be used outside of this file. #[derive(Debug, Deserialize, Serialize)] diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 639a50231845..dcab7f0b2ded 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -61,12 +61,10 @@ declare_clippy_lint! { /// xs.push(123); // Vec::push is _not_ disallowed in the config. /// ``` /// - /// Profiles allow scoping different disallow lists: + /// Disallowed profiles allow scoping different disallow lists: /// ```toml - /// [disallowed-methods-profiles.forward_pass] - /// paths = [ - /// { path = "crate::devices::Buffer::copy_to_host", reason = "Forward code must not touch host buffers" } - /// ] + /// [profiles.forward_pass] + /// disallowed-methods = [{ path = "crate::devices::Buffer::copy_to_host", reason = "Forward code must not touch host buffers" }] /// ``` /// /// ```rust,ignore @@ -109,14 +107,15 @@ impl DisallowedMethods { ); let mut profiles = FxHashMap::default(); - let mut names: Vec<_> = conf.disallowed_methods_profiles.keys().collect(); + let mut names: Vec<_> = conf.profiles.keys().collect(); names.sort(); for name in names { let symbol = Symbol::intern(name.as_str()); - let paths = conf - .disallowed_methods_profiles - .get(name) - .expect("profile entry must exist"); + let profile = conf.profiles.get(name).expect("profile entry must exist"); + let paths = profile.disallowed_methods.as_slice(); + if paths.is_empty() { + continue; + } let (map, _) = create_disallowed_map( tcx, paths, @@ -134,11 +133,7 @@ impl DisallowedMethods { } let mut known_profiles = FxHashSet::default(); - for name in conf - .disallowed_methods_profiles - .keys() - .chain(conf.disallowed_types_profiles.keys()) - { + for name in conf.profiles.keys() { known_profiles.insert(Symbol::intern(name.as_str())); } diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index 01dddb1fe4cb..f28ad9f776ce 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -55,12 +55,10 @@ declare_clippy_lint! { /// use std::collections::HashMap; /// ``` /// - /// Profiles can scope lists to specific modules: + /// Disallowed profiles can scope lists to specific modules: /// ```toml - /// [disallowed-types-profiles.forward_pass] - /// paths = [ - /// { path = "crate::buffers::HostBuffer", reason = "Prefer device buffers in forward computations" } - /// ] + /// [profiles.forward_pass] + /// disallowed-types = [{ path = "crate::buffers::HostBuffer", reason = "Prefer device buffers in forward computations" }] /// ``` /// /// ```rust,ignore @@ -109,23 +107,20 @@ impl DisallowedTypes { let default = TypeLookup::from_config(tcx, &conf.disallowed_types); let mut profiles = FxHashMap::default(); - let mut names: Vec<_> = conf.disallowed_types_profiles.keys().collect(); + let mut names: Vec<_> = conf.profiles.keys().collect(); names.sort(); for name in names { let symbol = Symbol::intern(name.as_str()); - let paths = conf - .disallowed_types_profiles - .get(name) - .expect("profile entry must exist"); + let profile = conf.profiles.get(name).expect("profile entry must exist"); + let paths = profile.disallowed_types.as_slice(); + if paths.is_empty() { + continue; + } profiles.insert(symbol, TypeLookup::from_config(tcx, paths)); } let mut known_profiles = FxHashSet::default(); - for name in conf - .disallowed_types_profiles - .keys() - .chain(conf.disallowed_methods_profiles.keys()) - { + for name in conf.profiles.keys() { known_profiles.insert(Symbol::intern(name.as_str())); } diff --git a/tests/ui-toml/disallowed_profiles_methods/clippy.toml b/tests/ui-toml/disallowed_profiles_methods/clippy.toml index 52b36099e0ad..c77f5658ae69 100644 --- a/tests/ui-toml/disallowed_profiles_methods/clippy.toml +++ b/tests/ui-toml/disallowed_profiles_methods/clippy.toml @@ -2,12 +2,12 @@ disallowed-methods = [ { path = "std::mem::drop" } ] -[disallowed-methods-profiles.forward_pass] -paths = [ +[profiles.forward_pass] +disallowed-methods = [ { path = "alloc::vec::Vec::push", reason = "push is forbidden in forward profile" } ] -[disallowed-methods-profiles.export] -paths = [ +[profiles.export] +disallowed-methods = [ { path = "core::option::Option::unwrap" } ] diff --git a/tests/ui-toml/disallowed_profiles_types/clippy.toml b/tests/ui-toml/disallowed_profiles_types/clippy.toml index f43b04bd6b33..3c47b2afe26f 100644 --- a/tests/ui-toml/disallowed_profiles_types/clippy.toml +++ b/tests/ui-toml/disallowed_profiles_types/clippy.toml @@ -2,12 +2,12 @@ disallowed-types = [ { path = "std::rc::Rc" } ] -[disallowed-types-profiles.forward_pass] -paths = [ +[profiles.forward_pass] +disallowed-types = [ { path = "std::cell::RefCell", reason = "Prefer shared references" } ] -[disallowed-types-profiles.export] -paths = [ +[profiles.export] +disallowed-types = [ { path = "std::sync::Mutex" } ] diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 76110ba9f208..ba930d094fc1 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -41,10 +41,8 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect disallowed-fields disallowed-macros disallowed-methods - disallowed-methods-profiles disallowed-names disallowed-types - disallowed-types-profiles doc-valid-idents enable-raw-pointer-heuristic-for-send enforce-iter-loop-reborrow @@ -72,6 +70,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect module-items-ordered-within-groupings msrv pass-by-value-size-limit + profiles pub-underscore-fields-behavior recursive-self-in-type-definitions semicolon-inside-block-ignore-singleline @@ -144,10 +143,8 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect disallowed-fields disallowed-macros disallowed-methods - disallowed-methods-profiles disallowed-names disallowed-types - disallowed-types-profiles doc-valid-idents enable-raw-pointer-heuristic-for-send enforce-iter-loop-reborrow @@ -175,6 +172,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect module-items-ordered-within-groupings msrv pass-by-value-size-limit + profiles pub-underscore-fields-behavior recursive-self-in-type-definitions semicolon-inside-block-ignore-singleline @@ -247,10 +245,8 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni disallowed-fields disallowed-macros disallowed-methods - disallowed-methods-profiles disallowed-names disallowed-types - disallowed-types-profiles doc-valid-idents enable-raw-pointer-heuristic-for-send enforce-iter-loop-reborrow @@ -278,6 +274,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni module-items-ordered-within-groupings msrv pass-by-value-size-limit + profiles pub-underscore-fields-behavior recursive-self-in-type-definitions semicolon-inside-block-ignore-singleline From 37482e211d36c74165c408bc021c25fa1bac5f18 Mon Sep 17 00:00:00 2001 From: Lallapallooza Date: Wed, 17 Dec 2025 13:54:44 +0000 Subject: [PATCH 4/5] fix: address review feedback --- clippy_config/src/conf.rs | 38 ++++++++---------- clippy_lints/src/disallowed_methods.rs | 40 +++++++++---------- clippy_lints/src/disallowed_types.rs | 39 ++++++++---------- clippy_utils/src/disallowed_profiles.rs | 4 +- .../disallowed_profiles_methods/main.rs | 11 ++++- .../disallowed_profiles_methods/main.stderr | 23 ++++++----- .../ui-toml/disallowed_profiles_types/main.rs | 4 +- .../disallowed_profiles_types/main.stderr | 9 +++-- 8 files changed, 84 insertions(+), 84 deletions(-) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index b51aa54aa190..5d822286473d 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -249,28 +249,7 @@ macro_rules! parse_conf_value_impl { let _ = &$field_span; deserialize!($map, $ty, $errors, $file) }}; - ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profiles:expr), ()) => { - deserialize_profiles!($map, $errors, $file, $field_span) - }; - ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ($disallowed:expr)) => {{ - let _ = &$field_span; - deserialize!($map, $ty, $errors, $file, $disallowed) - }}; - ( - $map:expr, - $ty:ty, - $errors:expr, - $file:expr, - $field_span:expr, - ($profiles:expr), - ($disallowed:expr) - ) => { - compile_error!("field cannot specify both profiles and disallowed-paths attributes") - }; -} - -macro_rules! deserialize_profiles { - ($map:expr, $errors:expr, $file:expr, $field_span:expr) => {{ + ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profiles:expr), ()) => {{ let raw_value = $map.next_value::()?; let value_span = $field_span.clone(); let toml::Value::Table(table) = raw_value else { @@ -287,6 +266,21 @@ macro_rules! deserialize_profiles { (map, value_span) }}; + ($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ($disallowed:expr)) => {{ + let _ = &$field_span; + deserialize!($map, $ty, $errors, $file, $disallowed) + }}; + ( + $map:expr, + $ty:ty, + $errors:expr, + $file:expr, + $field_span:expr, + ($profiles:expr), + ($disallowed:expr) + ) => { + compile_error!("field cannot specify both profiles and disallowed-paths attributes") + }; } macro_rules! define_Conf { diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index dcab7f0b2ded..d110427b083b 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -1,6 +1,6 @@ use clippy_config::Conf; use clippy_config::types::{DisallowedPath, create_disallowed_map}; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::disallowed_profiles::{ProfileEntry, ProfileResolver}; use clippy_utils::paths::PathNS; use clippy_utils::sym; @@ -107,15 +107,18 @@ impl DisallowedMethods { ); let mut profiles = FxHashMap::default(); - let mut names: Vec<_> = conf.profiles.keys().collect(); - names.sort(); - for name in names { + let mut known_profiles = FxHashSet::default(); + let mut profile_entries: Vec<_> = conf.profiles.iter().collect(); + profile_entries.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (name, profile) in profile_entries { let symbol = Symbol::intern(name.as_str()); - let profile = conf.profiles.get(name).expect("profile entry must exist"); + known_profiles.insert(symbol); + let paths = profile.disallowed_methods.as_slice(); if paths.is_empty() { continue; } + let (map, _) = create_disallowed_map( tcx, paths, @@ -132,11 +135,6 @@ impl DisallowedMethods { profiles.insert(symbol, map); } - let mut known_profiles = FxHashSet::default(); - for name in conf.profiles.keys() { - known_profiles.insert(Symbol::intern(name.as_str())); - } - Self { default, profiles, @@ -153,17 +151,15 @@ impl DisallowedMethods { } else { "clippy::disallowed_profile" }; - cx.tcx - .sess - .dcx() - .struct_span_warn( - entry.span, - format!( - "`{attr_name}` references unknown profile `{}` for `clippy::disallowed_methods`", - entry.name - ), - ) - .emit(); + span_lint( + cx, + DISALLOWED_METHODS, + entry.span, + format!( + "`{attr_name}` references unknown profile `{}` for `clippy::disallowed_methods`", + entry.name + ), + ); } } } @@ -188,7 +184,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { if is_active { active_profiles.push(entry.name); } else if !self.known_profiles.contains(&entry.name) { - unknown_profiles.push(entry.clone()); + unknown_profiles.push(*entry); } } } diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index f28ad9f776ce..020e4c62c65d 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -1,6 +1,6 @@ use clippy_config::Conf; use clippy_config::types::{DisallowedPath, create_disallowed_map}; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::disallowed_profiles::{ProfileEntry, ProfileResolver}; use clippy_utils::paths::PathNS; use clippy_utils::sym; @@ -107,11 +107,13 @@ impl DisallowedTypes { let default = TypeLookup::from_config(tcx, &conf.disallowed_types); let mut profiles = FxHashMap::default(); - let mut names: Vec<_> = conf.profiles.keys().collect(); - names.sort(); - for name in names { + let mut known_profiles = FxHashSet::default(); + let mut profile_entries: Vec<_> = conf.profiles.iter().collect(); + profile_entries.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (name, profile) in profile_entries { let symbol = Symbol::intern(name.as_str()); - let profile = conf.profiles.get(name).expect("profile entry must exist"); + known_profiles.insert(symbol); + let paths = profile.disallowed_types.as_slice(); if paths.is_empty() { continue; @@ -119,11 +121,6 @@ impl DisallowedTypes { profiles.insert(symbol, TypeLookup::from_config(tcx, paths)); } - let mut known_profiles = FxHashSet::default(); - for name in conf.profiles.keys() { - known_profiles.insert(Symbol::intern(name.as_str())); - } - Self { default, profiles, @@ -140,17 +137,15 @@ impl DisallowedTypes { } else { "clippy::disallowed_profile" }; - cx.tcx - .sess - .dcx() - .struct_span_warn( - entry.span, - format!( - "`{attr_name}` references unknown profile `{}` for `clippy::disallowed_types`", - entry.name - ), - ) - .emit(); + span_lint( + cx, + DISALLOWED_TYPES, + entry.span, + format!( + "`{attr_name}` references unknown profile `{}` for `clippy::disallowed_types`", + entry.name + ), + ); } } @@ -162,7 +157,7 @@ impl DisallowedTypes { if self.profiles.contains_key(&entry.name) { active_profiles.push(entry.name); } else if !self.known_profiles.contains(&entry.name) { - unknown_profiles.push(entry.clone()); + unknown_profiles.push(*entry); } } } diff --git a/clippy_utils/src/disallowed_profiles.rs b/clippy_utils/src/disallowed_profiles.rs index 17b99b136d6d..f6bb8bfcfc00 100644 --- a/clippy_utils/src/disallowed_profiles.rs +++ b/clippy_utils/src/disallowed_profiles.rs @@ -6,7 +6,7 @@ use rustc_hir::{Attribute, HirId}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; -#[derive(Clone)] +#[derive(Copy, Clone)] pub struct ProfileEntry { pub attr_name: Symbol, pub name: Symbol, @@ -39,6 +39,8 @@ pub struct ProfileResolver { impl ProfileResolver { pub fn active_profiles(&mut self, cx: &LateContext<'_>, hir_id: HirId) -> Option<&ProfileSelection> { + // NOTE: The `contains_key`+`get` dance is intentional: using only `get` here triggers borrowck + // errors because we need to mutate `self.cache` on cache misses. if self.cache.contains_key(&hir_id) { return self.cache.get(&hir_id).and_then(|selection| selection.as_ref()); } diff --git a/tests/ui-toml/disallowed_profiles_methods/main.rs b/tests/ui-toml/disallowed_profiles_methods/main.rs index b1c04a20ad59..dfd905a99a9e 100644 --- a/tests/ui-toml/disallowed_profiles_methods/main.rs +++ b/tests/ui-toml/disallowed_profiles_methods/main.rs @@ -12,6 +12,12 @@ fn default_violation() { std::mem::drop(value); //~ ERROR: use of a disallowed method `std::mem::drop` } +#[expect(clippy::disallowed_methods)] +fn expected_violation() { + let value = String::from("test"); + std::mem::drop(value); +} + #[clippy::disallowed_profile("forward_pass")] fn forward_profile() { let mut values = Vec::new(); @@ -25,8 +31,8 @@ fn export_profile() { } #[clippy::disallowed_profile("unknown_profile")] -//~^ WARN: unknown profile `unknown_profile` for -//~| WARN: unknown profile `unknown_profile` for +//~^ ERROR: unknown profile `unknown_profile` for +//~| ERROR: unknown profile `unknown_profile` for fn unknown_profile() { let mut values = Vec::new(); values.push(1); @@ -44,6 +50,7 @@ fn merged_profiles() { fn main() { default_violation(); + expected_violation(); forward_profile(); export_profile(); unknown_profile(); diff --git a/tests/ui-toml/disallowed_profiles_methods/main.stderr b/tests/ui-toml/disallowed_profiles_methods/main.stderr index ee4f4af32069..d4214f864d56 100644 --- a/tests/ui-toml/disallowed_profiles_methods/main.stderr +++ b/tests/ui-toml/disallowed_profiles_methods/main.stderr @@ -8,7 +8,7 @@ LL | std::mem::drop(value); = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` error: use of a disallowed method `alloc::vec::Vec::push` (profile: forward_pass) - --> tests/ui-toml/disallowed_profiles_methods/main.rs:18:12 + --> tests/ui-toml/disallowed_profiles_methods/main.rs:24:12 | LL | values.push(1); | ^^^^ @@ -16,31 +16,34 @@ LL | values.push(1); = note: push is forbidden in forward profile error: use of a disallowed method `core::option::Option::unwrap` (profile: export) - --> tests/ui-toml/disallowed_profiles_methods/main.rs:24:11 + --> tests/ui-toml/disallowed_profiles_methods/main.rs:30:11 | LL | value.unwrap(); | ^^^^^^ -warning: `clippy::disallowed_profile` references unknown profile `unknown_profile` for `clippy::disallowed_methods` - --> tests/ui-toml/disallowed_profiles_methods/main.rs:27:30 +error: `clippy::disallowed_profile` references unknown profile `unknown_profile` for `clippy::disallowed_methods` + --> tests/ui-toml/disallowed_profiles_methods/main.rs:33:30 | LL | #[clippy::disallowed_profile("unknown_profile")] | ^^^^^^^^^^^^^^^^^ -warning: `clippy::disallowed_profile` references unknown profile `unknown_profile` for `clippy::disallowed_types` - --> tests/ui-toml/disallowed_profiles_methods/main.rs:27:30 +error: `clippy::disallowed_profile` references unknown profile `unknown_profile` for `clippy::disallowed_types` + --> tests/ui-toml/disallowed_profiles_methods/main.rs:33:30 | LL | #[clippy::disallowed_profile("unknown_profile")] | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::disallowed-types` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::disallowed_types)]` error: use of a disallowed method `std::mem::drop` - --> tests/ui-toml/disallowed_profiles_methods/main.rs:34:5 + --> tests/ui-toml/disallowed_profiles_methods/main.rs:40:5 | LL | std::mem::drop(values); | ^^^^^^^^^^^^^^ error: use of a disallowed method `alloc::vec::Vec::push` (profile: forward_pass) - --> tests/ui-toml/disallowed_profiles_methods/main.rs:40:12 + --> tests/ui-toml/disallowed_profiles_methods/main.rs:46:12 | LL | values.push(1); | ^^^^ @@ -48,10 +51,10 @@ LL | values.push(1); = note: push is forbidden in forward profile error: use of a disallowed method `core::option::Option::unwrap` (profile: export) - --> tests/ui-toml/disallowed_profiles_methods/main.rs:42:11 + --> tests/ui-toml/disallowed_profiles_methods/main.rs:48:11 | LL | value.unwrap(); | ^^^^^^ -error: aborting due to 6 previous errors; 2 warnings emitted +error: aborting due to 8 previous errors diff --git a/tests/ui-toml/disallowed_profiles_types/main.rs b/tests/ui-toml/disallowed_profiles_types/main.rs index 9598fe9f8029..a21897fd67f7 100644 --- a/tests/ui-toml/disallowed_profiles_types/main.rs +++ b/tests/ui-toml/disallowed_profiles_types/main.rs @@ -21,8 +21,8 @@ fn export_profile() { } #[clippy::disallowed_profile("unknown_type_profile")] -//~^ WARN: unknown profile `unknown_type_profile` for -//~| WARN: unknown profile `unknown_type_profile` for +//~^ ERROR: unknown profile `unknown_type_profile` for +//~| ERROR: unknown profile `unknown_type_profile` for fn unknown_profile() { let _other = 1u32; let _fallback: Rc = todo!(); //~ ERROR: use of a disallowed type `std::rc::Rc` diff --git a/tests/ui-toml/disallowed_profiles_types/main.stderr b/tests/ui-toml/disallowed_profiles_types/main.stderr index cee93ae8c260..ccd6b23ba549 100644 --- a/tests/ui-toml/disallowed_profiles_types/main.stderr +++ b/tests/ui-toml/disallowed_profiles_types/main.stderr @@ -27,13 +27,16 @@ error: use of a disallowed type `std::sync::Mutex` (profile: export) LL | let _value: Mutex = todo!(); | ^^^^^^^^^^ -warning: `clippy::disallowed_profile` references unknown profile `unknown_type_profile` for `clippy::disallowed_methods` +error: `clippy::disallowed_profile` references unknown profile `unknown_type_profile` for `clippy::disallowed_methods` --> tests/ui-toml/disallowed_profiles_types/main.rs:23:30 | LL | #[clippy::disallowed_profile("unknown_type_profile")] | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::disallowed-methods` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` -warning: `clippy::disallowed_profile` references unknown profile `unknown_type_profile` for `clippy::disallowed_types` +error: `clippy::disallowed_profile` references unknown profile `unknown_type_profile` for `clippy::disallowed_types` --> tests/ui-toml/disallowed_profiles_types/main.rs:23:30 | LL | #[clippy::disallowed_profile("unknown_type_profile")] @@ -59,5 +62,5 @@ error: use of a disallowed type `std::sync::Mutex` (profile: export) LL | let _other: Mutex = todo!(); | ^^^^^^^^^^ -error: aborting due to 7 previous errors; 2 warnings emitted +error: aborting due to 9 previous errors From 03128d703a9dd2e5a3fe7e4f186e4cde0a8d6fd3 Mon Sep 17 00:00:00 2001 From: vitalii Date: Tue, 21 Apr 2026 01:34:05 +0100 Subject: [PATCH 5/5] fix: address final review feedback - document `ProfileEntry` and `ProfileSelection` - explain why `profiles` and `known_profiles` are separate - emit unknown-profile warning inline (drop intermediate SmallVec) - add tests covering `#[expect]` before/after `#[clippy::disallowed_profile]` with an unknown profile name, for both methods and types lints - sort_by -> sort_by_key to satisfy dogfood --- clippy_lints/src/disallowed_methods.rs | 32 +++++++++++-------- clippy_lints/src/disallowed_types.rs | 31 ++++++++++-------- clippy_utils/src/disallowed_profiles.rs | 17 ++++++++-- .../disallowed_profiles_methods/main.rs | 20 ++++++++++++ .../disallowed_profiles_methods/main.stderr | 14 +++++++- .../ui-toml/disallowed_profiles_types/main.rs | 18 +++++++++++ .../disallowed_profiles_types/main.stderr | 14 +++++++- 7 files changed, 114 insertions(+), 32 deletions(-) diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index d110427b083b..58cc318d57cc 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -83,7 +83,12 @@ impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); pub struct DisallowedMethods { default: DefIdMap<(&'static str, &'static DisallowedPath)>, + /// Lookup per profile that declares a non-empty `disallowed_methods` list. Profiles + /// declared in `[profiles.*]` but without `disallowed_methods` entries are absent here. profiles: FxHashMap>, + /// Every profile name declared in `[profiles.*]`, regardless of whether it contributes + /// to this lint. Used to suppress the "unknown profile" warning for profiles that exist + /// in config but only define entries for other lints (e.g. `disallowed_types`). known_profiles: FxHashSet, profile_cache: ProfileResolver, warned_unknown_profiles: FxHashSet, @@ -109,7 +114,7 @@ impl DisallowedMethods { let mut profiles = FxHashMap::default(); let mut known_profiles = FxHashSet::default(); let mut profile_entries: Vec<_> = conf.profiles.iter().collect(); - profile_entries.sort_by(|(a, _), (b, _)| a.cmp(b)); + profile_entries.sort_by_key(|(a, _)| *a); for (name, profile) in profile_entries { let symbol = Symbol::intern(name.as_str()); known_profiles.insert(symbol); @@ -177,22 +182,21 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { _ => return, }; let mut active_profiles = SmallVec::<[Symbol; 2]>::new(); - let mut unknown_profiles = SmallVec::<[ProfileEntry; 2]>::new(); - if let Some(selection) = self.profile_cache.active_profiles(cx, expr.hir_id) { - for entry in selection.iter() { - let is_active = self.profiles.contains_key(&entry.name); - if is_active { - active_profiles.push(entry.name); - } else if !self.known_profiles.contains(&entry.name) { - unknown_profiles.push(*entry); - } + // Copy entries out of the cache before iterating: `warn_unknown_profile` takes + // `&mut self`, which conflicts with the borrow held by `active_profiles(...)`. + let entries: SmallVec<[ProfileEntry; 2]> = self + .profile_cache + .active_profiles(cx, expr.hir_id) + .map(|selection| selection.iter().copied().collect()) + .unwrap_or_default(); + for entry in &entries { + if self.profiles.contains_key(&entry.name) { + active_profiles.push(entry.name); + } else if !self.known_profiles.contains(&entry.name) { + self.warn_unknown_profile(cx, entry); } } - for entry in unknown_profiles { - self.warn_unknown_profile(cx, &entry); - } - if let Some((profile, &(path, disallowed_path))) = active_profiles.iter().find_map(|symbol| { self.profiles .get(symbol) diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index 020e4c62c65d..37dd605617a7 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -95,7 +95,12 @@ impl TypeLookup { pub struct DisallowedTypes { default: TypeLookup, + /// Lookup per profile that declares a non-empty `disallowed_types` list. Profiles + /// declared in `[profiles.*]` but without `disallowed_types` entries are absent here. profiles: FxHashMap, + /// Every profile name declared in `[profiles.*]`, regardless of whether it contributes + /// to this lint. Used to suppress the "unknown profile" warning for profiles that exist + /// in config but only define entries for other lints (e.g. `disallowed_methods`). known_profiles: FxHashSet, profile_cache: ProfileResolver, warned_unknown_profiles: FxHashSet, @@ -109,7 +114,7 @@ impl DisallowedTypes { let mut profiles = FxHashMap::default(); let mut known_profiles = FxHashSet::default(); let mut profile_entries: Vec<_> = conf.profiles.iter().collect(); - profile_entries.sort_by(|(a, _), (b, _)| a.cmp(b)); + profile_entries.sort_by_key(|(a, _)| *a); for (name, profile) in profile_entries { let symbol = Symbol::intern(name.as_str()); known_profiles.insert(symbol); @@ -151,21 +156,21 @@ impl DisallowedTypes { fn check_res_emit(&mut self, cx: &LateContext<'_>, hir_id: rustc_hir::HirId, res: &Res, span: Span) { let mut active_profiles = SmallVec::<[Symbol; 2]>::new(); - let mut unknown_profiles = SmallVec::<[ProfileEntry; 2]>::new(); - if let Some(selection) = self.profile_cache.active_profiles(cx, hir_id) { - for entry in selection.iter() { - if self.profiles.contains_key(&entry.name) { - active_profiles.push(entry.name); - } else if !self.known_profiles.contains(&entry.name) { - unknown_profiles.push(*entry); - } + // Copy entries out of the cache before iterating: `warn_unknown_profile` takes + // `&mut self`, which conflicts with the borrow held by `active_profiles(...)`. + let entries: SmallVec<[ProfileEntry; 2]> = self + .profile_cache + .active_profiles(cx, hir_id) + .map(|selection| selection.iter().copied().collect()) + .unwrap_or_default(); + for entry in &entries { + if self.profiles.contains_key(&entry.name) { + active_profiles.push(entry.name); + } else if !self.known_profiles.contains(&entry.name) { + self.warn_unknown_profile(cx, entry); } } - for entry in unknown_profiles { - self.warn_unknown_profile(cx, &entry); - } - if let Some((profile, (path, disallowed_path))) = active_profiles.iter().find_map(|symbol| { self.profiles .get(symbol) diff --git a/clippy_utils/src/disallowed_profiles.rs b/clippy_utils/src/disallowed_profiles.rs index f6bb8bfcfc00..98a498981b3b 100644 --- a/clippy_utils/src/disallowed_profiles.rs +++ b/clippy_utils/src/disallowed_profiles.rs @@ -6,6 +6,12 @@ use rustc_hir::{Attribute, HirId}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; +/// One profile name referenced by a `#[clippy::disallowed_profile(...)]` or +/// `#[clippy::disallowed_profiles(...)]` attribute on an item. +/// +/// A single attribute produces one `ProfileEntry` per string argument. The entry records which +/// attribute variant introduced it, the profile name, and the span of that string literal so +/// diagnostics (e.g. "unknown profile") can point at the exact argument. #[derive(Copy, Clone)] pub struct ProfileEntry { pub attr_name: Symbol, @@ -13,6 +19,11 @@ pub struct ProfileEntry { pub span: Span, } +/// The set of profiles active at some `HirId`, obtained by walking up the HIR from that id and +/// collecting the first ancestor that carries a `#[clippy::disallowed_profile(s)]` attribute. +/// +/// An empty selection is represented by `None` at the call site; a `ProfileSelection` is always +/// non-empty. #[derive(Clone)] pub struct ProfileSelection { entries: SmallVec<[ProfileEntry; 2]>, @@ -85,12 +96,12 @@ fn profiles_from_attrs(cx: &LateContext<'_>, attrs: &[Attribute]) -> Option::new(); for attr in attrs { - let Some(path) = attr.ident_path() else { continue }; - if path.len() != 2 || path[0].name != sym::clippy { + let path = attr.path(); + if path.len() != 2 || path[0] != sym::clippy { continue; } - let name = path[1].name; + let name = path[1]; if name != sym::disallowed_profile && name != sym::disallowed_profiles { continue; } diff --git a/tests/ui-toml/disallowed_profiles_methods/main.rs b/tests/ui-toml/disallowed_profiles_methods/main.rs index dfd905a99a9e..07bb22e05d6f 100644 --- a/tests/ui-toml/disallowed_profiles_methods/main.rs +++ b/tests/ui-toml/disallowed_profiles_methods/main.rs @@ -48,6 +48,24 @@ fn merged_profiles() { value.unwrap(); //~ ERROR: use of a disallowed method `core::option::Option::unwrap` (profile: export) } +// `#[expect(clippy::disallowed_methods)]` silences the body warning and the unknown-profile +// warning tagged under `DISALLOWED_METHODS`, but not the one tagged under `DISALLOWED_TYPES`. +#[expect(clippy::disallowed_methods)] +#[clippy::disallowed_profile("unknown_profile_expect_before")] +//~^ ERROR: unknown profile `unknown_profile_expect_before` for `clippy::disallowed_types` +fn expect_before_unknown_profile() { + let value = String::from("test"); + std::mem::drop(value); +} + +#[clippy::disallowed_profile("unknown_profile_expect_after")] +//~^ ERROR: unknown profile `unknown_profile_expect_after` for `clippy::disallowed_types` +#[expect(clippy::disallowed_methods)] +fn expect_after_unknown_profile() { + let value = String::from("test"); + std::mem::drop(value); +} + fn main() { default_violation(); expected_violation(); @@ -55,4 +73,6 @@ fn main() { export_profile(); unknown_profile(); merged_profiles(); + expect_before_unknown_profile(); + expect_after_unknown_profile(); } diff --git a/tests/ui-toml/disallowed_profiles_methods/main.stderr b/tests/ui-toml/disallowed_profiles_methods/main.stderr index d4214f864d56..5f64b1bf4499 100644 --- a/tests/ui-toml/disallowed_profiles_methods/main.stderr +++ b/tests/ui-toml/disallowed_profiles_methods/main.stderr @@ -56,5 +56,17 @@ error: use of a disallowed method `core::option::Option::unwrap` (profile: expor LL | value.unwrap(); | ^^^^^^ -error: aborting due to 8 previous errors +error: `clippy::disallowed_profile` references unknown profile `unknown_profile_expect_before` for `clippy::disallowed_types` + --> tests/ui-toml/disallowed_profiles_methods/main.rs:54:30 + | +LL | #[clippy::disallowed_profile("unknown_profile_expect_before")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `clippy::disallowed_profile` references unknown profile `unknown_profile_expect_after` for `clippy::disallowed_types` + --> tests/ui-toml/disallowed_profiles_methods/main.rs:61:30 + | +LL | #[clippy::disallowed_profile("unknown_profile_expect_after")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors diff --git a/tests/ui-toml/disallowed_profiles_types/main.rs b/tests/ui-toml/disallowed_profiles_types/main.rs index a21897fd67f7..0161417dd602 100644 --- a/tests/ui-toml/disallowed_profiles_types/main.rs +++ b/tests/ui-toml/disallowed_profiles_types/main.rs @@ -34,10 +34,28 @@ fn merged_profiles() { let _other: Mutex = todo!(); //~ ERROR: use of a disallowed type `std::sync::Mutex` (profile: export) } +// `#[expect(clippy::disallowed_types)]` silences the body warning and the unknown-profile +// warning tagged under `DISALLOWED_TYPES`, but not one tagged under `DISALLOWED_METHODS`. +#[expect(clippy::disallowed_types)] +#[clippy::disallowed_profile("unknown_type_profile_expect_before")] +//~^ ERROR: unknown profile `unknown_type_profile_expect_before` for `clippy::disallowed_methods` +fn expect_before_unknown_profile() { + let _value: Rc = todo!(); +} + +#[clippy::disallowed_profile("unknown_type_profile_expect_after")] +//~^ ERROR: unknown profile `unknown_type_profile_expect_after` for `clippy::disallowed_methods` +#[expect(clippy::disallowed_types)] +fn expect_after_unknown_profile() { + let _value: Rc = todo!(); +} + fn main() { default_type(); forward_profile(); export_profile(); unknown_profile(); merged_profiles(); + expect_before_unknown_profile(); + expect_after_unknown_profile(); } diff --git a/tests/ui-toml/disallowed_profiles_types/main.stderr b/tests/ui-toml/disallowed_profiles_types/main.stderr index ccd6b23ba549..68d63e218c0d 100644 --- a/tests/ui-toml/disallowed_profiles_types/main.stderr +++ b/tests/ui-toml/disallowed_profiles_types/main.stderr @@ -62,5 +62,17 @@ error: use of a disallowed type `std::sync::Mutex` (profile: export) LL | let _other: Mutex = todo!(); | ^^^^^^^^^^ -error: aborting due to 9 previous errors +error: `clippy::disallowed_profile` references unknown profile `unknown_type_profile_expect_before` for `clippy::disallowed_methods` + --> tests/ui-toml/disallowed_profiles_types/main.rs:40:30 + | +LL | #[clippy::disallowed_profile("unknown_type_profile_expect_before")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `clippy::disallowed_profile` references unknown profile `unknown_type_profile_expect_after` for `clippy::disallowed_methods` + --> tests/ui-toml/disallowed_profiles_types/main.rs:46:30 + | +LL | #[clippy::disallowed_profile("unknown_type_profile_expect_after")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 11 previous errors