| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- //! Commonly used macros of libthreema.
- use convert_case::{Case, Casing as _};
- use proc_macro::TokenStream;
- use quote::{ToTokens as _, format_ident, quote};
- use syn::{
- self, Data, DeriveInput, Expr, Fields, Ident, ItemStruct, LitInt, LitStr, Variant,
- parse::{Parse, ParseStream},
- parse_macro_input, parse_quote,
- punctuated::Punctuated,
- };
- /// Provides the name of a `struct`, `enum` or `union`.
- ///
- /// # Examples
- ///
- /// Given the following:
- ///
- /// ```nobuild
- /// use libthreema_macros::Name;
- /// use crate::utils::debug::Name;
- ///
- /// #[derive(Name)]
- /// struct Something;
- /// ```
- ///
- /// the derive macro expands it to:
- ///
- /// ```nobuild
- /// struct Something;
- /// impl Name for Something {
- /// const NAME: &'static str = "Something";
- /// }
- /// ```
- #[proc_macro_derive(Name)]
- pub fn derive_name(input: TokenStream) -> TokenStream {
- // Parse the input tokens into a syntax tree.
- let input = parse_macro_input!(input as DeriveInput);
- // Implement `NAME`
- let name = input.ident;
- let literal_name = name.to_string();
- let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
- let expanded = quote! {
- impl #impl_generics crate::utils::debug::Name for #name #type_generics #where_clause {
- /// The name for debugging purposes
- const NAME: &'static str = #literal_name;
- }
- };
- // Generate code
- TokenStream::from(expanded)
- }
- /// Provides variant names for an `enum`.
- ///
- /// # Examples
- ///
- /// Given the following:
- ///
- /// ```
- /// use libthreema_macros::VariantNames;
- ///
- /// #[derive(VariantNames)]
- /// enum Something {
- /// SomeItem,
- /// SomeOtherItem(u64),
- /// }
- /// ```
- ///
- /// the derive macro expands it to:
- ///
- /// ```
- /// enum Something {
- /// SomeItem,
- /// SomeOtherItem(u64),
- /// }
- ///
- /// impl Something {
- /// pub const SOME_ITEM: &'static str = "SomeItem";
- /// pub const SOME_OTHER_ITEM: &'static str = "SomeOtherItem";
- ///
- /// pub const fn variant_name(&self) -> &'static str {
- /// match self {
- /// Self::SomeItem => Self::SOME_ITEM,
- /// Self::SomeOtherItem(..) => Self::SOME_OTHER_ITEM,
- /// }
- /// }
- /// }
- /// ```
- #[proc_macro_derive(VariantNames)]
- pub fn derive_variant_names(input: TokenStream) -> TokenStream {
- fn get_const_name(variant: &Variant) -> Ident {
- format_ident!(
- "{}",
- variant
- .ident
- .to_string()
- .from_case(Case::Pascal)
- .to_case(Case::UpperSnake)
- )
- }
- // Parse the input tokens into a syntax tree.
- let input = parse_macro_input!(input as DeriveInput);
- let enum_name = input.ident;
- let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
- // Map each variant to its literal identifier name
- let const_variants = match &input.data {
- Data::Enum(data) => data.variants.iter().map(|variant| {
- let docstring = format!(" Variant name of [`{}::{}`].", enum_name, variant.ident);
- let const_name = get_const_name(variant);
- let literal_name = variant.ident.to_string();
- quote! {
- #[doc = #docstring]
- pub const #const_name: &'static str = #literal_name;
- }
- }),
- #[expect(clippy::unimplemented, reason = "Only applicable to enums")]
- _ => unimplemented!(),
- };
- let mapped_variants = match &input.data {
- Data::Enum(data) => data.variants.iter().map(|variant| {
- let variant_name = &variant.ident;
- let parameters = match variant.fields {
- Fields::Unit => quote! {},
- Fields::Unnamed(..) => quote! { (..) },
- Fields::Named(..) => quote! { {..} },
- };
- let const_name = get_const_name(variant);
- quote! {
- Self::#variant_name #parameters => Self::#const_name
- }
- }),
- #[expect(clippy::unimplemented, reason = "Only applicable to enums")]
- _ => unimplemented!(),
- };
- // Implement for the enum
- let expanded = quote! {
- impl #impl_generics #enum_name #type_generics #where_clause {
- #(#const_variants)*
- /// Get the variant name of `self`.
- pub const fn variant_name(&self) -> &'static str {
- match self {
- #(#mapped_variants),*
- }
- }
- }
- };
- // Generate code
- TokenStream::from(expanded)
- }
- /// Implements [`Debug`] for the provided `enum`. Depends on [`VariantNames`].
- ///
- /// # Examples
- ///
- /// Given the following:
- ///
- /// ```
- /// use libthreema_macros::{DebugVariantNames, VariantNames};
- ///
- /// #[derive(DebugVariantNames, VariantNames)]
- /// enum Something {
- /// SomeItem,
- /// SomeOtherItem(u64),
- /// }
- /// ```
- ///
- /// the derive macro expands it to:
- ///
- /// ```
- /// # use libthreema_macros::VariantNames;
- /// #
- /// # #[derive(VariantNames)]
- /// enum Something {
- /// SomeItem,
- /// SomeOtherItem(u64),
- /// }
- ///
- /// // Omitting expansion of `VariantNames` here.
- ///
- /// impl std::fmt::Debug for Something {
- /// fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- /// write!(formatter, "{}::{}", "Something", self.variant_name())
- /// }
- /// }
- /// ```
- #[proc_macro_derive(DebugVariantNames)]
- pub fn derive_debug_variant_names(input: TokenStream) -> TokenStream {
- // Parse the input tokens into a syntax tree.
- let input = parse_macro_input!(input as DeriveInput);
- // Ensure it's an enum
- #[expect(clippy::unimplemented, reason = "Only applicable to enums")]
- if !matches!(input.data, Data::Enum(..)) {
- unimplemented!()
- }
- // Implement `Debug` for the enum
- let name = input.ident;
- let literal_name = name.to_string();
- let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
- let expanded = quote! {
- impl #impl_generics std::fmt::Debug for #name #type_generics #where_clause {
- fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(formatter, "{}::{}", #literal_name, self.variant_name())
- }
- }
- };
- // Generate code
- TokenStream::from(expanded)
- }
- struct Arrays(Punctuated<Expr, syn::Token![,]>);
- impl Parse for Arrays {
- fn parse(input: ParseStream) -> syn::parse::Result<Arrays> {
- let punctuated = Punctuated::parse_terminated(input)?;
- Ok(Arrays(punctuated))
- }
- }
- /// Concatenates fixed-size byte arrays into a single large byte array.
- ///
- /// # Examples
- ///
- /// ```
- /// use libthreema_macros::concat_fixed_bytes;
- ///
- /// let a = [1u8; 4];
- /// let b = [2u8; 3];
- /// let c = [3u8; 3];
- ///
- /// let concatenated: [u8; 10] = concat_fixed_bytes!(a, b, c);
- /// assert_eq!(concatenated, [1, 1, 1, 1, 2, 2, 2, 3, 3, 3]);
- /// ```
- #[proc_macro]
- pub fn concat_fixed_bytes(tokens: TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as Arrays).0.into_iter();
- let indices = input.clone().enumerate();
- let arrays: Vec<Expr> = input.collect();
- let field_length_parameters: Vec<Ident> = indices
- .clone()
- .map(|(index, _)| format_ident!("T{index}"))
- .collect();
- let field_names: Vec<Ident> = indices.map(|(index, _)| format_ident!("t{index}")).collect();
- let expanded = quote! {{
- #[repr(C)]
- struct ConcatenatedArrays<#(const #field_length_parameters: usize,)*> {
- #(#field_names: [u8; #field_length_parameters],)*
- }
- let concatenated_arrays = ConcatenatedArrays {
- #(#field_names: #arrays,)*
- };
- unsafe {
- let concatenated_bytes = core::mem::transmute(concatenated_arrays);
- concatenated_bytes
- }
- }};
- // Generate code
- TokenStream::from(expanded)
- }
- /// Annotates a `prost::Message` in the following way:
- ///
- /// ## `padding` fields
- ///
- /// These fields will be marked as deprecated to discourage direct usage of it. Furthermore, the padding tag
- /// will be extracted and made available on the message as a `PADDING_TAG` const. See the
- /// `ProtobufPaddedMessage` trait.
- #[proc_macro_attribute]
- pub fn protobuf_annotations(_attribute: TokenStream, input: TokenStream) -> TokenStream {
- fn annotate_protobuf_message(mut message: ItemStruct) -> syn::Result<TokenStream> {
- let mut padding_tag: Option<LitInt> = None;
- for field in &mut message.fields {
- let Some(name) = field.ident.as_ref() else {
- continue;
- };
- // Process `padding` fields so that we can use `ProtobufPaddedMessage` on them easily
- if name == "padding" {
- // Look for the tag value in `#[prost(..., tag = "<tag-value>")]`
- for attribute in &field.attrs {
- if !attribute.path().is_ident("prost") {
- continue;
- }
- attribute.parse_nested_meta(|meta| {
- let value: LitStr = meta.value()?.parse()?;
- if meta.path.is_ident("tag") {
- padding_tag = Some(value.parse()?);
- }
- Ok(())
- })?;
- }
- // Ensure nobody uses the field directly by deprecating it
- field.attrs.push(parse_quote! {
- #[deprecated(note = "Use ProtobufPaddedMessage trait to generate padding")]
- });
- }
- }
- let message_name = message.ident.clone();
- let mut output = message.into_token_stream();
- // Add any padding tag value as a const to the message
- if let Some(padding_tag) = padding_tag {
- output.extend(quote! {
- impl #message_name {
- /// Tag value of the padding of this message.
- pub const PADDING_TAG: u32 = #padding_tag;
- }
- });
- }
- Ok(output.into())
- }
- annotate_protobuf_message(parse_macro_input!(input as ItemStruct))
- .unwrap_or_else(|error| error.into_compile_error().into())
- }
- /// Implements [`subtle::ConstantTimeEq`] for named and unnamed structs.
- ///
- /// Moreover, this derives [`PartialEq`] and [`Eq`] using constant time comparison.
- ///
- /// Note: All fields must implement [`subtle::ConstantTimeEq`].
- ///
- /// The proc macro was adapted from <https://github.com/dalek-cryptography/subtle/pull/111>
- ///
- /// # Examples
- ///
- /// Given the following:
- ///
- /// ```
- /// use libthreema_macros::ConstantTimeEq;
- ///
- /// #[derive(ConstantTimeEq)]
- /// struct MyStruct {
- /// first_field: [u8; 32],
- /// second_field: u64,
- /// }
- /// ```
- ///
- /// the derive macro expands it to:
- ///
- /// ```
- /// struct MyStruct {
- /// first_field: [u8; 32],
- /// second_field: u64,
- /// }
- ///
- /// impl ::subtle::ConstantTimeEq for MyStruct {
- /// #[inline]
- /// fn ct_eq(&self, other: &Self) -> ::subtle::Choice {
- /// use ::subtle::ConstantTimeEq as _;
- /// return { self.first_field }.ct_eq(&{ other.first_field })
- /// & { self.second_field }.ct_eq(&{ other.second_field });
- /// }
- /// }
- /// impl PartialEq<Self> for MyStruct {
- /// #[inline]
- /// fn eq(&self, other: &Self) -> bool {
- /// use ::subtle::ConstantTimeEq as _;
- /// bool::from(self.ct_eq(other))
- /// }
- /// }
- /// impl Eq for MyStruct {}
- /// ```
- #[proc_macro_derive(ConstantTimeEq)]
- pub fn constant_time_eq(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input as DeriveInput);
- #[expect(
- clippy::unimplemented,
- reason = "Only applicable to named and unnamed structs"
- )]
- let Data::Struct(data_struct) = input.data else {
- unimplemented!()
- };
- let constant_time_eq_stream = match &data_struct.fields {
- Fields::Named(fields_named) => {
- let mut token_stream = quote! {};
- let mut fields = fields_named.named.iter().peekable();
- while let Some(field) = fields.next() {
- let ident = &field.ident;
- token_stream.extend(quote! { {self.#ident}.ct_eq(&{other.#ident}) });
- if fields.peek().is_some() {
- token_stream.extend(quote! { & });
- }
- }
- token_stream
- },
- Fields::Unnamed(unnamed_fields) => {
- let mut token_stream = quote! {};
- let mut fields = unnamed_fields.unnamed.iter().enumerate().peekable();
- while let Some(field) = fields.next() {
- let index = syn::Index::from(field.0);
- token_stream.extend(quote! { {self.#index}.ct_eq(&{other.#index}) });
- if fields.peek().is_some() {
- token_stream.extend(quote! { & });
- }
- }
- token_stream
- },
- #[expect(clippy::unimplemented, reason = "Not applicable to unit-like structs")]
- Fields::Unit => unimplemented!(),
- };
- let name = &input.ident;
- let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
- let expanded = quote! {
- impl #impl_generics ::subtle::ConstantTimeEq for #name #type_generics #where_clause {
- #[inline]
- fn ct_eq(&self, other: &Self) -> ::subtle::Choice {
- use ::subtle::ConstantTimeEq as _;
- return #constant_time_eq_stream
- }
- }
- impl #impl_generics PartialEq<Self> for #name #type_generics #where_clause {
- #[inline]
- fn eq(&self, other: &Self) -> bool{
- use ::subtle::ConstantTimeEq as _;
- bool::from(self.ct_eq(other))
- }
- }
- impl #impl_generics Eq for #name #type_generics #where_clause {}
- };
- TokenStream::from(expanded)
- }
- // Avoids dependencies to be picked up by the linter.
- mod external_crate_false_positives {
- use subtle as _;
- }
- // Avoids test dependencies to be picked up by the linter.
- #[cfg(test)]
- mod external_crate_false_positives_test_feature {
- use rstest as _;
- use trybuild as _;
- }
|