@@ -7,6 +7,7 @@ use crate::types::{
77} ;
88use clippy_utils:: msrvs:: Msrv ;
99use itertools:: Itertools ;
10+ use rustc_data_structures:: fx:: FxHashMap ;
1011use rustc_errors:: Applicability ;
1112use rustc_session:: Session ;
1213use 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+
224283macro_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+
344487define_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