Skip to content

Commit 0ff9540

Browse files
authored
Two improvements to disallowed_* (#13669)
The improvements are as follows: - Remove an [unnecessary `compile-flags` directive](https://github.com/rust-lang/rust-clippy/blob/f712eb5cdccd121d0569af12f20e6a0fabe4364d/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs#L1) in `tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs` - Support replacements as suggested by @mitsuhiko in #7609 (comment) --- changelog: support replacements in `disallowed_methods`
2 parents 8cc596c + 7c94744 commit 0ff9540

19 files changed

+222
-71
lines changed

Diff for: clippy_config/src/conf.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::ClippyConfiguration;
22
use crate::types::{
3-
DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering,
4-
SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind,
5-
SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
3+
DisallowedPath, DisallowedPathWithoutReplacement, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour,
4+
Rename, SourceItemOrdering, SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings,
5+
SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
66
};
77
use clippy_utils::msrvs::Msrv;
88
use rustc_errors::Applicability;
@@ -448,7 +448,7 @@ define_Conf! {
448448
avoid_breaking_exported_api: bool = true,
449449
/// The list of types which may not be held across an await point.
450450
#[lints(await_holding_invalid_type)]
451-
await_holding_invalid_types: Vec<DisallowedPath> = Vec::new(),
451+
await_holding_invalid_types: Vec<DisallowedPathWithoutReplacement> = Vec::new(),
452452
/// DEPRECATED LINT: BLACKLISTED_NAME.
453453
///
454454
/// Use the Disallowed Names lint instead

Diff for: clippy_config/src/types.rs

+74-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use clippy_utils::def_path_def_ids;
2+
use rustc_errors::{Applicability, Diag};
23
use rustc_hir::def_id::DefIdMap;
34
use rustc_middle::ty::TyCtxt;
5+
use rustc_span::Span;
46
use serde::de::{self, Deserializer, Visitor};
57
use serde::{Deserialize, Serialize, ser};
68
use std::collections::HashMap;
@@ -12,37 +14,99 @@ pub struct Rename {
1214
pub rename: String,
1315
}
1416

15-
#[derive(Debug, Deserialize)]
17+
pub type DisallowedPathWithoutReplacement = DisallowedPath<false>;
18+
19+
#[derive(Debug, Serialize)]
20+
pub struct DisallowedPath<const REPLACEMENT_ALLOWED: bool = true> {
21+
path: String,
22+
reason: Option<String>,
23+
replacement: Option<String>,
24+
}
25+
26+
impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<REPLACEMENT_ALLOWED> {
27+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
28+
where
29+
D: Deserializer<'de>,
30+
{
31+
let enum_ = DisallowedPathEnum::deserialize(deserializer)?;
32+
if !REPLACEMENT_ALLOWED && enum_.replacement().is_some() {
33+
return Err(de::Error::custom("replacement not allowed for this configuration"));
34+
}
35+
Ok(Self {
36+
path: enum_.path().to_owned(),
37+
reason: enum_.reason().map(ToOwned::to_owned),
38+
replacement: enum_.replacement().map(ToOwned::to_owned),
39+
})
40+
}
41+
}
42+
43+
// `DisallowedPathEnum` is an implementation detail to enable the `Deserialize` implementation just
44+
// above. `DisallowedPathEnum` is not meant to be used outside of this file.
45+
#[derive(Debug, Deserialize, Serialize)]
1646
#[serde(untagged)]
17-
pub enum DisallowedPath {
47+
enum DisallowedPathEnum {
1848
Simple(String),
19-
WithReason { path: String, reason: Option<String> },
49+
WithReason {
50+
path: String,
51+
reason: Option<String>,
52+
replacement: Option<String>,
53+
},
2054
}
2155

22-
impl DisallowedPath {
56+
impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
57+
pub fn path(&self) -> &str {
58+
&self.path
59+
}
60+
61+
pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) + use<'_, REPLACEMENT_ALLOWED> {
62+
move |diag| {
63+
if let Some(replacement) = &self.replacement {
64+
diag.span_suggestion(
65+
span,
66+
self.reason.as_ref().map_or_else(|| String::from("use"), Clone::clone),
67+
replacement,
68+
Applicability::MachineApplicable,
69+
);
70+
} else if let Some(reason) = &self.reason {
71+
diag.note(reason.clone());
72+
}
73+
}
74+
}
75+
}
76+
77+
impl DisallowedPathEnum {
2378
pub fn path(&self) -> &str {
2479
let (Self::Simple(path) | Self::WithReason { path, .. }) = self;
2580

2681
path
2782
}
2883

29-
pub fn reason(&self) -> Option<&str> {
84+
fn reason(&self) -> Option<&str> {
3085
match &self {
3186
Self::WithReason { reason, .. } => reason.as_deref(),
3287
Self::Simple(_) => None,
3388
}
3489
}
90+
91+
fn replacement(&self) -> Option<&str> {
92+
match &self {
93+
Self::WithReason { replacement, .. } => replacement.as_deref(),
94+
Self::Simple(_) => None,
95+
}
96+
}
3597
}
3698

3799
/// Creates a map of disallowed items to the reason they were disallowed.
38-
pub fn create_disallowed_map(
100+
pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
39101
tcx: TyCtxt<'_>,
40-
disallowed: &'static [DisallowedPath],
41-
) -> DefIdMap<(&'static str, Option<&'static str>)> {
102+
disallowed: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
103+
) -> DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> {
42104
disallowed
43105
.iter()
44-
.map(|x| (x.path(), x.path().split("::").collect::<Vec<_>>(), x.reason()))
45-
.flat_map(|(name, path, reason)| def_path_def_ids(tcx, &path).map(move |id| (id, (name, reason))))
106+
.map(|x| (x.path(), x.path().split("::").collect::<Vec<_>>(), x))
107+
.flat_map(|(name, path, disallowed_path)| {
108+
def_path_def_ids(tcx, &path).map(move |id| (id, (name, disallowed_path)))
109+
})
46110
.collect()
47111
}
48112

@@ -436,7 +500,6 @@ macro_rules! unimplemented_serialize {
436500
}
437501

438502
unimplemented_serialize! {
439-
DisallowedPath,
440503
Rename,
441504
MacroMatcher,
442505
}

Diff for: clippy_lints/src/await_holding_invalid.rs

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_config::Conf;
2-
use clippy_config::types::create_disallowed_map;
2+
use clippy_config::types::{DisallowedPathWithoutReplacement, create_disallowed_map};
33
use clippy_utils::diagnostics::span_lint_and_then;
44
use clippy_utils::{match_def_path, paths};
55
use rustc_hir as hir;
@@ -174,7 +174,7 @@ declare_clippy_lint! {
174174
impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]);
175175

176176
pub struct AwaitHolding {
177-
def_ids: DefIdMap<(&'static str, Option<&'static str>)>,
177+
def_ids: DefIdMap<(&'static str, &'static DisallowedPathWithoutReplacement)>,
178178
}
179179

180180
impl AwaitHolding {
@@ -247,25 +247,26 @@ impl AwaitHolding {
247247
);
248248
},
249249
);
250-
} else if let Some(&(path, reason)) = self.def_ids.get(&adt.did()) {
251-
emit_invalid_type(cx, ty_cause.source_info.span, path, reason);
250+
} else if let Some(&(path, disallowed_path)) = self.def_ids.get(&adt.did()) {
251+
emit_invalid_type(cx, ty_cause.source_info.span, path, disallowed_path);
252252
}
253253
}
254254
}
255255
}
256256
}
257257

258-
fn emit_invalid_type(cx: &LateContext<'_>, span: Span, path: &'static str, reason: Option<&'static str>) {
258+
fn emit_invalid_type(
259+
cx: &LateContext<'_>,
260+
span: Span,
261+
path: &'static str,
262+
disallowed_path: &'static DisallowedPathWithoutReplacement,
263+
) {
259264
span_lint_and_then(
260265
cx,
261266
AWAIT_HOLDING_INVALID_TYPE,
262267
span,
263268
format!("holding a disallowed type across an await point `{path}`"),
264-
|diag| {
265-
if let Some(reason) = reason {
266-
diag.note(reason);
267-
}
268-
},
269+
disallowed_path.diag_amendment(span),
269270
);
270271
}
271272

Diff for: clippy_lints/src/disallowed_macros.rs

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use clippy_config::Conf;
2-
use clippy_config::types::create_disallowed_map;
2+
use clippy_config::types::{DisallowedPath, create_disallowed_map};
33
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
44
use clippy_utils::macros::macro_backtrace;
55
use rustc_data_structures::fx::FxHashSet;
6-
use rustc_errors::Diag;
76
use rustc_hir::def_id::DefIdMap;
87
use rustc_hir::{
98
AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty,
@@ -59,7 +58,7 @@ declare_clippy_lint! {
5958
}
6059

6160
pub struct DisallowedMacros {
62-
disallowed: DefIdMap<(&'static str, Option<&'static str>)>,
61+
disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>,
6362
seen: FxHashSet<ExpnId>,
6463
// Track the most recently seen node that can have a `derive` attribute.
6564
// Needed to use the correct lint level.
@@ -90,13 +89,9 @@ impl DisallowedMacros {
9089
return;
9190
}
9291

93-
if let Some(&(path, reason)) = self.disallowed.get(&mac.def_id) {
92+
if let Some(&(path, disallowed_path)) = self.disallowed.get(&mac.def_id) {
9493
let msg = format!("use of a disallowed macro `{path}`");
95-
let add_note = |diag: &mut Diag<'_, _>| {
96-
if let Some(reason) = reason {
97-
diag.note(reason);
98-
}
99-
};
94+
let add_note = disallowed_path.diag_amendment(mac.span);
10095
if matches!(mac.kind, MacroKind::Derive)
10196
&& let Some(derive_src) = derive_src
10297
{

Diff for: clippy_lints/src/disallowed_methods.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_config::Conf;
2-
use clippy_config::types::create_disallowed_map;
2+
use clippy_config::types::{DisallowedPath, create_disallowed_map};
33
use clippy_utils::diagnostics::span_lint_and_then;
44
use rustc_hir::def::{CtorKind, DefKind, Res};
55
use rustc_hir::def_id::DefIdMap;
@@ -31,6 +31,8 @@ declare_clippy_lint! {
3131
/// # When using an inline table, can add a `reason` for why the method
3232
/// # is disallowed.
3333
/// { path = "std::vec::Vec::leak", reason = "no leaking memory" },
34+
/// # Can also add a `replacement` that will be offered as a suggestion.
35+
/// { path = "std::sync::Mutex::new", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex::new" },
3436
/// ]
3537
/// ```
3638
///
@@ -56,7 +58,7 @@ declare_clippy_lint! {
5658
}
5759

5860
pub struct DisallowedMethods {
59-
disallowed: DefIdMap<(&'static str, Option<&'static str>)>,
61+
disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>,
6062
}
6163

6264
impl DisallowedMethods {
@@ -83,17 +85,13 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
8385
},
8486
_ => return,
8587
};
86-
if let Some(&(path, reason)) = self.disallowed.get(&id) {
88+
if let Some(&(path, disallowed_path)) = self.disallowed.get(&id) {
8789
span_lint_and_then(
8890
cx,
8991
DISALLOWED_METHODS,
9092
span,
9193
format!("use of a disallowed method `{path}`"),
92-
|diag| {
93-
if let Some(reason) = reason {
94-
diag.note(reason);
95-
}
96-
},
94+
disallowed_path.diag_amendment(span),
9795
);
9896
}
9997
}

Diff for: clippy_lints/src/disallowed_types.rs

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clippy_config::Conf;
2+
use clippy_config::types::DisallowedPath;
23
use clippy_utils::diagnostics::span_lint_and_then;
34
use rustc_data_structures::fx::FxHashMap;
45
use rustc_hir::def::Res;
@@ -31,6 +32,8 @@ declare_clippy_lint! {
3132
/// # When using an inline table, can add a `reason` for why the type
3233
/// # is disallowed.
3334
/// { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
35+
/// # Can also add a `replacement` that will be offered as a suggestion.
36+
/// { path = "std::sync::Mutex", reason = "prefer faster & simpler non-poisonable mutex", replacement = "parking_lot::Mutex" },
3437
/// ]
3538
/// ```
3639
///
@@ -51,24 +54,23 @@ declare_clippy_lint! {
5154
}
5255

5356
pub struct DisallowedTypes {
54-
def_ids: DefIdMap<(&'static str, Option<&'static str>)>,
55-
prim_tys: FxHashMap<PrimTy, (&'static str, Option<&'static str>)>,
57+
def_ids: DefIdMap<(&'static str, &'static DisallowedPath)>,
58+
prim_tys: FxHashMap<PrimTy, (&'static str, &'static DisallowedPath)>,
5659
}
5760

5861
impl DisallowedTypes {
5962
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
6063
let mut def_ids = DefIdMap::default();
6164
let mut prim_tys = FxHashMap::default();
62-
for x in &conf.disallowed_types {
63-
let path: Vec<_> = x.path().split("::").collect::<Vec<_>>();
64-
let reason = x.reason();
65+
for disallowed_path in &conf.disallowed_types {
66+
let path: Vec<_> = disallowed_path.path().split("::").collect::<Vec<_>>();
6567
for res in clippy_utils::def_path_res(tcx, &path) {
6668
match res {
6769
Res::Def(_, id) => {
68-
def_ids.insert(id, (x.path(), reason));
70+
def_ids.insert(id, (disallowed_path.path(), disallowed_path));
6971
},
7072
Res::PrimTy(ty) => {
71-
prim_tys.insert(ty, (x.path(), reason));
73+
prim_tys.insert(ty, (disallowed_path.path(), disallowed_path));
7274
},
7375
_ => {},
7476
}
@@ -78,7 +80,7 @@ impl DisallowedTypes {
7880
}
7981

8082
fn check_res_emit(&self, cx: &LateContext<'_>, res: &Res, span: Span) {
81-
let (path, reason) = match res {
83+
let (path, disallowed_path) = match res {
8284
Res::Def(_, did) if let Some(&x) = self.def_ids.get(did) => x,
8385
Res::PrimTy(prim) if let Some(&x) = self.prim_tys.get(prim) => x,
8486
_ => return,
@@ -88,11 +90,7 @@ impl DisallowedTypes {
8890
DISALLOWED_TYPES,
8991
span,
9092
format!("use of a disallowed type `{path}`"),
91-
|diag| {
92-
if let Some(reason) = reason {
93-
diag.note(reason);
94-
}
95-
},
93+
disallowed_path.diag_amendment(span),
9694
);
9795
}
9896
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: error reading Clippy's configuration file: replacement not allowed for this configuration
2+
--> $DIR/tests/ui-toml/await_holding_invalid_type_with_replacement/clippy.toml:1:31
3+
|
4+
LL | await-holding-invalid-types = [
5+
| _______________________________^
6+
LL | | { path = "std::string::String", replacement = "std::net::Ipv4Addr" },
7+
LL | | ]
8+
| |_^
9+
10+
error: aborting due to 1 previous error
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
await-holding-invalid-types = [
2+
{ path = "std::string::String", replacement = "std::net::Ipv4Addr" },
3+
]
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
disallowed-types = [
2+
{ path = "std::string::String", replacement = "wrapper::String" },
3+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#![warn(clippy::disallowed_types)]
2+
3+
#[allow(clippy::disallowed_types)]
4+
mod wrapper {
5+
pub struct String(std::string::String);
6+
7+
impl From<&str> for String {
8+
fn from(value: &str) -> Self {
9+
Self(std::string::String::from(value))
10+
}
11+
}
12+
}
13+
14+
fn main() {
15+
let _ = wrapper::String::from("x");
16+
}

0 commit comments

Comments
 (0)