From dd0ca1f686310a3e2f2244c20fcf86f8c614c801 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 20 Sep 2023 13:46:01 +0300 Subject: [PATCH] Refactor `TryFrom` --- impl/src/try_from.rs | 131 +++++++---------------------- impl/src/utils.rs | 196 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 221 insertions(+), 106 deletions(-) diff --git a/impl/src/try_from.rs b/impl/src/try_from.rs index 8a84c685..9dbd9264 100644 --- a/impl/src/try_from.rs +++ b/impl/src/try_from.rs @@ -1,11 +1,14 @@ //! Implementation of a [`TryFrom`] derive macro. -use std::mem; - -use proc_macro2::{Literal, Span, TokenStream}; +use proc_macro2::{Literal, TokenStream}; use quote::{format_ident, quote, ToTokens}; use syn::spanned::Spanned as _; +use crate::utils::{ + attr::{self, ParseMultiple as _}, + Spanning, +}; + /// Expands a [`TryFrom`] derive macro. pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result { match &input.data { @@ -14,8 +17,21 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result Ok(Expansion { - repr: ReprAttribute::parse_attrs(&input.attrs)?, - attr: ItemAttribute::parse_attrs(&input.attrs)?, + repr: attr::ReprInt::parse_attrs(&input.attrs, &format_ident!("repr"))? + .map(Spanning::into_inner) + .unwrap_or_default(), + attr: ItemAttribute::parse_attrs(&input.attrs, &format_ident!("try_from"))? + .map(|attr| { + if matches!(attr.item, ItemAttribute::Types(_)) { + Err(syn::Error::new( + attr.span, + "`#[try_from(repr(...))]` attribute is not supported yet", + )) + } else { + Ok(attr.item) + } + }) + .transpose()?, ident: input.ident.clone(), generics: input.generics.clone(), variants: data.variants.clone().into_iter().collect(), @@ -32,100 +48,14 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result))] /// ``` -struct ItemAttribute; - -impl ItemAttribute { - /// Parses am [`ItemAttribute`] from the provided [`syn::Attribute`]s. - fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result> { - attrs - .as_ref() - .iter() - .filter(|attr| attr.path().is_ident("try_from")) - .try_fold(None, |mut attrs, attr| { - let mut parsed = None; - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("repr") { - parsed = Some(ItemAttribute); - Ok(()) - } else { - Err(meta.error("only `repr` is allowed here")) - } - })?; - if mem::replace(&mut attrs, parsed).is_some() { - Err(syn::Error::new( - attr.span(), - "only single `#[try_from(repr)]` attribute is allowed here", - )) - } else { - Ok(attrs) - } - }) - } -} - -/// Representation of a [`#[repr(u/i*)]` Rust attribute][0]. -/// -/// **NOTE**: Disregards any non-integer representation `#[repr]`s. -/// -/// ```rust,ignore -/// #[repr()] -/// ``` -/// -/// [0]: https://doc.rust-lang.org/reference/type-layout.html#primitive-representations -struct ReprAttribute(syn::Ident); - -impl ReprAttribute { - /// Parses a [`ReprAttribute`] from the provided [`syn::Attribute`]s. - /// - /// If there is no [`ReprAttribute`], then parses a [default `isize` discriminant][0]. - /// - /// [0]: https://doc.rust-lang.org/reference/items/enumerations.html#discriminants - fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result { - attrs - .as_ref() - .iter() - .filter(|attr| attr.path().is_ident("repr")) - .try_fold(None, |mut repr, attr| { - attr.parse_nested_meta(|meta| { - if let Some(ident) = meta.path.get_ident() { - if matches!( - ident.to_string().as_str(), - "u8" | "u16" - | "u32" - | "u64" - | "u128" - | "usize" - | "i8" - | "i16" - | "i32" - | "i64" - | "i128" - | "isize" - ) { - repr = Some(ident.clone()); - return Ok(()); - } - } - // Ignore all other attributes that could have a body, e.g. `align`. - _ = meta.input.parse::(); - Ok(()) - }) - .map(|_| repr) - }) - .map(|repr| { - // Default discriminant is interpreted as `isize`: - // https://doc.rust-lang.org/reference/items/enumerations.html#discriminants - repr.unwrap_or_else(|| syn::Ident::new("isize", Span::call_site())) - }) - .map(Self) - } -} +type ItemAttribute = attr::ReprConversion; /// Expansion of a macro for generating [`TryFrom`] implementation of an enum. struct Expansion { /// `#[repr(u/i*)]` of the enum. - repr: ReprAttribute, + repr: attr::ReprInt, /// [`ItemAttribute`] of the enum. attr: Option, @@ -146,12 +76,13 @@ impl ToTokens for Expansion { if self.attr.is_none() { return; } + let ident = &self.ident; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); - let repr = &self.repr.0; + let repr_ty = &self.repr.ty(); - let mut last_discriminant = quote! {0}; + let mut last_discriminant = quote! { 0 }; let mut inc = 0usize; let (consts, (discriminants, variants)): ( Vec, @@ -188,15 +119,15 @@ impl ToTokens for Expansion { quote! { #[automatically_derived] - impl #impl_generics ::core::convert::TryFrom<#repr #ty_generics> for #ident + impl #impl_generics ::core::convert::TryFrom<#repr_ty #ty_generics> for #ident #where_clause { - type Error = ::derive_more::TryFromReprError<#repr>; + type Error = ::derive_more::TryFromReprError<#repr_ty>; #[allow(non_upper_case_globals)] #[inline] - fn try_from(val: #repr) -> ::core::result::Result { - #( const #consts: #repr = #discriminants; )* + fn try_from(val: #repr_ty) -> ::core::result::Result { + #( const #consts: #repr_ty = #discriminants; )* match val { #(#consts => ::core::result::Result::Ok(#ident::#variants),)* _ => ::core::result::Result::Err(::derive_more::TryFromReprError::new(val)), diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 84fdafe4..8f9a627b 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -19,6 +19,7 @@ use syn::{ feature = "display", feature = "from", feature = "into", + feature = "try_from", ))] pub(crate) use self::either::Either; #[cfg(any(feature = "from", feature = "into"))] @@ -31,6 +32,7 @@ pub(crate) use self::generics_search::GenericsSearch; feature = "display", feature = "from", feature = "into", + feature = "try_from", ))] pub(crate) use self::spanning::Spanning; @@ -1481,6 +1483,7 @@ mod spanning { feature = "display", feature = "from", feature = "into", + feature = "try_from", ))] pub(crate) mod attr { use std::any::Any; @@ -1492,12 +1495,22 @@ pub(crate) mod attr { use super::{Either, Spanning}; + #[cfg(any( + feature = "as_ref", + feature = "debug", + feature = "display", + feature = "from", + feature = "into", + ))] pub(crate) use self::skip::Skip; #[cfg(any(feature = "as_ref", feature = "from"))] pub(crate) use self::{ - conversion::Conversion, empty::Empty, field_conversion::FieldConversion, - forward::Forward, types::Types, + conversion::Conversion, field_conversion::FieldConversion, forward::Forward, }; + #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] + pub(crate) use self::{empty::Empty, types::Types}; + #[cfg(feature = "try_from")] + pub(crate) use self::{repr_conversion::ReprConversion, repr_int::ReprInt}; /// [`Parse`]ing with additional state or metadata. pub(crate) trait Parser { @@ -1611,7 +1624,7 @@ pub(crate) mod attr { } } - #[cfg(any(feature = "as_ref", feature = "from"))] + #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] mod empty { use syn::{ parse::{Parse, ParseStream}, @@ -1696,6 +1709,106 @@ pub(crate) mod attr { impl ParseMultiple for Forward {} } + #[cfg(feature = "try_from")] + mod repr_int { + use proc_macro2::Span; + use syn::parse::{Parse, ParseStream}; + + use super::{ParseMultiple, Parser, Spanning}; + + /// Representation of a [`#[repr(u/i*)]` Rust attribute][0]. + /// + /// **NOTE**: Disregards any non-integer representation `#[repr]`s. + /// + /// ```rust,ignore + /// #[repr()] + /// ``` + /// + /// [0]: https://doc.rust-lang.org/reference/type-layout.html#primitive-representations + #[derive(Default)] + pub(crate) struct ReprInt(Option); + + impl ReprInt { + /// Returns [`syn::Ident`] of the primitive integer type behind this [`ReprInt`] + /// attribute. + /// + /// If there is no explicitly specified primitive integer type, then returns a + /// [default `isize` discriminant][0]. + /// + /// [0]: https://doc.rust-lang.org/reference/items/enumerations.html#discriminants + pub(crate) fn ty(&self) -> syn::Ident { + self.0 + .as_ref() + .cloned() + .unwrap_or_else(|| syn::Ident::new("isize", Span::call_site())) + } + } + + impl Parse for ReprInt { + fn parse(_: ParseStream<'_>) -> syn::Result { + unreachable!("call `attr::ParseMultiple::parse_attr_with()` instead") + } + } + + impl ParseMultiple for ReprInt { + fn parse_attr_with( + attr: &syn::Attribute, + _: &P, + ) -> syn::Result { + let mut repr = None; + attr.parse_nested_meta(|meta| { + if let Some(ident) = meta.path.get_ident() { + if matches!( + ident.to_string().as_str(), + "u8" | "u16" + | "u32" + | "u64" + | "u128" + | "usize" + | "i8" + | "i16" + | "i32" + | "i64" + | "i128" + | "isize" + ) { + repr = Some(ident.clone()); + return Ok(()); + } + } + // Ignore all other attributes that could have a body, e.g. `align`. + _ = meta.input.parse::(); + Ok(()) + })?; + Ok(Self(repr)) + } + + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + match (&prev.item.0, &new.item.0) { + (Some(_), None) | (None, None) => Ok(prev), + (None, Some(_)) => Ok(new), + (Some(_), Some(_)) => Err(syn::Error::new( + new.span, + format!( + "only single `#[{name}(u/i*)]` attribute is expected here", + ), + )), + } + } + } + } + + #[cfg(any( + feature = "as_ref", + feature = "debug", + feature = "display", + feature = "from", + feature = "into", + ))] mod skip { use syn::{ parse::{Parse, ParseStream}, @@ -1749,7 +1862,7 @@ pub(crate) mod attr { } } - #[cfg(any(feature = "as_ref", feature = "from"))] + #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] mod types { use syn::{ parse::{Parse, ParseStream}, @@ -1799,7 +1912,7 @@ pub(crate) mod attr { /// Untyped analogue of a [`Conversion`], recreating its type structure via [`Either`]. /// - /// Used to piggyback [`Parse`] and [`ParseMultiple`] impl to [`Either`]. + /// Used to piggyback [`Parse`] and [`ParseMultiple`] impls to [`Either`]. type Untyped = Either; /// Representation of an attribute, specifying which conversions should be generated: @@ -1866,7 +1979,7 @@ pub(crate) mod attr { /// Untyped analogue of a [`FieldConversion`], recreating its type structure via [`Either`]. /// - /// Used to piggyback [`Parse`] and [`ParseMultiple`] impl to [`Either`]. + /// Used to piggyback [`Parse`] and [`ParseMultiple`] impls to [`Either`]. type Untyped = Either>>; @@ -1955,6 +2068,77 @@ pub(crate) mod attr { } } } + + #[cfg(feature = "try_from")] + mod repr_conversion { + use syn::parse::{Parse, ParseStream}; + + use crate::utils::attr; + + use super::{ParseMultiple, Spanning}; + + /// Representation of an attribute, specifying which `repr`-conversions should be generated: + /// either direct into a discriminant, or for concrete specified types forwarding from a + /// discriminant. + /// + /// ```rust,ignore + /// #[(repr)] + /// #[(repr())] + /// ``` + pub(crate) enum ReprConversion { + Discriminant(attr::Empty), + Types(attr::Types), + } + + impl Parse for ReprConversion { + fn parse(input: ParseStream<'_>) -> syn::Result { + let prefix = syn::Ident::parse(input)?; + if prefix != "repr" { + return Err(syn::Error::new( + prefix.span(), + "expected `repr` argument here", + )); + } + if input.is_empty() { + Ok(Self::Discriminant(attr::Empty)) + } else { + let inner; + syn::parenthesized!(inner in input); + Ok(Self::Types(attr::Types::parse(&inner)?)) + } + } + } + + impl ParseMultiple for ReprConversion { + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + Ok(match (prev.item, new.item) { + (Self::Discriminant(_), Self::Discriminant(_)) => { + return Err(syn::Error::new( + new.span, + format!("only single `#[{name}(repr)]` attribute is allowed here"), + )) + }, + (Self::Types(p), Self::Types(n)) => { + attr::Types::merge_attrs( + Spanning::new(p, prev.span), + Spanning::new(n, new.span), + name, + )?.map(Self::Types) + }, + _ => return Err(syn::Error::new( + new.span, + format!( + "only single kind of `#[{name}(repr(...))]` attribute is allowed here", + ), + )) + }) + } + } + } } #[cfg(any(feature = "from", feature = "into"))]