about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_config/src/conf.rs8
-rw-r--r--clippy_config/src/types.rs85
-rw-r--r--clippy_lints/src/await_holding_invalid.rs21
-rw-r--r--clippy_lints/src/disallowed_macros.rs13
-rw-r--r--clippy_lints/src/disallowed_methods.rs14
-rw-r--r--clippy_lints/src/disallowed_types.rs24
-rw-r--r--tests/ui-toml/await_holding_invalid_type_with_replacement/await_holding_invalid_type.rs1
-rw-r--r--tests/ui-toml/await_holding_invalid_type_with_replacement/await_holding_invalid_type.stderr11
-rw-r--r--tests/ui-toml/await_holding_invalid_type_with_replacement/clippy.toml3
-rw-r--r--tests/ui-toml/replaceable_disallowed_types/clippy.toml3
-rw-r--r--tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.fixed16
-rw-r--r--tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.rs16
-rw-r--r--tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.stderr11
-rw-r--r--tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs2
-rw-r--r--tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr28
-rw-r--r--tests/ui-toml/toml_replaceable_disallowed_methods/clippy.toml4
-rw-r--r--tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.fixed8
-rw-r--r--tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.rs8
-rw-r--r--tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.stderr17
19 files changed, 222 insertions, 71 deletions
diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs
index a356584f973..10a17da1517 100644
--- a/clippy_config/src/conf.rs
+++ b/clippy_config/src/conf.rs
@@ -1,8 +1,8 @@
 use crate::ClippyConfiguration;
 use crate::types::{
-    DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering,
-    SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind,
-    SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
+    DisallowedPath, DisallowedPathWithoutReplacement, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour,
+    Rename, SourceItemOrdering, SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings,
+    SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
 };
 use clippy_utils::msrvs::Msrv;
 use rustc_errors::Applicability;
@@ -448,7 +448,7 @@ define_Conf! {
     avoid_breaking_exported_api: bool = true,
     /// The list of types which may not be held across an await point.
     #[lints(await_holding_invalid_type)]
-    await_holding_invalid_types: Vec<DisallowedPath> = Vec::new(),
+    await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
     /// DEPRECATED LINT: BLACKLISTED_NAME.
     ///
     /// Use the Disallowed Names lint instead
diff --git a/clippy_config/src/types.rs b/clippy_config/src/types.rs
index c949db9109d..c72291e9799 100644
--- a/clippy_config/src/types.rs
+++ b/clippy_config/src/types.rs
@@ -1,6 +1,8 @@
 use clippy_utils::def_path_def_ids;
+use rustc_errors::{Applicability, Diag};
 use rustc_hir::def_id::DefIdMap;
 use rustc_middle::ty::TyCtxt;
+use rustc_span::Span;
 use serde::de::{self, Deserializer, Visitor};
 use serde::{Deserialize, Serialize, ser};
 use std::collections::HashMap;
@@ -12,37 +14,99 @@ pub struct Rename {
     pub rename: String,
 }
 
-#[derive(Debug, Deserialize)]
+pub type DisallowedPathWithoutReplacement = DisallowedPath<false>;
+
+#[derive(Debug, Serialize)]
+pub struct DisallowedPath<const REPLACEMENT_ALLOWED: bool = true> {
+    path: String,
+    reason: Option<String>,
+    replacement: Option<String>,
+}
+
+impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<REPLACEMENT_ALLOWED> {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let enum_ = DisallowedPathEnum::deserialize(deserializer)?;
+        if !REPLACEMENT_ALLOWED && enum_.replacement().is_some() {
+            return Err(de::Error::custom("replacement not allowed for this configuration"));
+        }
+        Ok(Self {
+            path: enum_.path().to_owned(),
+            reason: enum_.reason().map(ToOwned::to_owned),
+            replacement: enum_.replacement().map(ToOwned::to_owned),
+        })
+    }
+}
+
+// `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)]
 #[serde(untagged)]
-pub enum DisallowedPath {
+enum DisallowedPathEnum {
     Simple(String),
-    WithReason { path: String, reason: Option<String> },
+    WithReason {
+        path: String,
+        reason: Option<String>,
+        replacement: Option<String>,
+    },
 }
 
-impl DisallowedPath {
+impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
+    pub fn path(&self) -> &str {
+        &self.path
+    }
+
+    pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) + use<'_, REPLACEMENT_ALLOWED> {
+        move |diag| {
+            if let Some(replacement) = &self.replacement {
+                diag.span_suggestion(
+                    span,
+                    self.reason.as_ref().map_or_else(|| String::from("use"), Clone::clone),
+                    replacement,
+                    Applicability::MachineApplicable,
+                );
+            } else if let Some(reason) = &self.reason {
+                diag.note(reason.clone());
+            }
+        }
+    }
+}
+
+impl DisallowedPathEnum {
     pub fn path(&self) -> &str {
         let (Self::Simple(path) | Self::WithReason { path, .. }) = self;
 
         path
     }
 
-    pub fn reason(&self) -> Option<&str> {
+    fn reason(&self) -> Option<&str> {
         match &self {
             Self::WithReason { reason, .. } => reason.as_deref(),
             Self::Simple(_) => None,
         }
     }
+
+    fn replacement(&self) -> Option<&str> {
+        match &self {
+            Self::WithReason { replacement, .. } => replacement.as_deref(),
+            Self::Simple(_) => None,
+        }
+    }
 }
 
 /// Creates a map of disallowed items to the reason they were disallowed.
-pub fn create_disallowed_map(
+pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
     tcx: TyCtxt<'_>,
-    disallowed: &'static [DisallowedPath],
-) -> DefIdMap<(&'static str, Option<&'static str>)> {
+    disallowed: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
+) -> DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> {
     disallowed
         .iter()
-        .map(|x| (x.path(), x.path().split("::").collect::<Vec<_>>(), x.reason()))
-        .flat_map(|(name, path, reason)| def_path_def_ids(tcx, &path).map(move |id| (id, (name, reason))))
+        .map(|x| (x.path(), x.path().split("::").collect::<Vec<_>>(), x))
+        .flat_map(|(name, path, disallowed_path)| {
+            def_path_def_ids(tcx, &path).map(move |id| (id, (name, disallowed_path)))
+        })
         .collect()
 }
 
@@ -436,7 +500,6 @@ macro_rules! unimplemented_serialize {
 }
 
 unimplemented_serialize! {
-    DisallowedPath,
     Rename,
     MacroMatcher,
 }
diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs
index 2eb0566bf9a..92a0c7f9acb 100644
--- a/clippy_lints/src/await_holding_invalid.rs
+++ b/clippy_lints/src/await_holding_invalid.rs
@@ -1,5 +1,5 @@
 use clippy_config::Conf;
-use clippy_config::types::create_disallowed_map;
+use clippy_config::types::{DisallowedPathWithoutReplacement, create_disallowed_map};
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::{match_def_path, paths};
 use rustc_hir as hir;
@@ -174,7 +174,7 @@ declare_clippy_lint! {
 impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]);
 
 pub struct AwaitHolding {
-    def_ids: DefIdMap<(&'static str, Option<&'static str>)>,
+    def_ids: DefIdMap<(&'static str, &'static DisallowedPathWithoutReplacement)>,
 }
 
 impl AwaitHolding {
@@ -247,25 +247,26 @@ impl AwaitHolding {
                             );
                         },
                     );
-                } else if let Some(&(path, reason)) = self.def_ids.get(&adt.did()) {
-                    emit_invalid_type(cx, ty_cause.source_info.span, path, reason);
+                } else if let Some(&(path, disallowed_path)) = self.def_ids.get(&adt.did()) {
+                    emit_invalid_type(cx, ty_cause.source_info.span, path, disallowed_path);
                 }
             }
         }
     }
 }
 
-fn emit_invalid_type(cx: &LateContext<'_>, span: Span, path: &'static str, reason: Option<&'static str>) {
+fn emit_invalid_type(
+    cx: &LateContext<'_>,
+    span: Span,
+    path: &'static str,
+    disallowed_path: &'static DisallowedPathWithoutReplacement,
+) {
     span_lint_and_then(
         cx,
         AWAIT_HOLDING_INVALID_TYPE,
         span,
         format!("holding a disallowed type across an await point `{path}`"),
-        |diag| {
-            if let Some(reason) = reason {
-                diag.note(reason);
-            }
-        },
+        disallowed_path.diag_amendment(span),
     );
 }
 
diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs
index 4e8853821c3..22310bc0798 100644
--- a/clippy_lints/src/disallowed_macros.rs
+++ b/clippy_lints/src/disallowed_macros.rs
@@ -1,9 +1,8 @@
 use clippy_config::Conf;
-use clippy_config::types::create_disallowed_map;
+use clippy_config::types::{DisallowedPath, create_disallowed_map};
 use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
 use clippy_utils::macros::macro_backtrace;
 use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::Diag;
 use rustc_hir::def_id::DefIdMap;
 use rustc_hir::{
     AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty,
@@ -59,7 +58,7 @@ declare_clippy_lint! {
 }
 
 pub struct DisallowedMacros {
-    disallowed: DefIdMap<(&'static str, Option<&'static str>)>,
+    disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>,
     seen: FxHashSet<ExpnId>,
     // Track the most recently seen node that can have a `derive` attribute.
     // Needed to use the correct lint level.
@@ -90,13 +89,9 @@ impl DisallowedMacros {
                 return;
             }
 
-            if let Some(&(path, reason)) = self.disallowed.get(&mac.def_id) {
+            if let Some(&(path, disallowed_path)) = self.disallowed.get(&mac.def_id) {
                 let msg = format!("use of a disallowed macro `{path}`");
-                let add_note = |diag: &mut Diag<'_, _>| {
-                    if let Some(reason) = reason {
-                        diag.note(reason);
-                    }
-                };
+                let add_note = disallowed_path.diag_amendment(mac.span);
                 if matches!(mac.kind, MacroKind::Derive)
                     && let Some(derive_src) = derive_src
                 {
diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs
index c4ed118b7c9..149cf1cf2de 100644
--- a/clippy_lints/src/disallowed_methods.rs
+++ b/clippy_lints/src/disallowed_methods.rs
@@ -1,5 +1,5 @@
 use clippy_config::Conf;
-use clippy_config::types::create_disallowed_map;
+use clippy_config::types::{DisallowedPath, create_disallowed_map};
 use clippy_utils::diagnostics::span_lint_and_then;
 use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::def_id::DefIdMap;
@@ -31,6 +31,8 @@ declare_clippy_lint! {
     ///     # When using an inline table, can add a `reason` for why the method
     ///     # is disallowed.
     ///     { path = "std::vec::Vec::leak", reason = "no leaking memory" },
+    ///     # Can also add a `replacement` that will be offered as a suggestion.
+    ///     { path = "std::sync::Mutex::new", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex::new" },
     /// ]
     /// ```
     ///
@@ -56,7 +58,7 @@ declare_clippy_lint! {
 }
 
 pub struct DisallowedMethods {
-    disallowed: DefIdMap<(&'static str, Option<&'static str>)>,
+    disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>,
 }
 
 impl DisallowedMethods {
@@ -83,17 +85,13 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
             },
             _ => return,
         };
-        if let Some(&(path, reason)) = self.disallowed.get(&id) {
+        if let Some(&(path, disallowed_path)) = self.disallowed.get(&id) {
             span_lint_and_then(
                 cx,
                 DISALLOWED_METHODS,
                 span,
                 format!("use of a disallowed method `{path}`"),
-                |diag| {
-                    if let Some(reason) = reason {
-                        diag.note(reason);
-                    }
-                },
+                disallowed_path.diag_amendment(span),
             );
         }
     }
diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs
index 947677e14bd..3659946b704 100644
--- a/clippy_lints/src/disallowed_types.rs
+++ b/clippy_lints/src/disallowed_types.rs
@@ -1,4 +1,5 @@
 use clippy_config::Conf;
+use clippy_config::types::DisallowedPath;
 use clippy_utils::diagnostics::span_lint_and_then;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_hir::def::Res;
@@ -31,6 +32,8 @@ declare_clippy_lint! {
     ///     # When using an inline table, can add a `reason` for why the type
     ///     # is disallowed.
     ///     { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
+    ///     # Can also add a `replacement` that will be offered as a suggestion.
+    ///     { path = "std::sync::Mutex", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex" },
     /// ]
     /// ```
     ///
@@ -51,24 +54,23 @@ declare_clippy_lint! {
 }
 
 pub struct DisallowedTypes {
-    def_ids: DefIdMap<(&'static str, Option<&'static str>)>,
-    prim_tys: FxHashMap<PrimTy, (&'static str, Option<&'static str>)>,
+    def_ids: DefIdMap<(&'static str, &'static DisallowedPath)>,
+    prim_tys: FxHashMap<PrimTy, (&'static str, &'static DisallowedPath)>,
 }
 
 impl DisallowedTypes {
     pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
         let mut def_ids = DefIdMap::default();
         let mut prim_tys = FxHashMap::default();
-        for x in &conf.disallowed_types {
-            let path: Vec<_> = x.path().split("::").collect::<Vec<_>>();
-            let reason = x.reason();
+        for disallowed_path in &conf.disallowed_types {
+            let path: Vec<_> = disallowed_path.path().split("::").collect::<Vec<_>>();
             for res in clippy_utils::def_path_res(tcx, &path) {
                 match res {
                     Res::Def(_, id) => {
-                        def_ids.insert(id, (x.path(), reason));
+                        def_ids.insert(id, (disallowed_path.path(), disallowed_path));
                     },
                     Res::PrimTy(ty) => {
-                        prim_tys.insert(ty, (x.path(), reason));
+                        prim_tys.insert(ty, (disallowed_path.path(), disallowed_path));
                     },
                     _ => {},
                 }
@@ -78,7 +80,7 @@ impl DisallowedTypes {
     }
 
     fn check_res_emit(&self, cx: &LateContext<'_>, res: &Res, span: Span) {
-        let (path, reason) = match res {
+        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,
@@ -88,11 +90,7 @@ impl DisallowedTypes {
             DISALLOWED_TYPES,
             span,
             format!("use of a disallowed type `{path}`"),
-            |diag| {
-                if let Some(reason) = reason {
-                    diag.note(reason);
-                }
-            },
+            disallowed_path.diag_amendment(span),
         );
     }
 }
diff --git a/tests/ui-toml/await_holding_invalid_type_with_replacement/await_holding_invalid_type.rs b/tests/ui-toml/await_holding_invalid_type_with_replacement/await_holding_invalid_type.rs
new file mode 100644
index 00000000000..f328e4d9d04
--- /dev/null
+++ b/tests/ui-toml/await_holding_invalid_type_with_replacement/await_holding_invalid_type.rs
@@ -0,0 +1 @@
+fn main() {}
diff --git a/tests/ui-toml/await_holding_invalid_type_with_replacement/await_holding_invalid_type.stderr b/tests/ui-toml/await_holding_invalid_type_with_replacement/await_holding_invalid_type.stderr
new file mode 100644
index 00000000000..86e30409af0
--- /dev/null
+++ b/tests/ui-toml/await_holding_invalid_type_with_replacement/await_holding_invalid_type.stderr
@@ -0,0 +1,11 @@
+error: error reading Clippy's configuration file: replacement not allowed for this configuration
+  --> $DIR/tests/ui-toml/await_holding_invalid_type_with_replacement/clippy.toml:1:31
+   |
+LL |   await-holding-invalid-types = [
+   |  _______________________________^
+LL | |     { path = "std::string::String", replacement = "std::net::Ipv4Addr" },
+LL | | ]
+   | |_^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui-toml/await_holding_invalid_type_with_replacement/clippy.toml b/tests/ui-toml/await_holding_invalid_type_with_replacement/clippy.toml
new file mode 100644
index 00000000000..f6bc59672ed
--- /dev/null
+++ b/tests/ui-toml/await_holding_invalid_type_with_replacement/clippy.toml
@@ -0,0 +1,3 @@
+await-holding-invalid-types = [
+    { path = "std::string::String", replacement = "std::net::Ipv4Addr" },
+]
diff --git a/tests/ui-toml/replaceable_disallowed_types/clippy.toml b/tests/ui-toml/replaceable_disallowed_types/clippy.toml
new file mode 100644
index 00000000000..a08a2f00f50
--- /dev/null
+++ b/tests/ui-toml/replaceable_disallowed_types/clippy.toml
@@ -0,0 +1,3 @@
+disallowed-types = [
+    { path = "std::string::String", replacement = "wrapper::String" },
+]
diff --git a/tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.fixed b/tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.fixed
new file mode 100644
index 00000000000..6546981bd81
--- /dev/null
+++ b/tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.fixed
@@ -0,0 +1,16 @@
+#![warn(clippy::disallowed_types)]
+
+#[allow(clippy::disallowed_types)]
+mod wrapper {
+    pub struct String(std::string::String);
+
+    impl From<&str> for String {
+        fn from(value: &str) -> Self {
+            Self(std::string::String::from(value))
+        }
+    }
+}
+
+fn main() {
+    let _ = wrapper::String::from("x");
+}
diff --git a/tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.rs b/tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.rs
new file mode 100644
index 00000000000..d76f1af481d
--- /dev/null
+++ b/tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.rs
@@ -0,0 +1,16 @@
+#![warn(clippy::disallowed_types)]
+
+#[allow(clippy::disallowed_types)]
+mod wrapper {
+    pub struct String(std::string::String);
+
+    impl From<&str> for String {
+        fn from(value: &str) -> Self {
+            Self(std::string::String::from(value))
+        }
+    }
+}
+
+fn main() {
+    let _ = String::from("x");
+}
diff --git a/tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.stderr b/tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.stderr
new file mode 100644
index 00000000000..bb63e6970a1
--- /dev/null
+++ b/tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.stderr
@@ -0,0 +1,11 @@
+error: use of a disallowed type `std::string::String`
+  --> tests/ui-toml/replaceable_disallowed_types/replaceable_disallowed_types.rs:15:13
+   |
+LL |     let _ = String::from("x");
+   |             ^^^^^^ help: use: `wrapper::String`
+   |
+   = note: `-D clippy::disallowed-types` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::disallowed_types)]`
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs
index 17fceae0178..0986290bb0e 100644
--- a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs
+++ b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs
@@ -1,5 +1,3 @@
-//@compile-flags: --crate-name conf_disallowed_methods
-
 #![allow(clippy::needless_raw_strings)]
 #![warn(clippy::disallowed_methods)]
 #![allow(clippy::useless_vec)]
diff --git a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr
index e77b2b95949..edda35d647a 100644
--- a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr
+++ b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr
@@ -1,5 +1,5 @@
 error: use of a disallowed method `regex::Regex::new`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:35:14
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:33:14
    |
 LL |     let re = Regex::new(r"ab.*c").unwrap();
    |              ^^^^^^^^^^
@@ -8,7 +8,7 @@ LL |     let re = Regex::new(r"ab.*c").unwrap();
    = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
 
 error: use of a disallowed method `regex::Regex::is_match`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:36:8
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:34:8
    |
 LL |     re.is_match("abc");
    |        ^^^^^^^^
@@ -16,73 +16,73 @@ LL |     re.is_match("abc");
    = note: no matching allowed
 
 error: use of a disallowed method `std::iter::Iterator::sum`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:39:14
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:37:14
    |
 LL |     a.iter().sum::<i32>();
    |              ^^^
 
 error: use of a disallowed method `slice::sort_unstable`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:41:7
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:39:7
    |
 LL |     a.sort_unstable();
    |       ^^^^^^^^^^^^^
 
 error: use of a disallowed method `f32::clamp`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:44:20
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:42:20
    |
 LL |     let _ = 2.0f32.clamp(3.0f32, 4.0f32);
    |                    ^^^^^
 
 error: use of a disallowed method `regex::Regex::new`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:47:61
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:45:61
    |
 LL |     let indirect: fn(&str) -> Result<Regex, regex::Error> = Regex::new;
    |                                                             ^^^^^^^^^^
 
 error: use of a disallowed method `f32::clamp`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:50:28
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:48:28
    |
 LL |     let in_call = Box::new(f32::clamp);
    |                            ^^^^^^^^^^
 
 error: use of a disallowed method `regex::Regex::new`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:51:53
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:49:53
    |
 LL |     let in_method_call = ["^", "$"].into_iter().map(Regex::new);
    |                                                     ^^^^^^^^^^
 
 error: use of a disallowed method `futures::stream::select_all`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:54:31
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:52:31
    |
 LL |     let same_name_as_module = select_all(vec![empty::<()>()]);
    |                               ^^^^^^^^^^
 
 error: use of a disallowed method `conf_disallowed_methods::local_fn`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:56:5
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:54:5
    |
 LL |     local_fn();
    |     ^^^^^^^^
 
 error: use of a disallowed method `conf_disallowed_methods::local_mod::f`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:57:5
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:55:5
    |
 LL |     local_mod::f();
    |     ^^^^^^^^^^^^
 
 error: use of a disallowed method `conf_disallowed_methods::Struct::method`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:59:7
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:57:7
    |
 LL |     s.method();
    |       ^^^^^^
 
 error: use of a disallowed method `conf_disallowed_methods::Trait::provided_method`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:60:7
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:58:7
    |
 LL |     s.provided_method();
    |       ^^^^^^^^^^^^^^^
 
 error: use of a disallowed method `conf_disallowed_methods::Trait::implemented_method`
-  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:61:7
+  --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:59:7
    |
 LL |     s.implemented_method();
    |       ^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui-toml/toml_replaceable_disallowed_methods/clippy.toml b/tests/ui-toml/toml_replaceable_disallowed_methods/clippy.toml
new file mode 100644
index 00000000000..dc393f1355b
--- /dev/null
+++ b/tests/ui-toml/toml_replaceable_disallowed_methods/clippy.toml
@@ -0,0 +1,4 @@
+disallowed-methods = [
+    { path = "replaceable_disallowed_methods::bad", replacement = "good" },
+    { path = "replaceable_disallowed_methods::questionable", replacement = "good", reason = "a better function exists" },
+]
diff --git a/tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.fixed b/tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.fixed
new file mode 100644
index 00000000000..dae7ce76ba2
--- /dev/null
+++ b/tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.fixed
@@ -0,0 +1,8 @@
+fn bad() {}
+fn questionable() {}
+fn good() {}
+
+fn main() {
+    good();
+    good();
+}
diff --git a/tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.rs b/tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.rs
new file mode 100644
index 00000000000..53678ffdf1c
--- /dev/null
+++ b/tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.rs
@@ -0,0 +1,8 @@
+fn bad() {}
+fn questionable() {}
+fn good() {}
+
+fn main() {
+    bad();
+    questionable();
+}
diff --git a/tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.stderr b/tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.stderr
new file mode 100644
index 00000000000..b8559202942
--- /dev/null
+++ b/tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.stderr
@@ -0,0 +1,17 @@
+error: use of a disallowed method `replaceable_disallowed_methods::bad`
+  --> tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.rs:6:5
+   |
+LL |     bad();
+   |     ^^^ help: use: `good`
+   |
+   = 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 `replaceable_disallowed_methods::questionable`
+  --> tests/ui-toml/toml_replaceable_disallowed_methods/replaceable_disallowed_methods.rs:7:5
+   |
+LL |     questionable();
+   |     ^^^^^^^^^^^^ help: a better function exists: `good`
+
+error: aborting due to 2 previous errors
+