lib.rs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. //! Commonly used macros of libthreema.
  2. use convert_case::{Case, Casing as _};
  3. use proc_macro::TokenStream;
  4. use quote::{format_ident, quote};
  5. use syn::{
  6. self,
  7. parse::{Parse, ParseStream},
  8. parse_macro_input,
  9. punctuated::Punctuated,
  10. Data, DeriveInput, Expr, Fields, Ident, Variant,
  11. };
  12. /// Provides variant names for an `enum`.
  13. ///
  14. /// # Examples
  15. ///
  16. /// Given the following:
  17. ///
  18. /// ```
  19. /// use libthreema_macros::VariantNames;
  20. ///
  21. /// #[derive(VariantNames)]
  22. /// enum Something {
  23. /// SomeItem,
  24. /// SomeOtherItem(u64),
  25. /// }
  26. /// ```
  27. ///
  28. /// the derive macro expands it to:
  29. ///
  30. /// ```
  31. /// enum Something {
  32. /// SomeItem,
  33. /// SomeOtherItem(u64),
  34. /// }
  35. ///
  36. /// impl Something {
  37. /// pub const SOME_ITEM: &'static str = "SomeItem";
  38. /// pub const SOME_OTHER_ITEM: &'static str = "SomeOtherItem";
  39. ///
  40. /// pub const fn variant_name(&self) -> &'static str {
  41. /// match self {
  42. /// Self::SomeItem => Self::SOME_ITEM,
  43. /// Self::SomeOtherItem(..) => Self::SOME_OTHER_ITEM,
  44. /// }
  45. /// }
  46. /// }
  47. /// ```
  48. #[proc_macro_derive(VariantNames)]
  49. pub fn derive_variant_names(input: TokenStream) -> TokenStream {
  50. fn get_const_name(variant: &Variant) -> Ident {
  51. format_ident!(
  52. "{}",
  53. variant
  54. .ident
  55. .to_string()
  56. .from_case(Case::Pascal)
  57. .to_case(Case::UpperSnake)
  58. )
  59. }
  60. // Parse the input tokens into a syntax tree.
  61. let input = parse_macro_input!(input as DeriveInput);
  62. let enum_name = input.ident;
  63. // Map each variant to its literal identifier name
  64. let const_variants = match &input.data {
  65. Data::Enum(data) => data.variants.iter().map(|variant| {
  66. let docstring = format!(" Variant name of [`{}::{}`].", enum_name, variant.ident);
  67. let const_name = get_const_name(variant);
  68. let literal_name = variant.ident.to_string();
  69. quote! {
  70. #[doc = #docstring]
  71. pub const #const_name: &'static str = #literal_name;
  72. }
  73. }),
  74. #[allow(clippy::unimplemented)]
  75. _ => unimplemented!(),
  76. };
  77. let mapped_variants = match &input.data {
  78. Data::Enum(data) => data.variants.iter().map(|variant| {
  79. let variant_name = &variant.ident;
  80. let parameters = match variant.fields {
  81. Fields::Unit => quote! {},
  82. Fields::Unnamed(..) => quote! { (..) },
  83. Fields::Named(..) => quote! { {..} },
  84. };
  85. let const_name = get_const_name(variant);
  86. quote! {
  87. Self::#variant_name #parameters => Self::#const_name
  88. }
  89. }),
  90. #[allow(clippy::unimplemented)]
  91. _ => unimplemented!(),
  92. };
  93. // Implement for the enum
  94. let expanded = quote! {
  95. impl #enum_name {
  96. #(#const_variants)*
  97. /// Get the variant name of `self`.
  98. pub const fn variant_name(&self) -> &'static str {
  99. match self {
  100. #(#mapped_variants),*
  101. }
  102. }
  103. }
  104. };
  105. // Generate code
  106. TokenStream::from(expanded)
  107. }
  108. /// Implements [`Debug`] for the provided `enum`. Depends on [`VariantNames`].
  109. ///
  110. /// # Examples
  111. ///
  112. /// Given the following:
  113. ///
  114. /// ```
  115. /// use libthreema_macros::{DebugVariantNames, VariantNames};
  116. ///
  117. /// #[derive(DebugVariantNames, VariantNames)]
  118. /// enum Something {
  119. /// SomeItem,
  120. /// SomeOtherItem(u64),
  121. /// }
  122. /// ```
  123. ///
  124. /// the derive macro expands it to:
  125. ///
  126. /// ```
  127. /// # use libthreema_macros::VariantNames;
  128. /// #
  129. /// # #[derive(VariantNames)]
  130. /// enum Something {
  131. /// SomeItem,
  132. /// SomeOtherItem(u64),
  133. /// }
  134. ///
  135. /// // Omitting expansion of `VariantNames` here.
  136. ///
  137. /// impl std::fmt::Debug for Something {
  138. /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  139. /// f.write_fmt(format_args!("{}::{}", "Something", self.variant_name()))
  140. /// }
  141. /// }
  142. /// ```
  143. #[proc_macro_derive(DebugVariantNames)]
  144. pub fn derive_debug_variant_names(input: TokenStream) -> TokenStream {
  145. // Parse the input tokens into a syntax tree.
  146. let input = parse_macro_input!(input as DeriveInput);
  147. // Ensure its an enum
  148. #[allow(clippy::unimplemented)]
  149. if !matches!(input.data, Data::Enum(..)) {
  150. unimplemented!()
  151. }
  152. // Implement `Debug` for the enum
  153. let name = input.ident;
  154. let literal_name = name.to_string();
  155. let expanded = quote! {
  156. impl std::fmt::Debug for #name {
  157. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  158. f.write_fmt(format_args!("{}::{}", #literal_name, self.variant_name()))
  159. }
  160. }
  161. };
  162. // Generate code
  163. TokenStream::from(expanded)
  164. }
  165. struct Arrays(Punctuated<Expr, syn::Token![,]>);
  166. impl Parse for Arrays {
  167. fn parse(input: ParseStream) -> syn::parse::Result<Arrays> {
  168. let punctuated = Punctuated::parse_terminated(input)?;
  169. Ok(Arrays(punctuated))
  170. }
  171. }
  172. /// Concatenates fixed-size byte arrays into a single large byte array.
  173. ///
  174. /// # Examples
  175. ///
  176. /// ```
  177. /// use libthreema_macros::concat_fixed_bytes;
  178. ///
  179. /// let a = [1u8; 4];
  180. /// let b = [2u8; 3];
  181. /// let c = [3u8; 3];
  182. ///
  183. /// let concatenated: [u8; 10] = concat_fixed_bytes!(a, b, c);
  184. /// assert_eq!(concatenated, [1, 1, 1, 1, 2, 2, 2, 3, 3, 3]);
  185. /// ```
  186. #[proc_macro]
  187. pub fn concat_fixed_bytes(tokens: TokenStream) -> TokenStream {
  188. let input = syn::parse_macro_input!(tokens as Arrays).0.into_iter();
  189. let indices = input.clone().enumerate();
  190. let arrays: Vec<Expr> = input.collect();
  191. let field_length_parameters: Vec<Ident> = indices
  192. .clone()
  193. .map(|(index, _)| format_ident!("T{index}"))
  194. .collect();
  195. let field_names: Vec<Ident> = indices
  196. .map(|(index, _)| format_ident!("t{index}"))
  197. .collect();
  198. let expanded = quote! {{
  199. #[repr(C)]
  200. struct ConcatenatedArrays<#(const #field_length_parameters: usize,)*> {
  201. #(#field_names: [u8; #field_length_parameters],)*
  202. }
  203. let concatenated_arrays = ConcatenatedArrays {
  204. #(#field_names: #arrays,)*
  205. };
  206. unsafe {
  207. core::mem::transmute(concatenated_arrays)
  208. }
  209. }};
  210. // Generate code
  211. TokenStream::from(expanded)
  212. }
  213. // Avoids test dependencies to be picked up by the linter.
  214. #[cfg(test)]
  215. mod external_crate_false_positives {
  216. use trybuild as _;
  217. }