7
7
mod ext;
8
8
mod repr;
9
9
10
- use proc_macro;
11
10
use proc_macro2:: Span ;
12
11
use quote:: quote;
13
12
use syn:: visit:: { self , Visit } ;
14
13
use syn:: {
15
- parse_quote, punctuated:: Punctuated , token:: Comma , Data , DataEnum , DataStruct , DeriveInput ,
16
- Error , GenericParam , Ident , Lifetime , Type , TypePath ,
14
+ parse_quote, punctuated:: Punctuated , token:: Comma , Data , DataEnum , DataStruct , DataUnion ,
15
+ DeriveInput , Error , GenericParam , Ident , Lifetime , Type , TypePath ,
17
16
} ;
18
17
19
18
use ext:: * ;
@@ -42,8 +41,9 @@ pub fn derive_from_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream
42
41
match & ast. data {
43
42
Data :: Struct ( strct) => derive_from_bytes_struct ( & ast, strct) ,
44
43
Data :: Enum ( enm) => derive_from_bytes_enum ( & ast, enm) ,
45
- Data :: Union ( _) => Error :: new ( Span :: call_site ( ) , "unsupported on unions" ) . to_compile_error ( ) ,
46
- } . into ( )
44
+ Data :: Union ( unn) => derive_from_bytes_union ( & ast, unn) ,
45
+ }
46
+ . into ( )
47
47
}
48
48
49
49
#[ proc_macro_derive( AsBytes ) ]
@@ -52,8 +52,9 @@ pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
52
52
match & ast. data {
53
53
Data :: Struct ( strct) => derive_as_bytes_struct ( & ast, strct) ,
54
54
Data :: Enum ( enm) => derive_as_bytes_enum ( & ast, enm) ,
55
- Data :: Union ( _) => Error :: new ( Span :: call_site ( ) , "unsupported on unions" ) . to_compile_error ( ) ,
56
- } . into ( )
55
+ Data :: Union ( unn) => derive_as_bytes_union ( & ast, unn) ,
56
+ }
57
+ . into ( )
57
58
}
58
59
59
60
#[ proc_macro_derive( Unaligned ) ]
@@ -62,8 +63,9 @@ pub fn derive_unaligned(ts: proc_macro::TokenStream) -> proc_macro::TokenStream
62
63
match & ast. data {
63
64
Data :: Struct ( strct) => derive_unaligned_struct ( & ast, strct) ,
64
65
Data :: Enum ( enm) => derive_unaligned_enum ( & ast, enm) ,
65
- Data :: Union ( _) => Error :: new ( Span :: call_site ( ) , "unsupported on unions" ) . to_compile_error ( ) ,
66
- } . into ( )
66
+ Data :: Union ( unn) => derive_unaligned_union ( & ast, unn) ,
67
+ }
68
+ . into ( )
67
69
}
68
70
69
71
// Unwrap a Result<_, Vec<Error>>, converting any Err value into a TokenStream
@@ -77,11 +79,18 @@ macro_rules! try_or_print {
77
79
} ;
78
80
}
79
81
82
+ const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS : & [ & [ StructRepr ] ] = & [
83
+ & [ StructRepr :: C ] ,
84
+ & [ StructRepr :: Transparent ] ,
85
+ & [ StructRepr :: Packed ] ,
86
+ & [ StructRepr :: C , StructRepr :: Packed ] ,
87
+ ] ;
88
+
80
89
// A struct is FromBytes if:
81
90
// - all fields are FromBytes
82
91
83
92
fn derive_from_bytes_struct ( ast : & DeriveInput , strct : & DataStruct ) -> proc_macro2:: TokenStream {
84
- impl_block ( ast, strct, "FromBytes" , true , false )
93
+ impl_block ( ast, strct, "FromBytes" , true , PaddingCheck :: None )
85
94
}
86
95
87
96
// An enum is FromBytes if:
@@ -124,7 +133,7 @@ fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Tok
124
133
. to_compile_error ( ) ;
125
134
}
126
135
127
- impl_block ( ast, enm, "FromBytes" , true , false )
136
+ impl_block ( ast, enm, "FromBytes" , true , PaddingCheck :: None )
128
137
}
129
138
130
139
#[ rustfmt:: skip]
@@ -151,6 +160,13 @@ const ENUM_FROM_BYTES_CFG: Config<EnumRepr> = {
151
160
}
152
161
} ;
153
162
163
+ // Like structs, unions are FromBytes if
164
+ // - all fields are FromBytes
165
+
166
+ fn derive_from_bytes_union ( ast : & DeriveInput , unn : & DataUnion ) -> proc_macro2:: TokenStream {
167
+ impl_block ( ast, unn, "FromBytes" , true , PaddingCheck :: None )
168
+ }
169
+
154
170
// A struct is AsBytes if:
155
171
// - all fields are AsBytes
156
172
// - repr(C) or repr(transparent) and
@@ -164,35 +180,26 @@ fn derive_as_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2:
164
180
. to_compile_error ( ) ;
165
181
}
166
182
167
- let reprs = try_or_print ! ( STRUCT_AS_BYTES_CFG . validate_reprs( ast) ) ;
183
+ // TODO(https://github.com/rust-lang/reference/pull/1163): If this PR gets
184
+ // merged, remove this TODO comment. Otherwise, if the repr(packed) guarantees
185
+ // turn out to not be as strong as we're relying on them to be (namely, that
186
+ // repr(packed) or repr(packed(1)) guarantee no inter-field padding), we will
187
+ // need to change what guarantees we accept from repr(packed).
168
188
169
- let require_size_check = match reprs. as_slice ( ) {
170
- [ StructRepr :: C ] | [ StructRepr :: Transparent ] => true ,
171
- [ StructRepr :: Packed ] | [ StructRepr :: C , StructRepr :: Packed ] => false ,
172
- // validate_reprs has already validated that it's one of the preceding
173
- // patterns
174
- _ => unreachable ! ( ) ,
175
- } ;
189
+ let reprs = try_or_print ! ( STRUCT_UNION_AS_BYTES_CFG . validate_reprs( ast) ) ;
190
+ let padding_check =
191
+ if reprs. contains ( & StructRepr :: Packed ) { PaddingCheck :: None } else { PaddingCheck :: Struct } ;
176
192
177
- impl_block ( ast, strct, "AsBytes" , true , require_size_check )
193
+ impl_block ( ast, strct, "AsBytes" , true , padding_check )
178
194
}
179
195
180
- #[ rustfmt:: skip]
181
- const STRUCT_AS_BYTES_CFG : Config < StructRepr > = {
182
- use StructRepr :: * ;
183
- Config {
184
- // NOTE: Since disallowed_but_legal_combinations is empty, this message
185
- // will never actually be emitted.
186
- allowed_combinations_message : r#"AsBytes requires repr of "C", "transparent", or "packed""# ,
187
- derive_unaligned : false ,
188
- allowed_combinations : & [
189
- & [ C ] ,
190
- & [ Transparent ] ,
191
- & [ C , Packed ] ,
192
- & [ Packed ] ,
193
- ] ,
194
- disallowed_but_legal_combinations : & [ ] ,
195
- }
196
+ const STRUCT_UNION_AS_BYTES_CFG : Config < StructRepr > = Config {
197
+ // NOTE: Since disallowed_but_legal_combinations is empty, this message
198
+ // will never actually be emitted.
199
+ allowed_combinations_message : r#"AsBytes requires either a) repr "C" or "transparent" with all fields implementing AsBytes or, b) repr "packed""# ,
200
+ derive_unaligned : false ,
201
+ allowed_combinations : STRUCT_UNION_ALLOWED_REPR_COMBINATIONS ,
202
+ disallowed_but_legal_combinations : & [ ] ,
196
203
} ;
197
204
198
205
// An enum is AsBytes if it is C-like and has a defined repr
@@ -206,7 +213,7 @@ fn derive_as_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Token
206
213
// We don't care what the repr is; we only care that it is one of the
207
214
// allowed ones.
208
215
let _: Vec < repr:: EnumRepr > = try_or_print ! ( ENUM_AS_BYTES_CFG . validate_reprs( ast) ) ;
209
- impl_block ( ast, enm, "AsBytes" , false , false )
216
+ impl_block ( ast, enm, "AsBytes" , false , PaddingCheck :: None )
210
217
}
211
218
212
219
#[ rustfmt:: skip]
@@ -234,43 +241,48 @@ const ENUM_AS_BYTES_CFG: Config<EnumRepr> = {
234
241
}
235
242
} ;
236
243
244
+ // A union is AsBytes if:
245
+ // - all fields are AsBytes
246
+ // - repr(C), repr(transparent), or repr(packed)
247
+ // - no padding (size of union equals size of each field type)
248
+
249
+ fn derive_as_bytes_union ( ast : & DeriveInput , unn : & DataUnion ) -> proc_macro2:: TokenStream {
250
+ // TODO: Support type parameters.
251
+ if !ast. generics . params . is_empty ( ) {
252
+ return Error :: new ( Span :: call_site ( ) , "unsupported on types with type parameters" )
253
+ . to_compile_error ( ) ;
254
+ }
255
+
256
+ // TODO(https://github.com/rust-lang/reference/pull/1163): If this PR gets
257
+ // merged, remove this TODO comment. Otherwise, if the repr(packed) guarantees
258
+ // turn out to not be as strong as we're relying on them to be (namely, that
259
+ // repr(packed) or repr(packed(1)) guarantee no inter-field padding), we will
260
+ // need to change what guarantees we accept from repr(packed).
261
+ try_or_print ! ( STRUCT_UNION_AS_BYTES_CFG . validate_reprs( ast) ) ;
262
+
263
+ impl_block ( ast, unn, "AsBytes" , true , PaddingCheck :: Union )
264
+ }
265
+
237
266
// A struct is Unaligned if:
238
267
// - repr(align) is no more than 1 and either
239
268
// - repr(C) or repr(transparent) and
240
269
// - all fields Unaligned
241
270
// - repr(packed)
242
271
243
272
fn derive_unaligned_struct ( ast : & DeriveInput , strct : & DataStruct ) -> proc_macro2:: TokenStream {
244
- let reprs = try_or_print ! ( STRUCT_UNALIGNED_CFG . validate_reprs( ast) ) ;
273
+ let reprs = try_or_print ! ( STRUCT_UNION_UNALIGNED_CFG . validate_reprs( ast) ) ;
274
+ let require_trait_bound = !reprs. contains ( & StructRepr :: Packed ) ;
245
275
246
- let require_trait_bound = match reprs. as_slice ( ) {
247
- [ StructRepr :: C ] | [ StructRepr :: Transparent ] => true ,
248
- [ StructRepr :: Packed ] | [ StructRepr :: C , StructRepr :: Packed ] => false ,
249
- // validate_reprs has already validated that it's one of the preceding
250
- // patterns
251
- _ => unreachable ! ( ) ,
252
- } ;
253
-
254
- impl_block ( ast, strct, "Unaligned" , require_trait_bound, false )
276
+ impl_block ( ast, strct, "Unaligned" , require_trait_bound, PaddingCheck :: None )
255
277
}
256
278
257
- #[ rustfmt:: skip]
258
- const STRUCT_UNALIGNED_CFG : Config < StructRepr > = {
259
- use StructRepr :: * ;
260
- Config {
261
- // NOTE: Since disallowed_but_legal_combinations is empty, this message
262
- // will never actually be emitted.
263
- allowed_combinations_message :
264
- r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""# ,
265
- derive_unaligned : true ,
266
- allowed_combinations : & [
267
- & [ C ] ,
268
- & [ Transparent ] ,
269
- & [ Packed ] ,
270
- & [ C , Packed ] ,
271
- ] ,
272
- disallowed_but_legal_combinations : & [ ] ,
273
- }
279
+ const STRUCT_UNION_UNALIGNED_CFG : Config < StructRepr > = Config {
280
+ // NOTE: Since disallowed_but_legal_combinations is empty, this message
281
+ // will never actually be emitted.
282
+ allowed_combinations_message : r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""# ,
283
+ derive_unaligned : true ,
284
+ allowed_combinations : STRUCT_UNION_ALLOWED_REPR_COMBINATIONS ,
285
+ disallowed_but_legal_combinations : & [ ] ,
274
286
} ;
275
287
276
288
// An enum is Unaligned if:
@@ -292,7 +304,7 @@ fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Toke
292
304
// of true for require_trait_bounds doesn't really do anything. But it's
293
305
// marginally more future-proof in case that restriction is lifted in the
294
306
// future.
295
- impl_block ( ast, enm, "Unaligned" , true , false )
307
+ impl_block ( ast, enm, "Unaligned" , true , PaddingCheck :: None )
296
308
}
297
309
298
310
#[ rustfmt:: skip]
@@ -320,12 +332,35 @@ const ENUM_UNALIGNED_CFG: Config<EnumRepr> = {
320
332
}
321
333
} ;
322
334
335
+ // Like structs, a union is Unaligned if:
336
+ // - repr(align) is no more than 1 and either
337
+ // - repr(C) or repr(transparent) and
338
+ // - all fields Unaligned
339
+ // - repr(packed)
340
+
341
+ fn derive_unaligned_union ( ast : & DeriveInput , unn : & DataUnion ) -> proc_macro2:: TokenStream {
342
+ let reprs = try_or_print ! ( STRUCT_UNION_UNALIGNED_CFG . validate_reprs( ast) ) ;
343
+ let require_trait_bound = !reprs. contains ( & StructRepr :: Packed ) ;
344
+
345
+ impl_block ( ast, unn, "Unaligned" , require_trait_bound, PaddingCheck :: None )
346
+ }
347
+
348
+ // This enum describes what kind of padding check needs to be generated for the associated impl
349
+ enum PaddingCheck {
350
+ // No additional padding check is required
351
+ None ,
352
+ // Check that the sum of the fields' sizes exactly equals the struct's size
353
+ Struct ,
354
+ // Check that the size of each field exactly equals the union's size
355
+ Union ,
356
+ }
357
+
323
358
fn impl_block < D : DataExt > (
324
359
input : & DeriveInput ,
325
360
data : & D ,
326
361
trait_name : & str ,
327
362
require_trait_bound : bool ,
328
- require_size_check : bool ,
363
+ padding_check : PaddingCheck ,
329
364
) -> proc_macro2:: TokenStream {
330
365
// In this documentation, we will refer to this hypothetical struct:
331
366
//
@@ -528,8 +563,9 @@ fn impl_block<D: DataExt>(
528
563
quote ! ( )
529
564
} ;
530
565
531
- let size_check_body = if require_size_check && !field_types. is_empty ( ) {
532
- quote ! (
566
+ let size_check_body = match ( field_types. is_empty ( ) , padding_check) {
567
+ ( true , _) | ( false , PaddingCheck :: None ) => quote ! ( ) ,
568
+ ( false , PaddingCheck :: Struct ) => quote ! (
533
569
const _: ( ) = {
534
570
trait HasPadding <const HAS_PADDING : bool > { }
535
571
fn assert_no_padding<T : HasPadding <false >>( ) { }
@@ -540,9 +576,19 @@ fn impl_block<D: DataExt>(
540
576
impl HasPadding <HAS_PADDING > for #type_ident { }
541
577
let _ = assert_no_padding:: <#type_ident>;
542
578
} ;
543
- )
544
- } else {
545
- quote ! ( )
579
+ ) ,
580
+ ( false , PaddingCheck :: Union ) => quote ! (
581
+ const _: ( ) = {
582
+ trait FieldsAreSameSize <const FIELDS_ARE_SAME_SIZE : bool > { }
583
+ fn assert_fields_are_same_size<T : FieldsAreSameSize <true >>( ) { }
584
+
585
+ const COMPOSITE_TYPE_SIZE : usize = :: core:: mem:: size_of:: <#type_ident>( ) ;
586
+ const FIELDS_ARE_SAME_SIZE : bool = true
587
+ #( && ( :: core:: mem:: size_of:: <#field_types>( ) == COMPOSITE_TYPE_SIZE ) ) * ;
588
+ impl FieldsAreSameSize <FIELDS_ARE_SAME_SIZE > for #type_ident { }
589
+ let _ = assert_fields_are_same_size:: <#type_ident>;
590
+ } ;
591
+ ) ,
546
592
} ;
547
593
548
594
quote ! {
@@ -587,7 +633,7 @@ mod tests {
587
633
&& elements_are_sorted_and_deduped ( & config. disallowed_but_legal_combinations )
588
634
}
589
635
590
- assert ! ( config_is_sorted( & STRUCT_UNALIGNED_CFG ) ) ;
636
+ assert ! ( config_is_sorted( & STRUCT_UNION_UNALIGNED_CFG ) ) ;
591
637
assert ! ( config_is_sorted( & ENUM_FROM_BYTES_CFG ) ) ;
592
638
assert ! ( config_is_sorted( & ENUM_UNALIGNED_CFG ) ) ;
593
639
}
@@ -605,7 +651,7 @@ mod tests {
605
651
overlap ( config. allowed_combinations , config. disallowed_but_legal_combinations )
606
652
}
607
653
608
- assert ! ( !config_overlaps( & STRUCT_UNALIGNED_CFG ) ) ;
654
+ assert ! ( !config_overlaps( & STRUCT_UNION_UNALIGNED_CFG ) ) ;
609
655
assert ! ( !config_overlaps( & ENUM_FROM_BYTES_CFG ) ) ;
610
656
assert ! ( !config_overlaps( & ENUM_UNALIGNED_CFG ) ) ;
611
657
}
0 commit comments