From 230f0f395a5158326e5f07209f1eafd41183704a Mon Sep 17 00:00:00 2001 From: Wyatt Herkamp Date: Fri, 13 Oct 2023 08:59:51 -0400 Subject: [PATCH] Generics are now supported in Macros --- CHANGELOG.md | 2 ++ digestible/tests/generics.rs | 32 ++++++++++++++++++ macros/src/container_attrs.rs | 24 +++++++------- macros/src/expand_enum.rs | 49 +++++++++++++++------------- macros/src/expand_struct.rs | 26 +++++++++------ macros/src/fields.rs | 2 +- macros/src/lib.rs | 3 +- macros/src/paths.rs | 30 ----------------- macros/src/shared.rs | 20 ------------ macros/src/utils.rs | 61 +++++++++++++++++++++++++++++++++++ 10 files changed, 151 insertions(+), 98 deletions(-) create mode 100644 digestible/tests/generics.rs delete mode 100644 macros/src/paths.rs delete mode 100644 macros/src/shared.rs create mode 100644 macros/src/utils.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ab8ed3..4faf0b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.2.3 (Unreleased) +- Generics are now supported in Macros ## 0.2.2 (2023-10-13) - Fixed Unresolved path for `core::any` diff --git a/digestible/tests/generics.rs b/digestible/tests/generics.rs new file mode 100644 index 0000000..8acb973 --- /dev/null +++ b/digestible/tests/generics.rs @@ -0,0 +1,32 @@ +use std::collections::hash_map::DefaultHasher; +use std::hash::Hash; +use digestible::Digestible; + +#[derive(Digestible)] +#[digestible(hash = LittleEndian)] +pub struct MyStructGenerics { + pub id: u32, + pub t: T, +} +#[derive(Digestible)] +pub struct MyStructGenericsAlreadyRequired { + pub id: u32, + pub t: T, +} + +#[test] +pub fn hash_test() { + let test = MyStructGenerics { + id: 0, + t: "Test".to_string(), + }; + let mut default_hasher = DefaultHasher::new(); + test.hash(&mut default_hasher); +} + + +#[derive(Digestible)] +pub enum MyEnum { + A(T), + B(u32), +} \ No newline at end of file diff --git a/macros/src/container_attrs.rs b/macros/src/container_attrs.rs index 8db0606..1bed5b2 100644 --- a/macros/src/container_attrs.rs +++ b/macros/src/container_attrs.rs @@ -1,6 +1,6 @@ use syn::parse::{Parse, ParseStream}; -use syn::{parse_quote, Path}; +use syn::{Attribute, parse_quote, Path}; #[derive(Debug)] pub enum TypeHeader { @@ -77,16 +77,14 @@ impl Parse for ContainerAttrs { Ok(attr) } } -macro_rules! get_container_attrs { - ($input:ident) => { - $input - .attrs - .iter() - .find(|v| v.path().is_ident("digestible")) - .map(|v| v.parse_args::()) - .transpose()? - .unwrap_or_default() - }; +pub fn get_container_attrs(attrs: &[Attribute])-> syn::Result { + let attrs = attrs + .iter() + .find(|v| v.path().is_ident("digestible")) + .map(|v| v.parse_args::()) + .transpose()? + .unwrap_or_default(); + Ok(attrs) } -use crate::paths::byte_order_impl_path; -pub(crate) use get_container_attrs; + +use crate::utils::byte_order_impl_path; diff --git a/macros/src/expand_enum.rs b/macros/src/expand_enum.rs index 1fe3ef1..98b6221 100644 --- a/macros/src/expand_enum.rs +++ b/macros/src/expand_enum.rs @@ -1,7 +1,7 @@ -use crate::paths::{digest_writer, digestible_path, private_path}; -use crate::container_attrs::{get_container_attrs, ContainerAttrs, TypeHeader}; +use crate::utils::{digest_writer, digestible_path, private_path}; +use crate::container_attrs::{get_container_attrs, TypeHeader}; use crate::fields::Field; -use crate::shared; +use crate::{ utils}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, ToTokens}; use syn::{DeriveInput, Path}; @@ -43,28 +43,27 @@ impl<'a> Variant<'a> { pub fn catch_block(&self, enum_name: &Ident) -> TokenStream { let ident = &self.ident; let writer = self.writer; - let endian = self.endian; let fields: Vec<_> = self.fields.iter().map(|v| &v.ident).collect(); let fn_name = format_ident!("digest_{}", self.ident); match self.enum_type { EnumType::Unit => { quote! { #enum_name::#ident => { - #fn_name::<#endian, W>(#writer); + #fn_name(#writer); } } } EnumType::Tuple => { quote! { #enum_name::#ident(#(#fields),*) => { - #fn_name::<#endian, W>(#writer, #(#fields),*); + #fn_name(#writer, #(#fields),*); } } } EnumType::Struct => { quote! { #enum_name::#ident{#(#fields),*} => { - #fn_name::<#endian, W>(#writer, #(#fields),*); + #fn_name(#writer, #(#fields),*); } } } @@ -89,26 +88,32 @@ impl ToTokens for Variant<'_> { let digest_writer = digest_writer(); let ident = &self.ident; let writer = self.writer; - let endian = self.endian; - let byte_order_path = crate::paths::byte_order_path(); let result = quote! { - fn #fn_name<#endian: #byte_order_path, W: #digest_writer>( - #writer: &mut W, - #(#fields_def),* - ) { + let #fn_name = |#writer: &mut W, #(#fields_def),*| { #digest_writer::write(writer, stringify!(#ident).as_bytes()); #(#fields)* - } + }; }; tokens.extend(result); } } pub(crate) fn expand(derive_input: DeriveInput) -> Result { - let name = &derive_input.ident; - let container_attrs = get_container_attrs!(derive_input); - let syn::Data::Enum(as_enum) = derive_input.data else { - unreachable!("digestible can only be derived for enums (expand_enum.rs)") + + let DeriveInput { + attrs, + ident, + mut generics, + data,.. + } = derive_input; + let syn::Data::Enum(as_enum) = data else { + //Checked before + unsafe{ + std::hint::unreachable_unchecked(); + } }; + utils::add_digestible_trait(&mut generics); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let container_attrs = get_container_attrs(&attrs)?; let writer = format_ident!("writer"); let order = format_ident!("B"); @@ -132,11 +137,11 @@ pub(crate) fn expand(derive_input: DeriveInput) -> Result { let variant = Variant::new(variant, &order, &writer)?; variants.push(variant); } - let catch_block: Vec<_> = variants.iter().map(|v| v.catch_block(name)).collect(); + let catch_block: Vec<_> = variants.iter().map(|v| v.catch_block(&ident)).collect(); let digestible = digestible_path(); - let byte_order_path = crate::paths::byte_order_path(); + let byte_order_path = crate::utils::byte_order_path(); let impl_hash = if let Some(impl_hash) = container_attrs.impl_hash { - shared::impl_hash(name, impl_hash) + utils::impl_hash(&ident, impl_hash, &impl_generics, &ty_generics, &where_clause) } else { quote! {} }; @@ -145,7 +150,7 @@ pub(crate) fn expand(derive_input: DeriveInput) -> Result { #[allow(unused_extern_crates, clippy::useless_attribute)] extern crate digestible as _digestible; #[automatically_derived] - impl #digestible for #name { + impl #impl_generics #digestible for #ident #ty_generics #where_clause { #[allow(non_snake_case)] fn digest<#order: #byte_order_path, W: _digestible::DigestWriter>( &self, diff --git a/macros/src/expand_struct.rs b/macros/src/expand_struct.rs index b8716d6..99a9fd7 100644 --- a/macros/src/expand_struct.rs +++ b/macros/src/expand_struct.rs @@ -1,17 +1,23 @@ -use crate::paths::{digest_writer, digestible_path, private_path}; -use crate::container_attrs::{get_container_attrs, ContainerAttrs, TypeHeader}; +use crate::utils::{digest_writer, digestible_path, private_path}; +use crate::container_attrs::{get_container_attrs, TypeHeader}; use crate::fields::Field; -use crate::shared; +use crate::{utils}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{DeriveInput, Path}; use syn::{Fields, Result}; pub(crate) fn expand(derive_input: DeriveInput) -> Result { - let name = &derive_input.ident; - let container_attrs = get_container_attrs!(derive_input); - let syn::Data::Struct(as_struct) = derive_input.data else { - unreachable!("digestible can only be derived for enums (expand_struct.rs)") + let DeriveInput{ attrs, ident, mut generics, data,.. } = derive_input; + utils::add_digestible_trait(&mut generics); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let container_attrs = get_container_attrs(&attrs)?; + let syn::Data::Struct(as_struct) = data else { + // This is checked before + unsafe{ + std::hint::unreachable_unchecked(); + } }; let mut fields = Vec::with_capacity(as_struct.fields.len()); @@ -53,9 +59,9 @@ pub(crate) fn expand(derive_input: DeriveInput) -> Result { todo!("type_id") } }; - let byte_order_path = crate::paths::byte_order_path(); + let byte_order_path = utils::byte_order_path(); let impl_hash = if let Some(impl_hash) = container_attrs.impl_hash { - shared::impl_hash(name, impl_hash) + utils::impl_hash(&ident, impl_hash, &impl_generics, &ty_generics, &where_clause) } else { quote! {} }; @@ -66,7 +72,7 @@ pub(crate) fn expand(derive_input: DeriveInput) -> Result { #[allow(unused_extern_crates, clippy::useless_attribute)] extern crate digestible as _digestible; #[automatically_derived] - impl #digestible for #name { + impl #impl_generics #digestible for #ident #ty_generics #where_clause { fn digest<#order: #byte_order_path, W: _digestible::DigestWriter>( &self, writer: &mut W, diff --git a/macros/src/fields.rs b/macros/src/fields.rs index b684f70..79594ca 100644 --- a/macros/src/fields.rs +++ b/macros/src/fields.rs @@ -1,4 +1,4 @@ -use crate::paths::{digest_with_path, digestible_path}; +use crate::utils::{digest_with_path, digestible_path}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, ToTokens}; use syn::parse::{Parse, ParseStream}; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 5832e70..cd57eca 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,9 +1,8 @@ -mod paths; +mod utils; mod container_attrs; mod expand_enum; mod expand_struct; mod fields; -mod shared; use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; diff --git a/macros/src/paths.rs b/macros/src/paths.rs deleted file mode 100644 index ae7a2a3..0000000 --- a/macros/src/paths.rs +++ /dev/null @@ -1,30 +0,0 @@ -use proc_macro2::Ident; -use syn::{parse_quote, Path}; - -pub fn digestible_path() -> Path { - parse_quote!(_digestible::Digestible) -} -pub fn digest_writer() -> Path { - parse_quote!(_digestible::DigestWriter) -} -pub fn digest_with_path(path: Path) -> Path { - parse_quote!(_digestible::digest_with::#path) -} -pub fn digester_using_hasher() -> Path { - parse_quote!(_digestible::hash_digester::DigesterUsingHasher) -} - -pub fn byte_order_path() -> Path { - parse_quote!(_digestible::byteorder::ByteOrder) -} -pub fn byte_order_impl_path(ident: Ident) -> Path { - parse_quote!(_digestible::byteorder::#ident) -} - -macro_rules! private_path { - // `()` indicates that the macro takes no argument. - ($key:ident) => { - syn::parse_quote!(_digestible::_private::$key) - }; -} -pub(crate) use private_path; \ No newline at end of file diff --git a/macros/src/shared.rs b/macros/src/shared.rs deleted file mode 100644 index 589c01a..0000000 --- a/macros/src/shared.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::paths::{digester_using_hasher, private_path}; -use proc_macro2::{Ident, TokenStream}; -use quote::quote; -use syn::Path; - -pub fn impl_hash(container: &Ident, endian_path: Path) -> TokenStream { - let digester_using_hasher = digester_using_hasher(); - let digestible_path = crate::paths::digestible_path(); - let hash: Path = private_path!(Hash); - let hasher: Path = private_path!(Hasher); - quote! { - #[automatically_derived] - impl #hash for #container { - fn hash(&self, state: &mut H) { - let mut digester = #digester_using_hasher(state); - ::digest::<#endian_path, _>(self,&mut digester); - } - } - } -} diff --git a/macros/src/utils.rs b/macros/src/utils.rs new file mode 100644 index 0000000..14cea8e --- /dev/null +++ b/macros/src/utils.rs @@ -0,0 +1,61 @@ +use proc_macro2::{Ident, TokenStream}; +use syn::{GenericParam, Generics, ImplGenerics, parse_quote, Path, TypeGenerics, WhereClause}; + +pub fn digestible_path() -> Path { + parse_quote!(_digestible::Digestible) +} +pub fn digest_writer() -> Path { + parse_quote!(_digestible::DigestWriter) +} +pub fn digest_with_path(path: Path) -> Path { + parse_quote!(_digestible::digest_with::#path) +} +pub fn digester_using_hasher() -> Path { + parse_quote!(_digestible::hash_digester::DigesterUsingHasher) +} + +pub fn byte_order_path() -> Path { + parse_quote!(_digestible::byteorder::ByteOrder) +} +pub fn byte_order_impl_path(ident: Ident) -> Path { + parse_quote!(_digestible::byteorder::#ident) +} + +macro_rules! private_path { + // `()` indicates that the macro takes no argument. + ($key:ident) => { + syn::parse_quote!(_digestible::_private::$key) + }; +} +pub(crate) use private_path; + +pub fn add_digestible_trait(generics: &mut Generics) { + if generics.params.is_empty() { + return; + } + for param in &mut generics.params { + if let GenericParam::Type(ty) = param { + ty.bounds.push(parse_quote!(_digestible::Digestible)); + } + } +} + +use quote::quote; + +/// Implements `Hash` for the container. +/// Using Digestible +pub fn impl_hash(container: &Ident, endian_path: Path, impl_generics: &ImplGenerics, ty_generics: &TypeGenerics, where_clause: &Option<&WhereClause>) -> TokenStream { + let digester_using_hasher = digester_using_hasher(); + let digestible_path = digestible_path(); + let hash: Path = private_path!(Hash); + let hasher: Path = private_path!(Hasher); + quote! { + #[automatically_derived] + impl #impl_generics #hash for #container #ty_generics #where_clause { + fn hash(&self, state: &mut H) { + let mut digester = #digester_using_hasher(state); + ::digest::<#endian_path, _>(self,&mut digester); + } + } + } +}