1use proc_macro2::{Ident, Literal, Span, TokenStream};
2use quote::{format_ident, quote, quote_spanned};
3use std::iter;
4use syn::spanned::Spanned;
5use syn::{
6 Data, DataEnum, DataStruct, DeriveInput, Error, ExprPath, Fields, Type, parse_macro_input,
7};
8
9#[proc_macro_derive(TrivialType, attributes(trivial_type))]
14pub fn trivial_type_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15 let input = parse_macro_input!(input as DeriveInput);
16
17 if !input.generics.params.is_empty() {
18 return Error::new(
19 input.ident.span(),
20 "`TrivialType` can't be derived on generic types",
21 )
22 .into_compile_error()
23 .into();
24 }
25
26 let validate = match parse_validate(&input) {
27 Ok(validate) => validate,
28 Err(error) => {
29 return error.to_compile_error().into();
30 }
31 };
32
33 let type_name = &input.ident;
34
35 let output = match &input.data {
36 Data::Struct(data_struct) => {
37 if let Err(error) = check_repr(
38 &input,
39 &["C", "transparent"],
40 "`TrivialType` on structs requires `#[repr(C)]` or `#[repr(transparent)]` without \
41 other options",
42 ) {
43 return error.to_compile_error().into();
44 }
45 let field_types = data_struct
46 .fields
47 .iter()
48 .map(|field| &field.ty)
49 .collect::<Vec<_>>();
50
51 let struct_metadata = match generate_struct_metadata(type_name, data_struct) {
52 Ok(struct_metadata) => struct_metadata,
53 Err(error) => {
54 return error.to_compile_error().into();
55 }
56 };
57
58 let checks = quote! {
62 let fields = bytes;
63 #(
64 let (_value, fields) =
65 <#field_types as ::ab_io_type::trivial_type::TrivialType>
66 ::try_from_bytes_unaligned(fields)?;
67 )*
68 let _: &[::core::primitive::u8] = fields;
70 };
71 let parsing_step = generate_parsing_step(checks, validate.as_ref());
72 let alignment = generate_alignment(&field_types);
73
74 let marker_impl = if validate.is_some() {
80 TokenStream::new()
81 } else {
82 quote! {
83 #[automatically_derived]
90 unsafe impl ::ab_io_type::trivial_type::AnyBitPatternMarker for #type_name
91 where
92 #(
93 for<'__ab_io_type> #field_types:
94 ::ab_io_type::trivial_type::AnyBitPatternMarker,
95 )*
96 {
97 }
98 }
99 };
100
101 quote! {
102 const _: () = {
103 ::core::assert!(
106 0 == (
107 ::core::mem::size_of::<#type_name>()
108 #(- ::core::mem::size_of::<#field_types>() )*
109 ),
110 "Struct must not have implicit padding. Consider reordering fields, adding \
111 `padding: [u8; N]` field where necessary or use `Unaligned<T>` wrapper for \
112 types with larger alignment to reduce it to one byte."
113 );
114
115 ::core::assert!(
117 ::core::primitive::u32::MAX as ::core::primitive::usize
118 >= ::core::mem::size_of::<#type_name>(),
119 "Type size must be smaller than 2^32"
120 );
121
122 let (type_details, remaining_metadata) =
126 ::ab_io_type::metadata::IoTypeMetadataKind::type_details::<{
127 <#type_name as ::ab_io_type::trivial_type::TrivialType>::METADATA.len()
128 }>(
129 <#type_name as ::ab_io_type::trivial_type::TrivialType>::METADATA,
130 )
131 .expect(
132 "Metadata must decode, which is not the case for layouts with \
133 padding or misaligned fields"
134 );
135 ::core::assert!(remaining_metadata.is_empty());
136 ::core::assert!(
137 ::core::mem::size_of::<#type_name>()
138 == type_details.recommended_capacity as ::core::primitive::usize
139 );
140 ::core::assert!(
143 ::ab_io_type::alignment::Alignment::of::<#type_name>().power
144 == type_details.alignment.power
145 );
146 };
147
148 #[automatically_derived]
149 unsafe impl ::ab_io_type::trivial_type::TrivialType for #type_name
150 where
151 #( #field_types: ::ab_io_type::trivial_type::TrivialType, )*
152 {
153 #alignment
154
155 const METADATA: &[::core::primitive::u8] = #struct_metadata;
156
157 #parsing_step
158 }
159
160 #marker_impl
161 }
162 }
163 Data::Enum(data_enum) => {
164 if let Err(error) = check_repr(
169 &input,
170 &["u8"],
171 "`TrivialType` on enums requires `#[repr(u8)]` without other options",
172 ) {
173 return error.to_compile_error().into();
174 }
175
176 let discriminant_assertions = data_enum
182 .variants
183 .iter()
184 .zip(0..=u8::MAX)
186 .filter_map(|(variant, index)| {
187 let (_eq_token, discriminant) = variant.discriminant.as_ref()?;
188 let span = Span::call_site().located_at(discriminant.span());
190 let name = format_ident!("__TRIVIAL_TYPE_DISCRIMINANT_{index}");
191 let message = format!(
192 "Discriminant of `{}` must be equal to its index {index}, variants are \
193 identified by their index in `TrivialType` metadata",
194 variant.ident
195 );
196
197 Some(quote_spanned! {span=>
198 impl #type_name {
199 #[doc(hidden)]
202 const #name: () = ::core::assert!(#index == (#discriminant), #message);
203 }
204
205 const _: () = #type_name::#name;
206 })
207 })
208 .collect::<Vec<_>>();
209
210 let field_types = data_enum
211 .variants
212 .iter()
213 .flat_map(|variant| &variant.fields)
214 .map(|field| &field.ty)
215 .collect::<Vec<_>>();
216
217 let padding_assertions = data_enum.variants.iter().map(|variant| {
218 let field_types = variant.fields.iter().map(|field| &field.ty);
219
220 quote! {
221 ::core::assert!(
224 0 == (
225 ::core::mem::size_of::<#type_name>()
226 - ::core::mem::size_of::<::core::primitive::u8>()
227 #(- ::core::mem::size_of::<#field_types>() )*
228 ),
229 "Enum must not have implicit padding. Consider reordering fields, adding \
230 `padding: [u8; N]` field where necessary or use `Unaligned<T>` wrapper for \
231 types with larger alignment to reduce it to one byte."
232 );
233 }
234 });
235
236 let variant_arms = data_enum
243 .variants
244 .iter()
245 .zip(0..=u8::MAX)
247 .map(|(variant, index)| {
248 let field_types = variant.fields.iter().map(|field| &field.ty);
249
250 quote! {
251 #index => {
252 #(
253 let (_value, fields) =
254 <#field_types as ::ab_io_type::trivial_type::TrivialType>
255 ::try_from_bytes_unaligned(fields)?;
256 )*
257 let _: &[::core::primitive::u8] = fields;
259 }
260 }
261 });
262 let checks = quote! {
263 let (&discriminant, fields) = <[::core::primitive::u8]>::split_first(bytes)?;
264 match discriminant {
265 #( #variant_arms )*
266 _ => {
267 return ::core::option::Option::None;
268 }
269 }
270 };
271 let parsing_step = generate_parsing_step(checks, validate.as_ref());
272
273 let alignment = generate_alignment(&field_types);
274
275 let enum_metadata = match generate_enum_metadata(type_name, data_enum) {
276 Ok(struct_metadata) => struct_metadata,
277 Err(error) => {
278 return error.to_compile_error().into();
279 }
280 };
281
282 quote! {
283 #( #discriminant_assertions )*
284
285 const _: () = {
286 #( #padding_assertions )*
287
288 ::core::assert!(
290 ::core::primitive::u32::MAX as ::core::primitive::usize
291 >= ::core::mem::size_of::<#type_name>(),
292 "Type size must be smaller than 2^32"
293 );
294
295 let (type_details, remaining_metadata) =
299 ::ab_io_type::metadata::IoTypeMetadataKind::type_details::<{
300 <#type_name as ::ab_io_type::trivial_type::TrivialType>::METADATA.len()
301 }>(
302 <#type_name as ::ab_io_type::trivial_type::TrivialType>::METADATA,
303 )
304 .expect(
305 "Metadata must decode, which is not the case for layouts with \
306 padding or misaligned fields"
307 );
308 ::core::assert!(remaining_metadata.is_empty());
309 ::core::assert!(
310 ::core::mem::size_of::<#type_name>()
311 == type_details.recommended_capacity as ::core::primitive::usize
312 );
313 ::core::assert!(
316 ::ab_io_type::alignment::Alignment::of::<#type_name>().power
317 == type_details.alignment.power
318 );
319 };
320
321 #[automatically_derived]
322 unsafe impl ::ab_io_type::trivial_type::TrivialType for #type_name
323 where
324 #( #field_types: ::ab_io_type::trivial_type::TrivialType, )*
325 {
326 #alignment
327
328 const METADATA: &[::core::primitive::u8] = #enum_metadata;
329
330 #parsing_step
331 }
332 }
333 }
334 Data::Union(data_union) => {
335 return Error::new(
336 data_union.union_token.span(),
337 "`TrivialType` can be derived for structs and enums, but not unions",
338 )
339 .to_compile_error()
340 .into();
341 }
342 };
343
344 output.into()
345}
346
347fn generate_alignment(field_types: &[&Type]) -> TokenStream {
349 quote! {
350 const ALIGNMENT: ::ab_io_type::alignment::Alignment = {
351 let mut field_alignments: &[::ab_io_type::alignment::Alignment] = &[
352 #( <#field_types as ::ab_io_type::trivial_type::TrivialType>::ALIGNMENT, )*
353 ];
354 let mut alignment =
355 <::core::primitive::u8 as ::ab_io_type::trivial_type::TrivialType>::ALIGNMENT;
356 while let [field_alignment, rest @ ..] = field_alignments {
357 if field_alignment.get() > alignment.get() {
358 alignment = *field_alignment;
359 }
360 field_alignments = rest;
361 }
362 alignment
363 };
364 }
365}
366
367fn parse_validate(input: &DeriveInput) -> Result<Option<ExprPath>, Error> {
370 let nested_attrs = match &input.data {
374 Data::Struct(data_struct) => data_struct
375 .fields
376 .iter()
377 .flat_map(|field| &field.attrs)
378 .collect::<Vec<_>>(),
379 Data::Enum(data_enum) => data_enum
380 .variants
381 .iter()
382 .flat_map(|variant| {
383 variant
384 .attrs
385 .iter()
386 .chain(variant.fields.iter().flat_map(|field| &field.attrs))
387 })
388 .collect::<Vec<_>>(),
389 Data::Union(data_union) => data_union
390 .fields
391 .named
392 .iter()
393 .flat_map(|field| &field.attrs)
394 .collect::<Vec<_>>(),
395 };
396 if let Some(attr) = nested_attrs
397 .into_iter()
398 .find(|attr| attr.path().is_ident("trivial_type"))
399 {
400 return Err(Error::new_spanned(
401 attr,
402 "`#[trivial_type(..)]` is only supported on the type",
403 ));
404 }
405
406 let mut validate = None;
407 let attrs = input
408 .attrs
409 .iter()
410 .filter(|attr| attr.path().is_ident("trivial_type"));
411 for attr in attrs {
412 let mut has_options = false;
413 attr.parse_nested_meta(|meta| {
414 has_options = true;
415 if !meta.path.is_ident("validate") {
416 return Err(meta.error("Unsupported `#[trivial_type(..)]` option"));
417 }
418 if validate.is_some() {
419 return Err(meta.error("`validate` is specified more than once"));
420 }
421 let path = meta.value()?.parse::<ExprPath>()?;
424 if let Some(attr) = path.attrs.first() {
425 return Err(Error::new_spanned(
426 attr,
427 "Attributes are not supported in `validate = path`",
428 ));
429 }
430 validate = Some(path);
431 Ok(())
432 })?;
433
434 if !has_options {
435 return Err(Error::new_spanned(
436 attr,
437 "`#[trivial_type(..)]` requires an option, like `validate = path`",
438 ));
439 }
440 }
441
442 Ok(validate)
443}
444
445fn generate_parsing_step(checks: TokenStream, validate: Option<&ExprPath>) -> TokenStream {
449 let validation = if let Some(path) = validate {
450 let span = Span::call_site().located_at(path.span());
453 quote_spanned! {span=>
454 if !#path(::ab_io_type::unaligned::Unaligned::<Self>::as_inner(__ab_io_type_value)) {
455 return ::core::option::Option::None;
456 }
457 }
458 } else {
459 TokenStream::new()
460 };
461
462 quote! {
467 #[inline(always)]
468 fn try_from_bytes_unaligned(
469 bytes: &[::core::primitive::u8],
470 ) -> ::core::option::Option<(
471 &::ab_io_type::unaligned::Unaligned<Self>,
472 &[::core::primitive::u8],
473 )> {
474 let (bytes, remainder) = <[::core::primitive::u8]>::split_at_checked(
475 bytes,
476 ::core::mem::size_of::<Self>(),
477 )?;
478 #checks
479 let __ab_io_type_value = unsafe {
485 &*<[::core::primitive::u8]>::as_ptr(bytes)
486 .cast::<::ab_io_type::unaligned::Unaligned<Self>>()
487 };
488 #validation
491 ::core::option::Option::Some((__ab_io_type_value, remainder))
492 }
493 }
494}
495
496fn check_repr(input: &DeriveInput, allowed: &[&str], message: &str) -> Result<(), Error> {
499 let mut found_allowed = false;
500
501 let repr_attrs = input
503 .attrs
504 .iter()
505 .filter(|attr| attr.path().is_ident("repr"));
506 for repr_attr in repr_attrs {
507 repr_attr.parse_nested_meta(|meta| {
508 if allowed.iter().any(|option| meta.path.is_ident(option)) {
509 found_allowed = true;
510 Ok(())
511 } else {
512 Err(meta.error(message))
513 }
514 })?;
515 }
516
517 if !found_allowed {
518 return Err(Error::new(input.ident.span(), message));
519 }
520
521 Ok(())
522}
523
524fn generate_struct_metadata(ident: &Ident, data_struct: &DataStruct) -> Result<TokenStream, Error> {
525 let inner_struct_metadata = generate_inner_struct_metadata(ident, &data_struct.fields)
526 .collect::<Result<Vec<_>, _>>()?;
527
528 Ok(quote! {{
532 #[inline(always)]
533 const fn metadata() -> (
534 [::core::primitive::u8; ::ab_io_type::metadata::MAX_METADATA_CAPACITY],
535 ::core::primitive::usize,
536 )
537 {
538 ::ab_io_type::metadata::concat_metadata_sources(&[
539 &[::ab_io_type::metadata::IoTypeMetadataKind::Struct as ::core::primitive::u8],
540 #( #inner_struct_metadata )*
541 ])
542 }
543
544 let (metadata_scratch, metadata_size) = &metadata();
546 <[_]>::split_at(metadata_scratch, *metadata_size).0
549 }})
550}
551
552fn generate_enum_metadata(ident: &Ident, data_enum: &DataEnum) -> Result<TokenStream, Error> {
553 let type_name_string = ident.to_string();
554 let type_name_bytes = type_name_string.as_bytes();
555
556 let type_name_bytes_len = u8::try_from(type_name_bytes.len()).map_err(|_error| {
557 Error::new(
558 ident.span(),
559 format!(
560 "Name of the enum must not be more than {} bytes in length",
561 u8::MAX
562 ),
563 )
564 })?;
565 let num_variants = u8::try_from(data_enum.variants.len()).map_err(|_error| {
566 Error::new(
567 ident.span(),
568 format!("Enum must not have more than {} variants", u8::MAX),
569 )
570 })?;
571 let enum_metadata_header = {
577 let enum_metadata_header = [Literal::u8_unsuffixed(type_name_bytes_len)]
578 .into_iter()
579 .chain(
580 type_name_bytes
581 .iter()
582 .map(|&char| Literal::byte_character(char)),
583 )
584 .chain([Literal::u8_unsuffixed(num_variants)]);
585
586 quote! {
587 &[
588 ::ab_io_type::metadata::IoTypeMetadataKind::Enum as ::core::primitive::u8,
589 #( #enum_metadata_header, )*
590 ]
591 }
592 };
593
594 let inner = data_enum
597 .variants
598 .iter()
599 .flat_map(|variant| generate_inner_struct_metadata(&variant.ident, &variant.fields))
600 .collect::<Result<Vec<TokenStream>, Error>>()?;
601
602 Ok(quote! {{
603 #[inline(always)]
604 const fn metadata() -> (
605 [::core::primitive::u8; ::ab_io_type::metadata::MAX_METADATA_CAPACITY],
606 ::core::primitive::usize,
607 )
608 {
609 ::ab_io_type::metadata::concat_metadata_sources(&[
610 #enum_metadata_header,
611 #( #inner )*
612 ])
613 }
614
615 let (metadata_scratch, metadata_size) = &metadata();
617 <[_]>::split_at(metadata_scratch, *metadata_size).0
620 }})
621}
622
623fn generate_inner_struct_metadata<'a>(
624 ident: &'a Ident,
625 fields: &'a Fields,
626) -> impl Iterator<Item = Result<TokenStream, Error>> + 'a {
627 iter::once_with(move || generate_inner_struct_metadata_header(ident, fields))
628 .chain(generate_fields_metadata(fields))
629}
630
631fn generate_inner_struct_metadata_header(
632 ident: &Ident,
633 fields: &Fields,
634) -> Result<TokenStream, Error> {
635 let ident_string = ident.to_string();
636 let ident_bytes = ident_string.as_bytes();
637
638 let ident_bytes_len = u8::try_from(ident_bytes.len()).map_err(|_error| {
639 Error::new(
640 ident.span(),
641 format!(
642 "Identifier must not be more than {} bytes in length",
643 u8::MAX
644 ),
645 )
646 })?;
647 let num_fields = u8::try_from(fields.len()).map_err(|_error| {
648 Error::new(
649 fields.span(),
650 format!("Must not have more than {} fields", u8::MAX),
651 )
652 })?;
653
654 Ok({
659 let struct_metadata_header = [Literal::u8_unsuffixed(ident_bytes_len)]
660 .into_iter()
661 .chain(
662 ident_bytes
663 .iter()
664 .map(|&char| Literal::byte_character(char)),
665 )
666 .chain([Literal::u8_unsuffixed(num_fields)]);
667
668 quote! {
669 &[#( #struct_metadata_header, )*],
670 }
671 })
672}
673
674fn generate_fields_metadata(
675 fields: &Fields,
676) -> impl Iterator<Item = Result<TokenStream, Error>> + '_ {
677 fields.iter().map(move |field| {
682 let field_metadata = if let Some(field_name) = &field.ident {
683 let field_name_string = field_name.to_string();
684 let field_name_bytes = field_name_string.as_bytes();
685 let field_name_len = u8::try_from(field_name_bytes.len()).map_err(|_error| {
686 Error::new(
687 field.span(),
688 format!(
689 "Name of the field must not be more than {} bytes in length",
690 u8::MAX
691 ),
692 )
693 })?;
694
695 let field_metadata = [Literal::u8_unsuffixed(field_name_len)].into_iter().chain(
696 field_name_bytes
697 .iter()
698 .map(|&char| Literal::byte_character(char)),
699 );
700
701 quote! { #( #field_metadata, )* }
702 } else {
703 quote! { 0, }
704 };
705 let field_type = &field.ty;
706
707 Ok(quote! {
708 &[ #field_metadata ],
709 <#field_type as ::ab_io_type::trivial_type::TrivialType>::METADATA,
710 })
711 })
712}