@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
33use std:: ops:: { Deref , DerefMut } ;
44use std:: sync:: LazyLock ;
55
6+ use itertools:: Itertools ;
67use private:: Sealed ;
78use rustc_ast:: { self as ast, LitKind , MetaItemLit , NodeId } ;
89use rustc_errors:: { DiagCtxtHandle , Diagnostic } ;
@@ -61,8 +62,11 @@ use crate::attributes::traits::{
6162} ;
6263use crate :: attributes:: transparency:: TransparencyParser ;
6364use crate :: attributes:: { AttributeParser as _, Combine , Single , WithoutArgs } ;
65+ use crate :: context:: MaybeWarn :: { Allow , Error , Warn } ;
6466use crate :: parser:: { ArgParser , MetaItemParser , PathParser } ;
65- use crate :: session_diagnostics:: { AttributeParseError , AttributeParseErrorReason , UnknownMetaItem } ;
67+ use crate :: session_diagnostics:: {
68+ AttributeParseError , AttributeParseErrorReason , InvalidTarget , UnknownMetaItem ,
69+ } ;
6670
6771type GroupType < S > = LazyLock < GroupTypeInner < S > > ;
6872
@@ -74,6 +78,7 @@ struct GroupTypeInner<S: Stage> {
7478struct GroupTypeInnerAccept < S : Stage > {
7579 template : AttributeTemplate ,
7680 accept_fn : AcceptFn < S > ,
81+ allowed_targets : AllowedTargets ,
7782}
7883
7984type AcceptFn < S > =
@@ -121,7 +126,8 @@ macro_rules! attribute_parsers {
121126 STATE_OBJECT . with_borrow_mut( |s| {
122127 accept_fn( s, cx, args)
123128 } )
124- } )
129+ } ) ,
130+ allowed_targets: <$names as crate :: attributes:: AttributeParser <$stage>>:: ALLOWED_TARGETS ,
125131 } ) ;
126132 }
127133
@@ -641,6 +647,67 @@ impl ShouldEmit {
641647 }
642648}
643649
650+ #[ derive( Debug ) ]
651+ pub ( crate ) enum AllowedTargets {
652+ AllowAll ,
653+ AllowList ( & ' static [ MaybeWarn ] ) ,
654+ AllowListWarnRest ( & ' static [ MaybeWarn ] ) ,
655+ }
656+
657+ pub ( crate ) enum AllowedResult {
658+ Allowed ,
659+ Warn ,
660+ Error ,
661+ }
662+
663+ impl AllowedTargets {
664+ pub ( crate ) fn is_allowed ( & self , target : Target ) -> AllowedResult {
665+ match self {
666+ AllowedTargets :: AllowAll => AllowedResult :: Allowed ,
667+ AllowedTargets :: AllowList ( list) => {
668+ if list. contains ( & Allow ( target) ) {
669+ AllowedResult :: Allowed
670+ } else if list. contains ( & Warn ( target) ) {
671+ AllowedResult :: Warn
672+ } else {
673+ AllowedResult :: Error
674+ }
675+ }
676+ AllowedTargets :: AllowListWarnRest ( list) => {
677+ if list. contains ( & Allow ( target) ) {
678+ AllowedResult :: Allowed
679+ } else if list. contains ( & Error ( target) ) {
680+ AllowedResult :: Error
681+ } else {
682+ AllowedResult :: Warn
683+ }
684+ }
685+ }
686+ }
687+
688+ pub ( crate ) fn allowed_targets ( & self ) -> Vec < Target > {
689+ match self {
690+ AllowedTargets :: AllowAll => unreachable ! ( ) ,
691+ AllowedTargets :: AllowList ( list) => list,
692+ AllowedTargets :: AllowListWarnRest ( list) => list,
693+ }
694+ . iter ( )
695+ . filter_map ( |target| match target {
696+ Allow ( target) => Some ( * target) ,
697+ Warn ( _) => None ,
698+ Error ( _) => None ,
699+ } )
700+ . collect ( )
701+ }
702+ }
703+
704+ #[ derive( Debug , Eq , PartialEq ) ]
705+ pub ( crate ) enum MaybeWarn {
706+ Allow ( Target ) ,
707+ Warn ( Target ) ,
708+ Error ( Target ) ,
709+ }
710+
644711/// Context created once, for example as part of the ast lowering
645712/// context, through which all attributes can be lowered.
646713pub struct AttributeParser < ' sess , S : Stage = Late > {
@@ -848,7 +915,52 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
848915 attr_path : path. get_attribute_path ( ) ,
849916 } ;
850917
851- ( accept. accept_fn ) ( & mut cx, args)
918+ ( accept. accept_fn ) ( & mut cx, args) ;
919+
920+ if self . stage . should_emit ( ) . should_emit ( ) {
921+ match accept. allowed_targets . is_allowed ( target) {
922+ AllowedResult :: Allowed => { }
923+ AllowedResult :: Warn => {
924+ let allowed_targets =
925+ accept. allowed_targets . allowed_targets ( ) ;
926+ emit_lint ( AttributeLint {
927+ id : target_id,
928+ span : attr. span ,
929+ kind : AttributeLintKind :: InvalidTarget {
930+ name : parts[ 0 ] ,
931+ target : target. plural_name ( ) ,
932+ only : if allowed_targets. len ( ) == 1 {
933+ "only "
934+ } else {
935+ ""
936+ } ,
937+ applied : allowed_targets_applied (
938+ allowed_targets,
939+ target,
940+ ) ,
941+ } ,
942+ } ) ;
943+ }
944+ AllowedResult :: Error => {
945+ let allowed_targets =
946+ accept. allowed_targets . allowed_targets ( ) ;
947+ self . dcx ( ) . emit_err ( InvalidTarget {
948+ span : attr. span ,
949+ name : parts[ 0 ] ,
950+ target : target. plural_name ( ) ,
951+ only : if allowed_targets. len ( ) == 1 {
952+ "only "
953+ } else {
954+ ""
955+ } ,
956+ applied : allowed_targets_applied (
957+ allowed_targets,
958+ target,
959+ ) ,
960+ } ) ;
961+ }
962+ }
963+ }
852964 }
853965 } else {
854966 // If we're here, we must be compiling a tool attribute... Or someone
@@ -936,6 +1048,77 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
9361048 }
9371049}
9381050
1051+ /// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to.
1052+ /// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string
1053+ pub ( crate ) fn allowed_targets_applied ( mut allowed_targets : Vec < Target > , target : Target ) -> String {
1054+ // We define groups of "similar" targets.
1055+ // If at least two of the targets are allowed, and the `target` is not in the group,
1056+ // we collapse the entire group to a single entry to simplify the target list
1057+ const FUNCTION_LIKE : & [ Target ] = & [
1058+ Target :: Fn ,
1059+ Target :: Closure ,
1060+ Target :: ForeignFn ,
1061+ Target :: Method ( MethodKind :: Inherent ) ,
1062+ Target :: Method ( MethodKind :: Trait { body : false } ) ,
1063+ Target :: Method ( MethodKind :: Trait { body : true } ) ,
1064+ Target :: Method ( MethodKind :: TraitImpl ) ,
1065+ ] ;
1066+ const METHOD_LIKE : & [ Target ] = & [
1067+ Target :: Method ( MethodKind :: Inherent ) ,
1068+ Target :: Method ( MethodKind :: Trait { body : false } ) ,
1069+ Target :: Method ( MethodKind :: Trait { body : true } ) ,
1070+ Target :: Method ( MethodKind :: TraitImpl ) ,
1071+ ] ;
1072+ const IMPL_LIKE : & [ Target ] =
1073+ & [ Target :: Impl { of_trait : false } , Target :: Impl { of_trait : true } ] ;
1074+ const ADT_LIKE : & [ Target ] = & [ Target :: Struct , Target :: Enum ] ;
1075+
1076+ let mut added_fake_targets = Vec :: new ( ) ;
1077+ filter_targets (
1078+ & mut allowed_targets,
1079+ FUNCTION_LIKE ,
1080+ "functions" ,
1081+ target,
1082+ & mut added_fake_targets,
1083+ ) ;
1084+ filter_targets ( & mut allowed_targets, METHOD_LIKE , "methods" , target, & mut added_fake_targets) ;
1085+ filter_targets ( & mut allowed_targets, IMPL_LIKE , "impl blocks" , target, & mut added_fake_targets) ;
1086+ filter_targets ( & mut allowed_targets, ADT_LIKE , "data types" , target, & mut added_fake_targets) ;
1087+
1088+ // If there is now only 1 target left, show that as the only possible target
1089+ if allowed_targets. len ( ) + added_fake_targets. len ( ) == 1 {
1090+ return allowed_targets
1091+ . get ( 0 )
1092+ . map ( |t| t. singular_name ( ) )
1093+ . or ( added_fake_targets. get ( 0 ) . copied ( ) )
1094+ . unwrap ( )
1095+ . to_string ( ) ;
1096+ }
1097+
1098+ added_fake_targets
1099+ . iter ( )
1100+ . copied ( )
1101+ . chain ( allowed_targets. iter ( ) . map ( |t| t. plural_name ( ) ) )
1102+ . join ( ", " )
1103+ }
1104+
1105+ fn filter_targets (
1106+ allowed_targets : & mut Vec < Target > ,
1107+ target_group : & ' static [ Target ] ,
1108+ target_group_name : & ' static str ,
1109+ target : Target ,
1110+ added_fake_targets : & mut Vec < & ' static str > ,
1111+ ) {
1112+ if target_group. contains ( & target) {
1113+ return ;
1114+ }
1115+ if allowed_targets. iter ( ) . filter ( |at| target_group. contains ( at) ) . count ( ) < 2 {
1116+ return ;
1117+ }
1118+ allowed_targets. retain ( |t| !target_group. contains ( t) ) ;
1119+ added_fake_targets. push ( target_group_name) ;
1120+ }
1121+
9391122/// Parse a single integer.
9401123///
9411124/// Used by attributes that take a single integer as argument, such as
0 commit comments