1
- use proc_macro2:: TokenStream ;
2
- use quote:: { quote, ToTokens } ;
3
- use syn:: { parse_quote, Attribute , Meta , NestedMeta } ;
1
+ use proc_macro2:: { Span , TokenStream } ;
2
+ use quote:: { quote, quote_spanned, ToTokens } ;
3
+ use std:: collections:: HashMap ;
4
+ use syn:: { parse_quote, spanned:: Spanned , Attribute , Generics , Ident , Lifetime , LifetimeDef } ;
5
+
6
+ /// Generate a type parameter with the given `suffix` that does not conflict with
7
+ /// any of the `existing` generics.
8
+ fn gen_param ( suffix : impl ToString , existing : & Generics ) -> Ident {
9
+ let mut suffix = suffix. to_string ( ) ;
10
+ while existing. type_params ( ) . any ( |t| t. ident == suffix) {
11
+ suffix. insert ( 0 , '_' ) ;
12
+ }
13
+ Ident :: new ( & suffix, Span :: call_site ( ) )
14
+ }
15
+
16
+ /// Return the `TyCtxt` interner for the given `structure`.
17
+ ///
18
+ /// If the input represented by `structure` has a `'tcx` lifetime parameter, then that will be used
19
+ /// used as the lifetime of the `TyCtxt`. Otherwise a `'tcx` lifetime parameter that is unrelated
20
+ /// to the input will be used.
21
+ fn gen_interner ( structure : & mut synstructure:: Structure < ' _ > ) -> TokenStream {
22
+ let lt = structure
23
+ . ast ( )
24
+ . generics
25
+ . lifetimes ( )
26
+ . find_map ( |def| ( def. lifetime . ident == "tcx" ) . then_some ( & def. lifetime ) )
27
+ . cloned ( )
28
+ . unwrap_or_else ( || {
29
+ let tcx: Lifetime = parse_quote ! { ' tcx } ;
30
+ structure. add_impl_generic ( LifetimeDef :: new ( tcx. clone ( ) ) . into ( ) ) ;
31
+ tcx
32
+ } ) ;
33
+
34
+ quote ! { :: rustc_middle:: ty:: TyCtxt <#lt> }
35
+ }
36
+
37
+ /// Returns the `Span` of the first `#[skip_traversal]` attribute in `attrs`.
38
+ fn find_skip_traversal_attribute ( attrs : & [ Attribute ] ) -> Option < Span > {
39
+ attrs. iter ( ) . find ( |& attr| * attr == parse_quote ! { #[ skip_traversal] } ) . map ( Spanned :: span)
40
+ }
4
41
5
42
pub struct Foldable ;
6
43
pub struct Visitable ;
7
44
8
45
/// An abstraction over traversable traits.
9
46
pub trait Traversable {
10
- /// The trait that this `Traversable` represents.
11
- fn traversable ( ) -> TokenStream ;
47
+ /// The trait that this `Traversable` represents, parameterised by `interner`.
48
+ fn traversable ( interner : & impl ToTokens ) -> TokenStream ;
49
+
50
+ /// Any supertraits that this trait is required to implement.
51
+ fn supertraits ( interner : & impl ToTokens ) -> TokenStream ;
12
52
13
- /// The `match` arms for a traversal of this type .
14
- fn arms ( structure : & mut synstructure :: Structure < ' _ > ) -> TokenStream ;
53
+ /// A (`noop`) traversal of this trait upon the `bind` expression .
54
+ fn traverse ( bind : TokenStream , noop : bool ) -> TokenStream ;
15
55
16
- /// The body of an implementation given the match `arms`.
17
- fn impl_body ( arms : impl ToTokens ) -> TokenStream ;
56
+ /// A `match` arm for `variant`, where `f` generates the tokens for each binding.
57
+ fn arm (
58
+ variant : & synstructure:: VariantInfo < ' _ > ,
59
+ f : impl FnMut ( & synstructure:: BindingInfo < ' _ > ) -> TokenStream ,
60
+ ) -> TokenStream ;
61
+
62
+ /// The body of an implementation given the `interner`, `traverser` and match expression `body`.
63
+ fn impl_body (
64
+ interner : impl ToTokens ,
65
+ traverser : impl ToTokens ,
66
+ body : impl ToTokens ,
67
+ ) -> TokenStream ;
18
68
}
19
69
20
70
impl Traversable for Foldable {
21
- fn traversable ( ) -> TokenStream {
22
- quote ! { :: rustc_middle:: ty:: fold:: TypeFoldable <:: rustc_middle :: ty :: TyCtxt < ' tcx> > }
71
+ fn traversable ( interner : & impl ToTokens ) -> TokenStream {
72
+ quote ! { :: rustc_middle:: ty:: fold:: TypeFoldable <#interner > }
23
73
}
24
- fn arms ( structure : & mut synstructure:: Structure < ' _ > ) -> TokenStream {
25
- structure. each_variant ( |vi| {
26
- let bindings = vi. bindings ( ) ;
27
- vi. construct ( |_, index| {
28
- let bind = & bindings[ index] ;
29
-
30
- // retain value of fields with #[type_foldable(identity)]
31
- let fixed = bind
32
- . ast ( )
33
- . attrs
34
- . iter ( )
35
- . map ( Attribute :: parse_meta)
36
- . filter_map ( Result :: ok)
37
- . flat_map ( |attr| match attr {
38
- Meta :: List ( list) if list. path . is_ident ( "type_foldable" ) => list. nested ,
39
- _ => Default :: default ( ) ,
40
- } )
41
- . any ( |nested| match nested {
42
- NestedMeta :: Meta ( Meta :: Path ( path) ) => path. is_ident ( "identity" ) ,
43
- _ => false ,
44
- } ) ;
45
-
46
- if fixed {
47
- bind. to_token_stream ( )
48
- } else {
49
- quote ! {
50
- :: rustc_middle:: ty:: fold:: TypeFoldable :: try_fold_with( #bind, __folder) ?
51
- }
52
- }
53
- } )
54
- } )
74
+ fn supertraits ( interner : & impl ToTokens ) -> TokenStream {
75
+ Visitable :: traversable ( interner)
76
+ }
77
+ fn traverse ( bind : TokenStream , noop : bool ) -> TokenStream {
78
+ if noop {
79
+ bind
80
+ } else {
81
+ quote ! { :: rustc_middle:: ty:: prefer_noop_traversal_if_applicable!( #bind. try_fold_with( folder) ) ? }
82
+ }
83
+ }
84
+ fn arm (
85
+ variant : & synstructure:: VariantInfo < ' _ > ,
86
+ mut f : impl FnMut ( & synstructure:: BindingInfo < ' _ > ) -> TokenStream ,
87
+ ) -> TokenStream {
88
+ let bindings = variant. bindings ( ) ;
89
+ variant. construct ( |_, index| f ( & bindings[ index] ) )
55
90
}
56
- fn impl_body ( arms : impl ToTokens ) -> TokenStream {
91
+ fn impl_body (
92
+ interner : impl ToTokens ,
93
+ traverser : impl ToTokens ,
94
+ body : impl ToTokens ,
95
+ ) -> TokenStream {
57
96
quote ! {
58
- fn try_fold_with<__F : :: rustc_middle:: ty:: fold:: FallibleTypeFolder <:: rustc_middle :: ty :: TyCtxt < ' tcx> >>(
97
+ fn try_fold_with<#traverser : :: rustc_middle:: ty:: fold:: FallibleTypeFolder <#interner >>(
59
98
self ,
60
- __folder : & mut __F
61
- ) -> :: core:: result:: Result <Self , __F :: Error > {
62
- :: core:: result:: Result :: Ok ( match self { #arms } )
99
+ folder : & mut #traverser
100
+ ) -> :: core:: result:: Result <Self , #traverser :: Error > {
101
+ :: core:: result:: Result :: Ok ( #body )
63
102
}
64
103
}
65
104
}
66
105
}
67
106
68
107
impl Traversable for Visitable {
69
- fn traversable ( ) -> TokenStream {
70
- quote ! { :: rustc_middle:: ty:: visit:: TypeVisitable <:: rustc_middle :: ty :: TyCtxt < ' tcx> > }
108
+ fn traversable ( interner : & impl ToTokens ) -> TokenStream {
109
+ quote ! { :: rustc_middle:: ty:: visit:: TypeVisitable <#interner > }
71
110
}
72
- fn arms ( structure : & mut synstructure:: Structure < ' _ > ) -> TokenStream {
73
- // ignore fields with #[type_visitable(ignore)]
74
- structure. filter ( |bi| {
75
- !bi. ast ( )
76
- . attrs
77
- . iter ( )
78
- . map ( Attribute :: parse_meta)
79
- . filter_map ( Result :: ok)
80
- . flat_map ( |attr| match attr {
81
- Meta :: List ( list) if list. path . is_ident ( "type_visitable" ) => list. nested ,
82
- _ => Default :: default ( ) ,
83
- } )
84
- . any ( |nested| match nested {
85
- NestedMeta :: Meta ( Meta :: Path ( path) ) => path. is_ident ( "ignore" ) ,
86
- _ => false ,
87
- } )
88
- } ) ;
89
-
90
- structure. each ( |bind| {
91
- quote ! {
92
- :: rustc_middle:: ty:: visit:: TypeVisitable :: visit_with( #bind, __visitor) ?;
93
- }
94
- } )
111
+ fn supertraits ( _: & impl ToTokens ) -> TokenStream {
112
+ quote ! { :: core:: clone:: Clone + :: core:: fmt:: Debug }
113
+ }
114
+ fn traverse ( bind : TokenStream , noop : bool ) -> TokenStream {
115
+ if noop {
116
+ quote ! { }
117
+ } else {
118
+ quote ! { :: rustc_middle:: ty:: prefer_noop_traversal_if_applicable!( #bind. visit_with( visitor) ) ?; }
119
+ }
95
120
}
96
- fn impl_body ( arms : impl ToTokens ) -> TokenStream {
121
+ fn arm (
122
+ variant : & synstructure:: VariantInfo < ' _ > ,
123
+ f : impl FnMut ( & synstructure:: BindingInfo < ' _ > ) -> TokenStream ,
124
+ ) -> TokenStream {
125
+ variant. bindings ( ) . iter ( ) . map ( f) . collect ( )
126
+ }
127
+ fn impl_body (
128
+ interner : impl ToTokens ,
129
+ traverser : impl ToTokens ,
130
+ body : impl ToTokens ,
131
+ ) -> TokenStream {
97
132
quote ! {
98
- fn visit_with<__V : :: rustc_middle:: ty:: visit:: TypeVisitor <:: rustc_middle :: ty :: TyCtxt < ' tcx> >>(
133
+ fn visit_with<#traverser : :: rustc_middle:: ty:: visit:: TypeVisitor <#interner >>(
99
134
& self ,
100
- __visitor : & mut __V
101
- ) -> :: std :: ops:: ControlFlow <__V :: BreakTy > {
102
- match self { #arms }
103
- :: std :: ops:: ControlFlow :: Continue ( ( ) )
135
+ visitor : & mut #traverser
136
+ ) -> :: core :: ops:: ControlFlow <#traverser :: BreakTy > {
137
+ #body
138
+ :: core :: ops:: ControlFlow :: Continue ( ( ) )
104
139
}
105
140
}
106
141
}
@@ -109,17 +144,78 @@ impl Traversable for Visitable {
109
144
pub fn traversable_derive < T : Traversable > (
110
145
mut structure : synstructure:: Structure < ' _ > ,
111
146
) -> TokenStream {
112
- if let syn:: Data :: Union ( _) = structure. ast ( ) . data {
113
- panic ! ( "cannot derive on union" )
114
- }
147
+ let skip_traversal = quote ! { :: rustc_middle:: ty:: SkipTraversalAutoImplOnly } ;
148
+
149
+ let interner = gen_interner ( & mut structure) ;
150
+ let traverser = gen_param ( "T" , & structure. ast ( ) . generics ) ;
151
+ let traversable = T :: traversable ( & interner) ;
152
+ let ast = structure. ast ( ) ;
115
153
116
- structure. add_bounds ( synstructure:: AddBounds :: Generics ) ;
154
+ structure. add_bounds ( synstructure:: AddBounds :: None ) ;
117
155
structure. bind_with ( |_| synstructure:: BindStyle :: Move ) ;
118
156
119
- if !structure. ast ( ) . generics . lifetimes ( ) . any ( |lt| lt. lifetime . ident == "tcx" ) {
120
- structure. add_impl_generic ( parse_quote ! { ' tcx } ) ;
157
+ // If our derived implementation will be generic over the traversable type, then we must
158
+ // constrain it to only those generic combinations that satisfy the traversable trait's
159
+ // supertraits.
160
+ let is_generic = !ast. generics . params . is_empty ( ) ;
161
+ if is_generic {
162
+ let supertraits = T :: supertraits ( & interner) ;
163
+ structure. add_where_predicate ( parse_quote ! { Self : #supertraits } ) ;
121
164
}
122
165
123
- let arms = T :: arms ( & mut structure) ;
124
- structure. bound_impl ( T :: traversable ( ) , T :: impl_body ( arms) )
166
+ let body = if let Some ( _span) = find_skip_traversal_attribute ( & ast. attrs ) {
167
+ if !is_generic {
168
+ // FIXME: spanned error causes ICE: "suggestion must not have overlapping parts"
169
+ return quote ! ( {
170
+ :: core:: compile_error!( "non-generic types are automatically skipped where possible" ) ;
171
+ } ) ;
172
+ }
173
+ structure. add_where_predicate ( parse_quote ! { Self : #skip_traversal } ) ;
174
+ T :: traverse ( quote ! { self } , true )
175
+ } else {
176
+ // We add predicates to each generic field type, rather than to our generic type parameters.
177
+ // This results in a "perfect derive" that avoids having to propagate `#[skip_traversal]` annotations
178
+ // into wrapping types, but it can result in trait solver cycles if any type parameters are involved
179
+ // in recursive type definitions; fortunately that is not the case (yet).
180
+ let mut predicates = HashMap :: new ( ) ;
181
+ let arms = structure. each_variant ( |variant| {
182
+ let skipped_variant_span = find_skip_traversal_attribute ( & variant. ast ( ) . attrs ) ;
183
+ if variant. referenced_ty_params ( ) . is_empty ( ) {
184
+ if let Some ( span) = skipped_variant_span {
185
+ return quote_spanned ! ( span => {
186
+ :: core:: compile_error!( "non-generic variants are automatically skipped where possible" ) ;
187
+ } ) ;
188
+ }
189
+ }
190
+ T :: arm ( variant, |bind| {
191
+ let ast = bind. ast ( ) ;
192
+ let skipped_span = skipped_variant_span. or_else ( || find_skip_traversal_attribute ( & ast. attrs ) ) ;
193
+ if bind. referenced_ty_params ( ) . is_empty ( ) {
194
+ if skipped_variant_span. is_none ( ) && let Some ( span) = skipped_span {
195
+ return quote_spanned ! ( span => {
196
+ :: core:: compile_error!( "non-generic fields are automatically skipped where possible" ) ;
197
+ } ) ;
198
+ }
199
+ } else if let Some ( prev) = predicates. insert ( ast. ty . clone ( ) , skipped_span) && let Some ( span) = prev. xor ( skipped_span) {
200
+ // It makes no sense to allow this. Skipping the field requires that its type impls the `SkipTraversalAutoImplOnly`
201
+ // auto-trait, which means that it does not contain anything interesting to traversers; not also skipping all other
202
+ // fields of identical type is indicative of a likely erroneous assumption on the author's part--especially since
203
+ // such other (unannotated) fields will be skipped anyway, as the noop traversal takes precedence.
204
+ return quote_spanned ! ( span => {
205
+ :: core:: compile_error!( "generic field skipped here, but another field of the same type is not skipped" ) ;
206
+ } ) ;
207
+ }
208
+ T :: traverse ( bind. into_token_stream ( ) , skipped_span. is_some ( ) )
209
+ } )
210
+ } ) ;
211
+ // the order in which `where` predicates appear in rust source is irrelevant
212
+ #[ allow( rustc:: potential_query_instability) ]
213
+ for ( ty, skip) in predicates {
214
+ let bound = if skip. is_some ( ) { & skip_traversal } else { & traversable } ;
215
+ structure. add_where_predicate ( parse_quote ! { #ty: #bound } ) ;
216
+ }
217
+ quote ! { match self { #arms } }
218
+ } ;
219
+
220
+ structure. bound_impl ( traversable, T :: impl_body ( interner, traverser, body) )
125
221
}
0 commit comments