Skip to content

Commit c86fcd0

Browse files
committed
Add profile-specific configuration for disallowed methods and types
1 parent 63c7197 commit c86fcd0

16 files changed

Lines changed: 899 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7110,8 +7110,10 @@ Released 2018-09-13
71107110
[`const-literal-digits-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#const-literal-digits-threshold
71117111
[`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros
71127112
[`disallowed-methods`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods
7113+
[`disallowed-methods-profiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods-profiles
71137114
[`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names
71147115
[`disallowed-types`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-types
7116+
[`disallowed-types-profiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-types-profiles
71157117
[`doc-valid-idents`]: https://doc.rust-lang.org/clippy/lint_configuration.html#doc-valid-idents
71167118
[`enable-raw-pointer-heuristic-for-send`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enable-raw-pointer-heuristic-for-send
71177119
[`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow

book/src/lint_configuration.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,30 @@ The list of disallowed methods, written as fully qualified paths.
529529
* [`disallowed_methods`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods)
530530

531531

532+
## `disallowed-methods-profiles`
533+
Named profiles of disallowed methods, keyed by profile name.
534+
535+
#### Example
536+
537+
```toml
538+
[disallowed-methods-profiles.forward_pass]
539+
paths = [
540+
{ path = "crate::io::DeviceBuffer::copy_to_host", reason = "Forward code stays on the device" }
541+
]
542+
543+
[disallowed-methods-profiles.export]
544+
paths = [
545+
{ path = "crate::io::DeviceBuffer::into_host_slice" }
546+
]
547+
```
548+
549+
**Default Value:** `{}`
550+
551+
---
552+
**Affected lints:**
553+
* [`disallowed_methods`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods)
554+
555+
532556
## `disallowed-names`
533557
The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
534558
`".."` can be used as part of the list to indicate that the configured values should be appended to the
@@ -558,6 +582,25 @@ The list of disallowed types, written as fully qualified paths.
558582
* [`disallowed_types`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types)
559583

560584

585+
## `disallowed-types-profiles`
586+
Named profiles of disallowed types, keyed by profile name.
587+
588+
#### Example
589+
590+
```toml
591+
[disallowed-types-profiles.forward_pass]
592+
paths = [
593+
{ path = "crate::io::HostBuffer", reason = "Prefer device buffers" }
594+
]
595+
```
596+
597+
**Default Value:** `{}`
598+
599+
---
600+
**Affected lints:**
601+
* [`disallowed_types`](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types)
602+
603+
561604
## `doc-valid-idents`
562605
The list of words this lint should not consider as identifiers needing ticks. The value
563606
`".."` can be used as part of the list to indicate, that the configured values should be appended to the

clippy_config/src/conf.rs

Lines changed: 176 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::types::{
77
};
88
use clippy_utils::msrvs::Msrv;
99
use itertools::Itertools;
10+
use rustc_data_structures::fx::FxHashMap;
1011
use rustc_errors::Applicability;
1112
use rustc_session::Session;
1213
use rustc_span::edit_distance::edit_distance;
@@ -221,12 +222,71 @@ macro_rules! deserialize {
221222
}};
222223
}
223224

225+
macro_rules! parse_conf_value {
226+
(
227+
$map:expr,
228+
$ty:ty,
229+
$errors:expr,
230+
$file:expr,
231+
$field_span:expr,
232+
profile @[$($profile:expr)?],
233+
disallowed @[$($disallowed:expr)?]
234+
) => {
235+
parse_conf_value_impl!(
236+
$map,
237+
$ty,
238+
$errors,
239+
$file,
240+
$field_span,
241+
($($profile)?),
242+
($($disallowed)?)
243+
)
244+
};
245+
}
246+
247+
macro_rules! parse_conf_value_impl {
248+
($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ()) => {
249+
deserialize!($map, $ty, $errors, $file)
250+
};
251+
($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profile:expr), ()) => {
252+
deserialize_profiles!($map, $errors, $file, $field_span, $profile)
253+
};
254+
($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, (), ($disallowed:expr)) => {
255+
deserialize!($map, $ty, $errors, $file, $disallowed)
256+
};
257+
($map:expr, $ty:ty, $errors:expr, $file:expr, $field_span:expr, ($profile:expr), ($disallowed:expr)) => {
258+
compile_error!("field cannot specify both disallowed profile table and disallowed path attributes")
259+
};
260+
}
261+
262+
macro_rules! deserialize_profiles {
263+
($map:expr, $errors:expr, $file:expr, $field_span:expr, $replacements_allowed:expr) => {{
264+
let raw_value = $map.next_value::<toml::Value>()?;
265+
let value_span = $field_span.clone();
266+
let toml::Value::Table(table) = raw_value else {
267+
$errors.push(ConfError::spanned(
268+
$file,
269+
"expected table with named profiles",
270+
None,
271+
value_span.clone(),
272+
));
273+
continue;
274+
};
275+
276+
let map =
277+
parse_disallowed_profiles::<{ $replacements_allowed }>(table, $file, value_span.clone(), &mut $errors);
278+
279+
(map, value_span)
280+
}};
281+
}
282+
224283
macro_rules! define_Conf {
225284
($(
226285
$(#[doc = $doc:literal])+
227286
$(#[conf_deprecated($dep:literal, $new_conf:ident)])?
228287
$(#[default_text = $default_text:expr])?
229288
$(#[disallowed_paths_allow_replacements = $replacements_allowed:expr])?
289+
$(#[disallowed_paths_profile(replacements_allowed = $profile_replacements_allowed:expr)])?
230290
$(#[lints($($for_lints:ident),* $(,)?)])?
231291
$name:ident: $ty:ty = $default:expr,
232292
)*) => {
@@ -281,10 +341,18 @@ macro_rules! define_Conf {
281341

282342
match field {
283343
$(Field::$name => {
344+
let _field_span = name.span();
284345
// Is this a deprecated field, i.e., is `$dep` set? If so, push a warning.
285346
$(warnings.push(ConfError::spanned(self.0, format!("deprecated field `{}`. {}", name.get_ref(), $dep), None, name.span()));)?
286-
let (value, value_span) =
287-
deserialize!(map, $ty, errors, self.0 $(, $replacements_allowed)?);
347+
let (value, value_span) = parse_conf_value!(
348+
map,
349+
$ty,
350+
errors,
351+
self.0,
352+
_field_span,
353+
profile @[$($profile_replacements_allowed)?],
354+
disallowed @[$($replacements_allowed)?]
355+
);
288356
// Was this field set previously?
289357
if $name.is_some() {
290358
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<usize>) -> Span {
341409
)
342410
}
343411

412+
fn parse_disallowed_profiles<const REPLACEMENT_ALLOWED: bool>(
413+
table: toml::value::Table,
414+
file: &SourceFile,
415+
value_span: Range<usize>,
416+
errors: &mut Vec<ConfError>,
417+
) -> FxHashMap<String, Vec<DisallowedPath<REPLACEMENT_ALLOWED>>> {
418+
let mut profiles = FxHashMap::default();
419+
let config_span = span_from_toml_range(file, value_span.clone());
420+
421+
for (profile_name, profile_value) in table {
422+
let toml::Value::Table(mut profile_table) = profile_value else {
423+
errors.push(ConfError::spanned(
424+
file,
425+
format!("invalid profile `{profile_name}`: expected table with `paths` entry"),
426+
None,
427+
value_span.clone(),
428+
));
429+
continue;
430+
};
431+
432+
let Some(paths_value) = profile_table.remove("paths") else {
433+
errors.push(ConfError::spanned(
434+
file,
435+
format!("profile `{profile_name}` missing `paths` entry"),
436+
None,
437+
value_span.clone(),
438+
));
439+
continue;
440+
};
441+
442+
if !profile_table.is_empty() {
443+
let keys = profile_table.keys().map(String::as_str).collect::<Vec<_>>().join(", ");
444+
errors.push(ConfError::spanned(
445+
file,
446+
format!("profile `{profile_name}` has unknown keys: {keys}"),
447+
None,
448+
value_span.clone(),
449+
));
450+
}
451+
452+
let toml::Value::Array(entries) = paths_value else {
453+
errors.push(ConfError::spanned(
454+
file,
455+
format!("profile `{profile_name}`: `paths` must be an array"),
456+
None,
457+
value_span.clone(),
458+
));
459+
continue;
460+
};
461+
462+
let mut disallowed = Vec::with_capacity(entries.len());
463+
for entry in entries {
464+
match DisallowedPath::<REPLACEMENT_ALLOWED>::deserialize(entry.clone()) {
465+
Ok(mut path) => {
466+
path.set_span(config_span);
467+
disallowed.push(path);
468+
},
469+
Err(err) => errors.push(ConfError::spanned(
470+
file,
471+
format!(
472+
"profile `{profile_name}`: {}",
473+
err.to_string().replace('\n', " ").trim()
474+
),
475+
None,
476+
value_span.clone(),
477+
)),
478+
}
479+
}
480+
481+
profiles.insert(profile_name, disallowed);
482+
}
483+
484+
profiles
485+
}
486+
344487
define_Conf! {
345488
/// Which crates to allow absolute paths from
346489
#[lints(absolute_paths)]
@@ -600,6 +743,24 @@ define_Conf! {
600743
#[disallowed_paths_allow_replacements = true]
601744
#[lints(disallowed_methods)]
602745
disallowed_methods: Vec<DisallowedPath> = Vec::new(),
746+
/// Named profiles of disallowed methods, keyed by profile name.
747+
///
748+
/// #### Example
749+
///
750+
/// ```toml
751+
/// [disallowed-methods-profiles.forward_pass]
752+
/// paths = [
753+
/// { path = "crate::io::DeviceBuffer::copy_to_host", reason = "Forward code stays on the device" }
754+
/// ]
755+
///
756+
/// [disallowed-methods-profiles.export]
757+
/// paths = [
758+
/// { path = "crate::io::DeviceBuffer::into_host_slice" }
759+
/// ]
760+
/// ```
761+
#[disallowed_paths_profile(replacements_allowed = true)]
762+
#[lints(disallowed_methods)]
763+
disallowed_methods_profiles: FxHashMap<String, Vec<DisallowedPath>> = FxHashMap::default(),
603764
/// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
604765
/// `".."` can be used as part of the list to indicate that the configured values should be appended to the
605766
/// default configuration of Clippy. By default, any configuration will replace the default value.
@@ -616,6 +777,19 @@ define_Conf! {
616777
#[disallowed_paths_allow_replacements = true]
617778
#[lints(disallowed_types)]
618779
disallowed_types: Vec<DisallowedPath> = Vec::new(),
780+
/// Named profiles of disallowed types, keyed by profile name.
781+
///
782+
/// #### Example
783+
///
784+
/// ```toml
785+
/// [disallowed-types-profiles.forward_pass]
786+
/// paths = [
787+
/// { path = "crate::io::HostBuffer", reason = "Prefer device buffers" }
788+
/// ]
789+
/// ```
790+
#[disallowed_paths_profile(replacements_allowed = true)]
791+
#[lints(disallowed_types)]
792+
disallowed_types_profiles: FxHashMap<String, Vec<DisallowedPath>> = FxHashMap::default(),
619793
/// The list of words this lint should not consider as identifiers needing ticks. The value
620794
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
621795
/// default configuration of Clippy. By default, any configuration will replace the default value. For example:

0 commit comments

Comments
 (0)