1
1
use proc_macro2:: { Ident , Span , TokenStream } ;
2
2
use quote:: { quote, ToTokens } ;
3
- use std:: collections:: HashSet ;
4
- use syn:: { parse_quote, visit, Field , Generics , Lifetime } ;
3
+ use std:: collections:: { hash_map:: Entry , HashMap } ;
4
+ use syn:: {
5
+ meta:: ParseNestedMeta , parse:: Error , parse_quote, visit, Attribute , Field , Generics , Lifetime ,
6
+ LitStr , Token ,
7
+ } ;
5
8
6
9
#[ cfg( test) ]
7
10
mod tests;
@@ -31,6 +34,114 @@ enum Type {
31
34
}
32
35
use Type :: * ;
33
36
37
+ #[ derive( Clone , Copy ) ]
38
+ enum WhenToSkip {
39
+ /// No skip_traversal annotation requires the annotated item to be skipped
40
+ Never ,
41
+
42
+ /// A skip_traversal annotation requires the annotated item to be skipped, with its type
43
+ /// constrained to TriviallyTraversable
44
+ Always ( Span ) ,
45
+
46
+ /// A `despite_potential_miscompilation_because` annotation is present, thus requiring the
47
+ /// annotated item to be forcibly skipped without its type being constrained to
48
+ /// TriviallyTraversable
49
+ Forced ,
50
+ }
51
+
52
+ impl Default for WhenToSkip {
53
+ fn default ( ) -> Self {
54
+ Self :: Never
55
+ }
56
+ }
57
+
58
+ impl WhenToSkip {
59
+ fn is_skipped ( & self ) -> bool {
60
+ !matches ! ( self , WhenToSkip :: Never )
61
+ }
62
+ }
63
+
64
+ impl std:: ops:: BitOrAssign for WhenToSkip {
65
+ fn bitor_assign ( & mut self , rhs : Self ) {
66
+ match self {
67
+ Self :: Forced => ( ) ,
68
+ Self :: Always ( _) => {
69
+ if matches ! ( rhs, Self :: Forced ) {
70
+ * self = Self :: Forced ;
71
+ }
72
+ }
73
+ Self :: Never => * self = rhs,
74
+ }
75
+ }
76
+ }
77
+
78
+ impl WhenToSkip {
79
+ fn find < const IS_TYPE : bool > ( & mut self , attrs : & [ Attribute ] , ty : Type ) -> Result < ( ) , Error > {
80
+ fn parse_reason ( meta : & ParseNestedMeta < ' _ > ) -> Result < ( ) , Error > {
81
+ if !meta. value ( ) ?. parse :: < LitStr > ( ) ?. value ( ) . trim ( ) . is_empty ( ) {
82
+ Ok ( ( ) )
83
+ } else {
84
+ Err ( meta. error ( "skip reason must be a non-empty string" ) )
85
+ }
86
+ }
87
+
88
+ let mut found = None ;
89
+ for attr in attrs {
90
+ if attr. path ( ) . is_ident ( "skip_traversal" ) {
91
+ found = Some ( attr) ;
92
+ attr. parse_nested_meta ( |meta| {
93
+ if meta. path . is_ident ( "despite_potential_miscompilation_because" ) {
94
+ parse_reason ( & meta) ?;
95
+ * self |= Self :: Forced ;
96
+ return Ok ( ( ) ) ;
97
+ }
98
+
99
+ if !IS_TYPE && ty == Generic && meta. path . is_ident ( "because_trivial" ) {
100
+ * self |= Self :: Always ( meta. error ( "" ) . span ( ) ) ;
101
+ return Ok ( ( ) ) ;
102
+ }
103
+
104
+ Err ( meta. error ( "unsupported skip reason" ) )
105
+ } ) ?;
106
+ }
107
+ }
108
+
109
+ if let ( Self :: Never , Some ( attr) ) = ( self , found) {
110
+ Err ( Error :: new_spanned (
111
+ attr,
112
+ if IS_TYPE {
113
+ match ty {
114
+ Trivial => {
115
+ "trivially traversable types are always skipped, so this attribute is superfluous"
116
+ }
117
+ _ => {
118
+ "\
119
+ Justification must be provided for skipping this potentially interesting type, by specifying\n \
120
+ `despite_potential_miscompilation_because = \" <reason>\" `\
121
+ "
122
+ }
123
+ }
124
+ } else {
125
+ match ty {
126
+ Trivial => {
127
+ "trivially traversable fields are always skipped, so this attribute is superfluous"
128
+ }
129
+ _ => {
130
+ "\
131
+ Justification must be provided for skipping potentially interesting fields, by specifying EITHER:\n \
132
+ `because_trivial` if concrete instances do not actually contain anything of interest (enforced by the compiler); OR\n \
133
+ `despite_potential_miscompilation_because = \" <reason>\" ` in the rare case that a field should always be skipped regardless\
134
+ "
135
+ }
136
+ }
137
+ } ,
138
+ ) )
139
+ } else {
140
+ Ok ( ( ) )
141
+ }
142
+ }
143
+ }
144
+
34
145
pub struct Interner < ' a > ( Option < & ' a Lifetime > ) ;
35
146
36
147
impl < ' a > Interner < ' a > {
@@ -73,7 +184,7 @@ pub trait Traversable {
73
184
/// A `match` arm for `variant`, where `f` generates the tokens for each binding.
74
185
fn arm (
75
186
variant : & synstructure:: VariantInfo < ' _ > ,
76
- f : impl FnMut ( & synstructure:: BindingInfo < ' _ > ) -> TokenStream ,
187
+ f : impl FnMut ( & synstructure:: BindingInfo < ' _ > ) -> Result < TokenStream , Error > ,
77
188
) -> TokenStream ;
78
189
79
190
/// The body of an implementation given the `interner`, `traverser` and match expression `body`.
@@ -100,10 +211,10 @@ impl Traversable for Foldable {
100
211
}
101
212
fn arm (
102
213
variant : & synstructure:: VariantInfo < ' _ > ,
103
- mut f : impl FnMut ( & synstructure:: BindingInfo < ' _ > ) -> TokenStream ,
214
+ mut f : impl FnMut ( & synstructure:: BindingInfo < ' _ > ) -> Result < TokenStream , Error > ,
104
215
) -> TokenStream {
105
216
let bindings = variant. bindings ( ) ;
106
- variant. construct ( |_, index| f ( & bindings[ index] ) )
217
+ variant. construct ( |_, index| f ( & bindings[ index] ) . unwrap_or_else ( Error :: into_compile_error ) )
107
218
}
108
219
fn impl_body (
109
220
interner : Interner < ' _ > ,
@@ -137,9 +248,14 @@ impl Traversable for Visitable {
137
248
}
138
249
fn arm (
139
250
variant : & synstructure:: VariantInfo < ' _ > ,
140
- f : impl FnMut ( & synstructure:: BindingInfo < ' _ > ) -> TokenStream ,
251
+ f : impl FnMut ( & synstructure:: BindingInfo < ' _ > ) -> Result < TokenStream , Error > ,
141
252
) -> TokenStream {
142
- variant. bindings ( ) . iter ( ) . map ( f) . collect ( )
253
+ variant
254
+ . bindings ( )
255
+ . iter ( )
256
+ . map ( f)
257
+ . collect :: < Result < _ , _ > > ( )
258
+ . unwrap_or_else ( Error :: into_compile_error)
143
259
}
144
260
fn impl_body (
145
261
interner : Interner < ' _ > ,
@@ -199,49 +315,101 @@ impl Interner<'_> {
199
315
200
316
pub fn traversable_derive < T : Traversable > (
201
317
mut structure : synstructure:: Structure < ' _ > ,
202
- ) -> TokenStream {
318
+ ) -> Result < TokenStream , Error > {
319
+ use WhenToSkip :: * ;
320
+
203
321
let ast = structure. ast ( ) ;
204
322
205
323
let interner = Interner :: resolve ( & ast. generics ) ;
206
324
let traverser = gen_param ( "T" , & ast. generics ) ;
207
325
let traversable = T :: traversable ( & interner) ;
208
326
327
+ let skip_traversal =
328
+ |t : & dyn ToTokens | parse_quote ! { #interner: :: rustc_middle:: ty:: TriviallyTraverses <#t> } ;
329
+
209
330
structure. underscore_const ( true ) ;
210
331
structure. add_bounds ( synstructure:: AddBounds :: None ) ;
211
332
structure. bind_with ( |_| synstructure:: BindStyle :: Move ) ;
212
333
213
- if interner. 0 . is_none ( ) {
334
+ let not_generic = if interner. 0 . is_none ( ) {
214
335
structure. add_impl_generic ( parse_quote ! { ' tcx } ) ;
215
- }
336
+ Trivial
337
+ } else {
338
+ NotGeneric
339
+ } ;
216
340
217
341
// If our derived implementation will be generic over the traversable type, then we must
218
342
// constrain it to only those generic combinations that satisfy the traversable trait's
219
343
// supertraits.
220
- if ast. generics . type_params ( ) . next ( ) . is_some ( ) {
344
+ let ty = if ast. generics . type_params ( ) . next ( ) . is_some ( ) {
221
345
let supertraits = T :: supertraits ( & interner) ;
222
346
structure. add_where_predicate ( parse_quote ! { Self : #supertraits } ) ;
223
- }
347
+ Generic
348
+ } else {
349
+ not_generic
350
+ } ;
224
351
225
- // We add predicates to each generic field type, rather than to our generic type parameters.
226
- // This results in a "perfect derive", but it can result in trait solver cycles if any type
227
- // parameters are involved in recursive type definitions; fortunately that is not the case (yet).
228
- let mut predicates = HashSet :: new ( ) ;
229
- let arms = structure. each_variant ( |variant| {
230
- T :: arm ( variant, |bind| {
231
- let ast = bind. ast ( ) ;
232
- let field_ty = interner. type_of ( & bind. referenced_ty_params ( ) , [ ast] ) ;
233
- if field_ty == Generic {
234
- predicates. insert ( ast. ty . clone ( ) ) ;
352
+ let mut when_to_skip = WhenToSkip :: default ( ) ;
353
+ when_to_skip. find :: < true > ( & ast. attrs , ty) ?;
354
+ let body = if when_to_skip. is_skipped ( ) {
355
+ if let Always ( _) = when_to_skip {
356
+ structure. add_where_predicate ( skip_traversal ( & <Token ! [ Self ] >:: default ( ) ) ) ;
357
+ }
358
+ T :: traverse ( quote ! { self } , true , & interner)
359
+ } else {
360
+ // We add predicates to each generic field type, rather than to our generic type parameters.
361
+ // This results in a "perfect derive" that avoids having to propagate `#[skip_traversal]` annotations
362
+ // into wrapping types, but it can result in trait solver cycles if any type parameters are involved
363
+ // in recursive type definitions; fortunately that is not the case (yet).
364
+ let mut predicates = HashMap :: < _ , ( _ , _ ) > :: new ( ) ;
365
+
366
+ let arms = structure. each_variant ( |variant| {
367
+ let variant_ty = interner. type_of ( & variant. referenced_ty_params ( ) , variant. ast ( ) . fields ) ;
368
+ let mut skipped_variant = WhenToSkip :: default ( ) ;
369
+ if let Err ( err) = skipped_variant. find :: < false > ( variant. ast ( ) . attrs , variant_ty) {
370
+ return err. into_compile_error ( ) ;
235
371
}
236
- T :: traverse ( bind. into_token_stream ( ) , field_ty == Trivial , & interner)
237
- } )
238
- } ) ;
239
- // the order in which `where` predicates appear in rust source is irrelevant
240
- #[ allow( rustc:: potential_query_instability) ]
241
- for ty in predicates {
242
- structure. add_where_predicate ( parse_quote ! { #ty: #traversable } ) ;
243
- }
244
- let body = quote ! { match self { #arms } } ;
372
+ T :: arm ( variant, |bind| {
373
+ let ast = bind. ast ( ) ;
374
+ let is_skipped = variant_ty == Trivial || {
375
+ let field_ty = interner. type_of ( & bind. referenced_ty_params ( ) , [ ast] ) ;
376
+ field_ty == Trivial || {
377
+ let mut skipped_field = skipped_variant;
378
+ skipped_field. find :: < false > ( & ast. attrs , field_ty) ?;
379
+
380
+ match predicates. entry ( ast. ty . clone ( ) ) {
381
+ Entry :: Occupied ( existing) => match ( & mut existing. into_mut ( ) . 0 , skipped_field) {
382
+ ( Never , Never ) | ( Never , Forced ) | ( Forced , Forced ) | ( Always ( _) , Always ( _) ) => ( ) ,
383
+ ( existing @ Forced , Never ) => * existing = Never ,
384
+ ( & mut Always ( span) , _) | ( _, Always ( span) ) => return Err ( Error :: new ( span, format ! ( "\
385
+ This annotation only makes sense if all fields of type `{0}` are annotated identically.\n \
386
+ In particular, the derived impl will only be applicable when `{0}: TriviallyTraversable` and therefore all traversals of `{0}` will be no-ops;\n \
387
+ accordingly it makes no sense for other fields of type `{0}` to omit `#[skip_traversal]` or to include `despite_potential_miscompilation_because`.\
388
+ ", ast. ty. to_token_stream( ) ) ) ) ,
389
+ } ,
390
+ Entry :: Vacant ( entry) => { entry. insert ( ( skipped_field, field_ty) ) ; }
391
+ }
392
+
393
+ skipped_field. is_skipped ( )
394
+ }
395
+ } ;
396
+
397
+ Ok ( T :: traverse ( bind. into_token_stream ( ) , is_skipped, & interner) )
398
+ } )
399
+ } ) ;
400
+
401
+ // the order in which `where` predicates appear in rust source is irrelevant
402
+ #[ allow( rustc:: potential_query_instability) ]
403
+ for ( ty, ( when_to_skip, field_ty) ) in predicates {
404
+ structure. add_where_predicate ( match when_to_skip {
405
+ Always ( _) => skip_traversal ( & ty) ,
406
+ // we only need to add traversable predicate for generic types
407
+ Never if field_ty == Generic => parse_quote ! { #ty: #traversable } ,
408
+ _ => continue ,
409
+ } ) ;
410
+ }
411
+ quote ! { match self { #arms } }
412
+ } ;
245
413
246
- structure. bound_impl ( traversable, T :: impl_body ( interner, traverser, body) )
414
+ Ok ( structure. bound_impl ( traversable, T :: impl_body ( interner, traverser, body) ) )
247
415
}
0 commit comments