@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
3
3
use std:: ops:: { Deref , DerefMut } ;
4
4
use std:: sync:: LazyLock ;
5
5
6
+ use itertools:: Itertools ;
6
7
use private:: Sealed ;
7
8
use rustc_ast:: { self as ast, LitKind , MetaItemLit , NodeId } ;
8
9
use rustc_errors:: { DiagCtxtHandle , Diagnostic } ;
@@ -61,8 +62,11 @@ use crate::attributes::traits::{
61
62
} ;
62
63
use crate :: attributes:: transparency:: TransparencyParser ;
63
64
use crate :: attributes:: { AttributeParser as _, Combine , Single , WithoutArgs } ;
65
+ use crate :: context:: MaybeWarn :: { Allow , Error , Warn } ;
64
66
use crate :: parser:: { ArgParser , MetaItemParser , PathParser } ;
65
- use crate :: session_diagnostics:: { AttributeParseError , AttributeParseErrorReason , UnknownMetaItem } ;
67
+ use crate :: session_diagnostics:: {
68
+ AttributeParseError , AttributeParseErrorReason , InvalidTarget , UnknownMetaItem ,
69
+ } ;
66
70
67
71
type GroupType < S > = LazyLock < GroupTypeInner < S > > ;
68
72
@@ -74,6 +78,7 @@ struct GroupTypeInner<S: Stage> {
74
78
struct GroupTypeInnerAccept < S : Stage > {
75
79
template : AttributeTemplate ,
76
80
accept_fn : AcceptFn < S > ,
81
+ allowed_targets : AllowedTargets ,
77
82
}
78
83
79
84
type AcceptFn < S > =
@@ -121,7 +126,8 @@ macro_rules! attribute_parsers {
121
126
STATE_OBJECT . with_borrow_mut( |s| {
122
127
accept_fn( s, cx, args)
123
128
} )
124
- } )
129
+ } ) ,
130
+ allowed_targets: <$names as crate :: attributes:: AttributeParser <$stage>>:: ALLOWED_TARGETS ,
125
131
} ) ;
126
132
}
127
133
@@ -641,6 +647,67 @@ impl ShouldEmit {
641
647
}
642
648
}
643
649
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
+
644
711
/// Context created once, for example as part of the ast lowering
645
712
/// context, through which all attributes can be lowered.
646
713
pub struct AttributeParser < ' sess , S : Stage = Late > {
@@ -848,7 +915,52 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
848
915
attr_path : path. get_attribute_path ( ) ,
849
916
} ;
850
917
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
+ }
852
964
}
853
965
} else {
854
966
// If we're here, we must be compiling a tool attribute... Or someone
@@ -936,6 +1048,77 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
936
1048
}
937
1049
}
938
1050
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. plural_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
+
939
1122
/// Parse a single integer.
940
1123
///
941
1124
/// Used by attributes that take a single integer as argument, such as
0 commit comments