diff --git a/CHANGELOG.md b/CHANGELOG.md index 748e283edffb..0630aad380d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6540,6 +6540,7 @@ Released 2018-09-13 [`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods [`disallowed_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names [`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents +[`disallowed_trait_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_trait_usage [`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type [`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types [`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression @@ -7363,6 +7364,7 @@ Released 2018-09-13 [`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros [`disallowed-methods`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods [`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names +[`disallowed-trait-usage`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-trait-usage [`disallowed-types`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-types [`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 diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index c87f8e9a68de..14634f17dc64 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -585,6 +585,33 @@ default configuration of Clippy. By default, any configuration will replace the * [`disallowed_names`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names) +## `disallowed-trait-usage` +The list of disallowed trait usages. Each entry forbids using a type via a specific +trait interface. + +**Fields:** +- `type` (optional): the fully qualified path to a concrete type (e.g. `"i32"`, `"std::path::PathBuf"`) +- `implements` (optional): the fully qualified path to a trait; matches any type implementing it +- `trait` (required): the fully qualified path to the disallowed trait (e.g. `"std::fmt::Debug"`) +- `reason` (optional): explanation why this trait usage is disallowed + +Exactly one of `type` or `implements` must be specified. + +### Example +```toml +disallowed-trait-usage = [ + { type = "std::path::PathBuf", trait = "std::fmt::Debug", reason = "Use path.display() instead" }, + { implements = "std::error::Error", trait = "std::fmt::Debug", reason = "Use Display instead" }, +] +``` + +**Default Value:** `[]` + +--- +**Affected lints:** +* [`disallowed_trait_usage`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_trait_usage) + + ## `disallowed-types` The list of disallowed types, written as fully qualified paths. diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 41099f94b044..eefc1499b8dd 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -628,6 +628,26 @@ define_Conf! { /// default configuration of Clippy. By default, any configuration will replace the default value. #[lints(disallowed_names)] disallowed_names: Vec = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(), + /// The list of disallowed trait usages. Each entry forbids using a type via a specific + /// trait interface. + /// + /// **Fields:** + /// - `type` (optional): the fully qualified path to a concrete type (e.g. `"i32"`, `"std::path::PathBuf"`) + /// - `implements` (optional): the fully qualified path to a trait; matches any type implementing it + /// - `trait` (required): the fully qualified path to the disallowed trait (e.g. `"std::fmt::Debug"`) + /// - `reason` (optional): explanation why this trait usage is disallowed + /// + /// Exactly one of `type` or `implements` must be specified. + /// + /// ### Example + /// ```toml + /// disallowed-trait-usage = [ + /// { type = "std::path::PathBuf", trait = "std::fmt::Debug", reason = "Use path.display() instead" }, + /// { implements = "std::error::Error", trait = "std::fmt::Debug", reason = "Use Display instead" }, + /// ] + /// ``` + #[lints(disallowed_trait_usage)] + disallowed_trait_usage: Vec = Vec::new(), /// The list of disallowed types, written as fully qualified paths. /// /// **Fields:** diff --git a/clippy_config/src/types.rs b/clippy_config/src/types.rs index 8d9326a904b1..a32d463b9525 100644 --- a/clippy_config/src/types.rs +++ b/clippy_config/src/types.rs @@ -671,6 +671,28 @@ impl Serialize for SourceItemOrderingWithinModuleItemGroupings { } } +/// An entry in the `disallowed-trait-usage` configuration. +/// +/// Forbids using a type via a specific trait interface. The type can be specified +/// either as a concrete type (`type`) or as a trait bound (`implements`), but not both. +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct DisallowedTraitUsage { + /// The fully qualified path to a concrete type (e.g. `"i32"`, `"std::path::PathBuf"`). + /// Mutually exclusive with `implements`. + #[serde(rename = "type")] + pub type_path: Option, + /// The fully qualified path to a trait (e.g. `"std::error::Error"`). + /// When set, the rule applies to any type that implements this trait. + /// Mutually exclusive with `type`. + pub implements: Option, + /// The fully qualified path to the trait being disallowed (e.g. `"std::fmt::Debug"`). + #[serde(rename = "trait")] + pub trait_path: String, + /// Optional reason explaining why this usage is disallowed. + pub reason: Option, +} + // these impls are never actually called but are used by the various config options that default to // empty lists macro_rules! unimplemented_serialize { @@ -689,6 +711,7 @@ macro_rules! unimplemented_serialize { } unimplemented_serialize! { + DisallowedTraitUsage, Rename, MacroMatcher, } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c164241673a3..4bc887d4443c 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -110,6 +110,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::disallowed_methods::DISALLOWED_METHODS_INFO, crate::disallowed_names::DISALLOWED_NAMES_INFO, crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO, + crate::disallowed_trait_usage::DISALLOWED_TRAIT_USAGE_INFO, crate::disallowed_types::DISALLOWED_TYPES_INFO, crate::doc::DOC_BROKEN_LINK_INFO, crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO, diff --git a/clippy_lints/src/disallowed_trait_usage.rs b/clippy_lints/src/disallowed_trait_usage.rs new file mode 100644 index 000000000000..70d7827a3e27 --- /dev/null +++ b/clippy_lints/src/disallowed_trait_usage.rs @@ -0,0 +1,293 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node}; +use clippy_utils::paths::{PathNS, find_crates, lookup_path}; +use clippy_utils::sym; +use clippy_utils::ty::implements_trait; +use rustc_ast::{FormatArgsPiece, FormatTrait}; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::DefId; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_session::impl_lint_pass; +use rustc_span::Symbol; + +declare_clippy_lint! { + /// ### What it does + /// Denies using a configured type via a configured trait interface. + /// + /// Note: Even though this lint is warn-by-default, it will only trigger if + /// entries are defined in the clippy.toml file. + /// + /// ### Why is this bad? + /// Some trait implementations on certain types produce undesirable results. + /// For example, `Debug` formatting of path types includes escaping and quoting + /// that is usually not wanted in user-facing output. + /// + /// ### Example + /// An example clippy.toml configuration: + /// ```toml + /// # clippy.toml + /// disallowed-trait-usage = [ + /// # Forbid Debug formatting of a specific type: + /// { type = "std::path::PathBuf", trait = "std::fmt::Debug", reason = "Use path.display() instead" }, + /// # Forbid Debug formatting of any type implementing a trait: + /// { implements = "std::error::Error", trait = "std::fmt::Debug", reason = "Use Display instead" }, + /// ] + /// ``` + /// + /// ```rust,ignore + /// use std::path::PathBuf; + /// let path = PathBuf::from("/tmp"); + /// println!("{path:?}"); // Triggers the lint + /// ``` + /// Use instead: + /// ```rust,ignore + /// use std::path::PathBuf; + /// let path = PathBuf::from("/tmp"); + /// println!("{}", path.display()); // OK + /// ``` + #[clippy::version = "1.96.0"] + pub DISALLOWED_TRAIT_USAGE, + style, + "use of a type via a disallowed trait interface" +} + +impl_lint_pass!(DisallowedTraitUsage => [DISALLOWED_TRAIT_USAGE]); + +/// Identifies a type: either a concrete type (ADT/primitive) or "any type implementing a trait". +#[derive(Clone, Copy)] +enum TypeMatcher { + /// Matches a specific ADT (struct, enum, union, etc.) by `DefId`. + Def(DefId), + /// Matches a primitive type. + Prim(rustc_hir::PrimTy), + /// Matches any type that implements the given trait. + ImplementsTrait(DefId), +} + +/// A resolved disallowed (type, trait) pair. +struct ResolvedEntry { + type_matcher: TypeMatcher, + trait_def_id: DefId, + /// Description of the type constraint, used in diagnostics. + type_description: &'static str, + trait_path: &'static str, + reason: Option<&'static str>, +} + +pub struct DisallowedTraitUsage { + format_args: FormatArgsStorage, + entries: Vec, +} + +/// Returns true if path's root crate is loaded (or the path is a single segment). +fn is_crate_loaded(tcx: TyCtxt<'_>, sym_path: &[Symbol]) -> bool { + sym_path.len() < 2 || !find_crates(tcx, sym_path[0]).is_empty() +} + +fn emit_invalid_path_warning(tcx: TyCtxt<'_>, sym_path: &[Symbol], path: &str, expected: &str) { + if !is_crate_loaded(tcx, sym_path) { + return; + } + + // Re-lookup in arbitrary namespace to produce a good "expected X, found Y" message + let found = lookup_path(tcx, PathNS::Arbitrary, sym_path); + let message = if let Some(&def_id) = found.first() { + let (article, description) = tcx.article_and_description(def_id); + format!("expected a {expected}, found {article} {description}: `{path}` (in `disallowed-trait-usage`)") + } else { + format!("`{path}` does not refer to a reachable {expected} (in `disallowed-trait-usage`)") + }; + + tcx.sess.dcx().warn(message); +} + +fn resolve_type_matcher(tcx: TyCtxt<'_>, path: &str) -> Option { + let sym_name = Symbol::intern(path); + if let Some(prim) = rustc_hir::PrimTy::from_name(sym_name) { + return Some(TypeMatcher::Prim(prim)); + } + + let sym_path: Vec = path.split("::").map(Symbol::intern).collect(); + let def_ids = lookup_path(tcx, PathNS::Type, &sym_path); + let result = def_ids.iter().find(|&&did| { + matches!( + tcx.def_kind(did), + DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::TyAlias | DefKind::ForeignTy + ) + }); + + if let Some(&def_id) = result { + Some(TypeMatcher::Def(def_id)) + } else { + emit_invalid_path_warning(tcx, &sym_path, path, "type"); + None + } +} + +fn resolve_trait_def_id(tcx: TyCtxt<'_>, path: &str) -> Option { + let sym_path: Vec = path.split("::").map(Symbol::intern).collect(); + let def_ids = lookup_path(tcx, PathNS::Type, &sym_path); + let result = def_ids.iter().find(|&&did| matches!(tcx.def_kind(did), DefKind::Trait)); + + if let Some(&def_id) = result { + Some(def_id) + } else { + emit_invalid_path_warning(tcx, &sym_path, path, "trait"); + None + } +} + +impl DisallowedTraitUsage { + pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, format_args: FormatArgsStorage) -> Self { + let entries = conf + .disallowed_trait_usage + .iter() + .filter_map(|entry| { + let trait_def_id = resolve_trait_def_id(tcx, &entry.trait_path); + + let (type_matcher, type_description) = match (&entry.type_path, &entry.implements) { + (Some(type_path), None) => { + let matcher = resolve_type_matcher(tcx, type_path); + (matcher, type_path.as_str()) + }, + (None, Some(impl_path)) => { + let impl_trait_id = resolve_trait_def_id(tcx, impl_path); + (impl_trait_id.map(TypeMatcher::ImplementsTrait), impl_path.as_str()) + }, + (Some(_), Some(_)) => { + tcx.sess + .dcx() + .warn("`type` and `implements` are mutually exclusive (in `disallowed-trait-usage`)"); + return None; + }, + (None, None) => { + tcx.sess + .dcx() + .warn("either `type` or `implements` must be specified (in `disallowed-trait-usage`)"); + return None; + }, + }; + + Some(ResolvedEntry { + type_matcher: type_matcher?, + trait_def_id: trait_def_id?, + type_description, + trait_path: &entry.trait_path, + reason: entry.reason.as_deref(), + }) + }) + .collect(); + + Self { format_args, entries } + } + + fn check_type_trait<'tcx>( + &self, + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + trait_def_id: DefId, + report_span: rustc_span::Span, + ) { + let ty = ty.peel_refs(); + + for entry in &self.entries { + if entry.trait_def_id != trait_def_id { + continue; + } + + let type_matches = match entry.type_matcher { + TypeMatcher::Def(def_id) => matches!(ty.kind(), ty::Adt(adt_def, _) if adt_def.did() == def_id), + TypeMatcher::Prim(prim) => ty_matches_prim(ty, prim), + TypeMatcher::ImplementsTrait(impl_trait_id) => implements_trait(cx, ty, impl_trait_id, &[]), + }; + + if type_matches { + let message = match entry.type_matcher { + TypeMatcher::ImplementsTrait(_) => format!( + "use of implementor of `{}` via trait `{}` is disallowed", + entry.type_description, entry.trait_path, + ), + _ => format!( + "use of `{}` via trait `{}` is disallowed", + entry.type_description, entry.trait_path, + ), + }; + + span_lint_and_then(cx, DISALLOWED_TRAIT_USAGE, report_span, message, |diag| { + if let Some(reason) = entry.reason { + diag.note(reason.to_owned()); + } + }); + } + } + } +} + +fn ty_matches_prim(ty: Ty<'_>, prim: rustc_hir::PrimTy) -> bool { + match prim { + rustc_hir::PrimTy::Bool => ty.is_bool(), + rustc_hir::PrimTy::Char => ty.is_char(), + rustc_hir::PrimTy::Str => ty.is_str(), + rustc_hir::PrimTy::Int(i) => matches!(ty.kind(), ty::Int(ti) if *ti == i), + rustc_hir::PrimTy::Uint(u) => matches!(ty.kind(), ty::Uint(tu) if *tu == u), + rustc_hir::PrimTy::Float(f) => matches!(ty.kind(), ty::Float(tf) if *tf == f), + } +} + +fn format_trait_to_diagnostic_sym(format_trait: FormatTrait) -> Symbol { + match format_trait { + FormatTrait::Display => rustc_span::sym::Display, + FormatTrait::Debug => rustc_span::sym::Debug, + FormatTrait::LowerExp => sym::LowerExp, + FormatTrait::UpperExp => sym::UpperExp, + FormatTrait::Octal => sym::Octal, + FormatTrait::Pointer => rustc_span::sym::Pointer, + FormatTrait::Binary => sym::Binary, + FormatTrait::LowerHex => sym::LowerHex, + FormatTrait::UpperHex => sym::UpperHex, + } +} + +impl<'tcx> LateLintPass<'tcx> for DisallowedTraitUsage { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if self.entries.is_empty() { + return; + } + + // Check format macro arguments + if let Some(macro_call) = root_macro_call_first_node(cx, expr) + && is_format_macro(cx, macro_call.def_id) + && let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) + { + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece + && let Ok(index) = placeholder.argument.index + && let Some(arg) = format_args.arguments.all_args().get(index) + && let Some(arg_expr) = find_format_arg_expr(expr, arg) + { + let diag_sym = format_trait_to_diagnostic_sym(placeholder.format_trait); + if let Some(trait_def_id) = cx.tcx.get_diagnostic_item(diag_sym) { + let ty = cx.typeck_results().expr_ty(arg_expr); + let report_span = placeholder.span.unwrap_or(arg_expr.span); + self.check_type_trait(cx, ty, trait_def_id, report_span); + } + } + } + return; + } + + // Check method calls where the method comes from a trait + if let ExprKind::MethodCall(name, receiver, _, _) = &expr.kind + && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + { + let method_parent = cx.tcx.parent(method_def_id); + if matches!(cx.tcx.def_kind(method_parent), DefKind::Trait) { + let ty = cx.typeck_results().expr_ty(receiver); + self.check_type_trait(cx, ty, method_parent, name.ident.span); + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 68a8f51e7f4d..08d0f2d547c1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -107,6 +107,7 @@ mod disallowed_macros; mod disallowed_methods; mod disallowed_names; mod disallowed_script_idents; +mod disallowed_trait_usage; mod disallowed_types; mod doc; mod double_parens; @@ -718,6 +719,16 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(|_| Box::new(bool_assert_comparison::BoolAssertComparison)), Box::new(|_| Box::::default()), Box::new(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))), + { + let format_args = format_args_storage.clone(); + Box::new(move |tcx| { + Box::new(disallowed_trait_usage::DisallowedTraitUsage::new( + tcx, + conf, + format_args.clone(), + )) + }) + }, Box::new(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))), Box::new(move |_| Box::new(strlen_on_c_strings::StrlenOnCStrings::new(conf))), Box::new(move |_| Box::new(self_named_constructors::SelfNamedConstructors)), diff --git a/tests/ui-toml/toml_disallowed_trait_usage/clippy.toml b/tests/ui-toml/toml_disallowed_trait_usage/clippy.toml new file mode 100644 index 000000000000..390af766324c --- /dev/null +++ b/tests/ui-toml/toml_disallowed_trait_usage/clippy.toml @@ -0,0 +1,9 @@ +disallowed-trait-usage = [ + { type = "i32", trait = "std::fmt::Debug", reason = "Use Display formatting instead" }, + { type = "std::path::PathBuf", trait = "std::fmt::Debug", reason = "Use path.display() instead" }, + { type = "std::path::Path", trait = "std::fmt::Debug", reason = "Use path.display() instead" }, + { type = "disallowed_trait_usage::MyStruct", trait = "disallowed_trait_usage::MyTrait", reason = "Use a different approach" }, + { implements = "std::error::Error", trait = "std::fmt::Debug", reason = "Use Display for errors" }, + # implements with a custom local trait, no reason + { implements = "disallowed_trait_usage::MyTrait", trait = "std::fmt::Debug" }, +] diff --git a/tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs b/tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs new file mode 100644 index 000000000000..5b058b848152 --- /dev/null +++ b/tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs @@ -0,0 +1,113 @@ +#![warn(clippy::disallowed_trait_usage)] +#![allow(clippy::io_other_error)] + +use std::path::{Path, PathBuf}; + +trait MyTrait { + fn do_thing(&self) -> i32; +} + +struct MyStruct; + +impl MyTrait for MyStruct { + fn do_thing(&self) -> i32 { + 42 + } +} + +impl std::fmt::Debug for MyStruct { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("MyStruct") + } +} + +struct OtherStruct; + +impl MyTrait for OtherStruct { + fn do_thing(&self) -> i32 { + 99 + } +} + +fn main() { + // === Concrete `type` matching === + + // Should trigger: Debug formatting of i32 + println!("{:?}", 42_i32); + //~^ disallowed_trait_usage + + // Should trigger: Debug formatting of PathBuf + let path = PathBuf::from("/tmp"); + println!("{path:?}"); + //~^ disallowed_trait_usage + + // Should trigger: Debug formatting of &Path (references are peeled) + let path_ref: &Path = path.as_path(); + println!("{path_ref:?}"); + //~^ disallowed_trait_usage + + // Should trigger: Debug formatting of i32 via format! + let _ = format!("{:?}", 0_i32); + //~^ disallowed_trait_usage + + // Should trigger: Debug formatting of PathBuf via write! + use std::fmt::Write; + let mut buf = String::new(); + write!(buf, "{path:?}").ok(); + //~^ disallowed_trait_usage + + // Should trigger: custom trait method call on custom type + let my = MyStruct; + my.do_thing(); + //~^ disallowed_trait_usage + + // Should trigger: custom trait method call via reference + let my_ref = &MyStruct; + my_ref.do_thing(); + //~^ disallowed_trait_usage + + // Should NOT trigger: Display formatting of i32 (only Debug is disallowed) + println!("{}", 42_i32); + + // Should NOT trigger: Debug formatting of String (not in config) + let s = String::from("hello"); + println!("{s:?}"); + + // Should NOT trigger: same custom trait on a different type (OtherStruct not in config) + let other = OtherStruct; + other.do_thing(); + + // === `implements` matching === + + // Should trigger: Debug formatting of std::io::Error (implements std::error::Error) + let err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone"); + println!("{err:?}"); + //~^ disallowed_trait_usage + + // Should trigger: Debug formatting of Error via format! + let _ = format!("{:?}", std::io::Error::new(std::io::ErrorKind::Other, "oops")); + //~^ disallowed_trait_usage + + // Should NOT trigger: Display formatting of Error (only Debug is disallowed) + println!("{err}"); + + // Should NOT trigger: Debug formatting of String (doesn't implement Error) + println!("{s:?}"); + + // Should trigger: Debug formatting of MyStruct (implements MyTrait, which is in `implements` + // config) + println!("{my:?}"); + //~^ disallowed_trait_usage + + // Should NOT trigger: OtherStruct implements MyTrait but doesn't impl Debug, + // so Debug formatting can't even be used on it (won't compile without this guard). + // Instead, test that Display of MyStruct doesn't trigger (only Debug is disallowed via + // `implements`). (MyStruct has no Display impl, so we test via the method call path instead.) + + // Should trigger: method call on a type matching `implements` — + // OtherStruct implements MyTrait, and MyTrait::do_thing is disallowed on MyStruct (via concrete + // `type`), but OtherStruct is NOT matched by the concrete `type` entry. However, it IS matched + // by the `implements = MyTrait` + `trait = Debug` entry — but that only covers Debug, not + // MyTrait methods. So this should NOT trigger. + other.do_thing(); +} diff --git a/tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.stderr b/tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.stderr new file mode 100644 index 000000000000..e50f7bf2af4c --- /dev/null +++ b/tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.stderr @@ -0,0 +1,82 @@ +error: use of `i32` via trait `std::fmt::Debug` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:36:15 + | +LL | println!("{:?}", 42_i32); + | ^^^^ + | + = note: Use Display formatting instead + = note: `-D clippy::disallowed-trait-usage` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::disallowed_trait_usage)]` + +error: use of `std::path::PathBuf` via trait `std::fmt::Debug` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:41:15 + | +LL | println!("{path:?}"); + | ^^^^^^^^ + | + = note: Use path.display() instead + +error: use of `std::path::Path` via trait `std::fmt::Debug` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:46:15 + | +LL | println!("{path_ref:?}"); + | ^^^^^^^^^^^^ + | + = note: Use path.display() instead + +error: use of `i32` via trait `std::fmt::Debug` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:50:22 + | +LL | let _ = format!("{:?}", 0_i32); + | ^^^^ + | + = note: Use Display formatting instead + +error: use of `std::path::PathBuf` via trait `std::fmt::Debug` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:56:18 + | +LL | write!(buf, "{path:?}").ok(); + | ^^^^^^^^ + | + = note: Use path.display() instead + +error: use of `disallowed_trait_usage::MyStruct` via trait `disallowed_trait_usage::MyTrait` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:61:8 + | +LL | my.do_thing(); + | ^^^^^^^^ + | + = note: Use a different approach + +error: use of `disallowed_trait_usage::MyStruct` via trait `disallowed_trait_usage::MyTrait` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:66:12 + | +LL | my_ref.do_thing(); + | ^^^^^^^^ + | + = note: Use a different approach + +error: use of implementor of `std::error::Error` via trait `std::fmt::Debug` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:84:15 + | +LL | println!("{err:?}"); + | ^^^^^^^ + | + = note: Use Display for errors + +error: use of implementor of `std::error::Error` via trait `std::fmt::Debug` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:88:22 + | +LL | let _ = format!("{:?}", std::io::Error::new(std::io::ErrorKind::Other, "oops")); + | ^^^^ + | + = note: Use Display for errors + +error: use of implementor of `disallowed_trait_usage::MyTrait` via trait `std::fmt::Debug` is disallowed + --> tests/ui-toml/toml_disallowed_trait_usage/disallowed_trait_usage.rs:99:15 + | +LL | println!("{my:?}"); + | ^^^^^^ + +error: aborting due to 10 previous errors + diff --git a/tests/ui-toml/toml_disallowed_trait_usage_invalid/clippy.toml b/tests/ui-toml/toml_disallowed_trait_usage_invalid/clippy.toml new file mode 100644 index 000000000000..ea86a812af05 --- /dev/null +++ b/tests/ui-toml/toml_disallowed_trait_usage_invalid/clippy.toml @@ -0,0 +1,14 @@ +disallowed-trait-usage = [ + # Non-existent type + { type = "std::nonexistent::FakeType", trait = "std::fmt::Debug", reason = "testing" }, + # Non-existent trait + { type = "i32", trait = "std::nonexistent::FakeTrait", reason = "testing" }, + # Path exists but is a function, not a type + { type = "std::mem::swap", trait = "std::fmt::Debug", reason = "testing" }, + # Path exists but is a struct, not a trait + { type = "i32", trait = "std::string::String", reason = "testing" }, + # Both type and implements specified (mutually exclusive) + { type = "i32", implements = "std::error::Error", trait = "std::fmt::Debug", reason = "testing" }, + # Neither type nor implements specified + { trait = "std::fmt::Debug", reason = "testing" }, +] diff --git a/tests/ui-toml/toml_disallowed_trait_usage_invalid/disallowed_trait_usage_invalid.rs b/tests/ui-toml/toml_disallowed_trait_usage_invalid/disallowed_trait_usage_invalid.rs new file mode 100644 index 000000000000..7905f94c34b6 --- /dev/null +++ b/tests/ui-toml/toml_disallowed_trait_usage_invalid/disallowed_trait_usage_invalid.rs @@ -0,0 +1,13 @@ +//@error-in-other-file: `std::nonexistent::FakeType` does not refer to a reachable type +//@error-in-other-file: `std::nonexistent::FakeTrait` does not refer to a reachable trait +//@error-in-other-file: expected a type, found a function: `std::mem::swap` +//@error-in-other-file: expected a trait, found a struct: `std::string::String` +//@error-in-other-file: `type` and `implements` are mutually exclusive +//@error-in-other-file: either `type` or `implements` must be specified + +#![warn(clippy::disallowed_trait_usage)] + +fn main() { + // None of these should trigger since all config entries are invalid + println!("{:?}", 42_i32); +} diff --git a/tests/ui-toml/toml_disallowed_trait_usage_invalid/disallowed_trait_usage_invalid.stderr b/tests/ui-toml/toml_disallowed_trait_usage_invalid/disallowed_trait_usage_invalid.stderr new file mode 100644 index 000000000000..1607e19006da --- /dev/null +++ b/tests/ui-toml/toml_disallowed_trait_usage_invalid/disallowed_trait_usage_invalid.stderr @@ -0,0 +1,14 @@ +warning: `std::nonexistent::FakeType` does not refer to a reachable type (in `disallowed-trait-usage`) + +warning: `std::nonexistent::FakeTrait` does not refer to a reachable trait (in `disallowed-trait-usage`) + +warning: expected a type, found a function: `std::mem::swap` (in `disallowed-trait-usage`) + +warning: expected a trait, found a struct: `std::string::String` (in `disallowed-trait-usage`) + +warning: `type` and `implements` are mutually exclusive (in `disallowed-trait-usage`) + +warning: either `type` or `implements` must be specified (in `disallowed-trait-usage`) + +warning: 6 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..bb1bdcc7d21d 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -42,6 +42,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect disallowed-macros disallowed-methods disallowed-names + disallowed-trait-usage disallowed-types doc-valid-idents enable-raw-pointer-heuristic-for-send @@ -143,6 +144,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect disallowed-macros disallowed-methods disallowed-names + disallowed-trait-usage disallowed-types doc-valid-idents enable-raw-pointer-heuristic-for-send @@ -244,6 +246,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni disallowed-macros disallowed-methods disallowed-names + disallowed-trait-usage disallowed-types doc-valid-idents enable-raw-pointer-heuristic-for-send