deluxe-macros-0.5.0/.cargo_vcs_info.json0000644000000001440000000000100136050ustar { "git": { "sha1": "550c8ea2831f7104bf3c457db1ea9513c2fa1c31" }, "path_in_vcs": "macros" }deluxe-macros-0.5.0/COPYING000064400000000000000000000020661046102023000134350ustar 00000000000000MIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. deluxe-macros-0.5.0/Cargo.toml0000644000000023010000000000100116000ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "deluxe-macros" version = "0.5.0" description = "Derive macros for Deluxe procedural macro attribute parser" homepage = "https://github.com/jf2048/deluxe" documentation = "https://docs.rs/deluxe-macros" license = "MIT" repository = "https://github.com/jf2048/deluxe.git" [lib] path = "lib.rs" proc-macro = true [dependencies.deluxe-core] version = "0.5.0" default-features = false [dependencies.heck] version = "0.4.0" [dependencies.if_chain] version = "1.0.2" [dependencies.proc-macro-crate] version = "1.1.3" [dependencies.proc-macro2] version = "1.0.38" [dependencies.quote] version = "1.0.25" [dependencies.syn] version = "2.0.10" features = [ "parsing", "proc-macro", ] default-features = false deluxe-macros-0.5.0/Cargo.toml.orig000064400000000000000000000011741046102023000152700ustar 00000000000000[package] name = "deluxe-macros" version = "0.5.0" edition = "2021" description = "Derive macros for Deluxe procedural macro attribute parser" license = "MIT" documentation = "https://docs.rs/deluxe-macros" homepage = "https://github.com/jf2048/deluxe" repository = "https://github.com/jf2048/deluxe.git" [lib] path = "lib.rs" proc-macro = true [dependencies] heck = "0.4.0" if_chain = "1.0.2" proc-macro2 = "1.0.38" proc-macro-crate = "1.1.3" quote = "1.0.25" syn = { version = "2.0.10", features = ["parsing", "proc-macro"], default-features = false } deluxe-core = { path = "../core", version = "0.5.0", default-features = false } deluxe-macros-0.5.0/lib.rs000064400000000000000000000456111046102023000135210ustar 00000000000000//! # Deluxe Macros //! //! Procedural derive macros for [deluxe](https://docs.rs/deluxe). See the documentation of that //! crate for an overview. #![deny(missing_docs)] #![deny(unsafe_code)] #[macro_use] mod util; mod parse_attributes; mod parse_meta_item; mod types; use util::*; use deluxe_core::Errors; use proc_macro::TokenStream; /// Generates [`ExtractAttributes`](deluxe_core::ExtractAttributes) for a struct or enum. /// /// This macro is identical to [`ParseAttributes`], except for the following differences: /// - The generated [`extract_attributes`](deluxe_core::ExtractAttributes::extract_attributes) /// function will remove any attributes matching the paths listed in /// [`#[deluxe(attributes(...))]`](ParseAttributes#deluxeattributes). /// /// - Reference types for [`#[deluxe(container)]`](ParseAttributes#deluxecontainer) fields will not /// work. The container type must be an owned type or an [`Option`], and the container will /// always be [`clone`](Clone::clone)d into the field. The cloning will happen after the /// attributes have been extracted. Using the `lifetime` parameter for custom container types /// will also not work. /// /// See the [`ParseAttributes`] and [`ParseMetaItem`] documentation for all other details. #[proc_macro_derive(ExtractAttributes, attributes(deluxe))] pub fn derive_extract_attributes(item: TokenStream) -> TokenStream { let errors = Errors::new(); let mut tokens = util::parse::(item, &errors) .map(|input| { parse_attributes::impl_parse_attributes(input, &errors, parse_attributes::Mode::Extract) }) .unwrap_or_default(); tokens.extend(errors.into_compile_errors()); tokens.into() } /// Generates [`ParseAttributes`](deluxe_core::ParseAttributes) for a struct or enum. /// /// Supported attributes are the same as in [`ParseMetaItem`]. Additionally, some extra attributes /// are supported. /// /// ### Extra Container Attributes /// /// The following extra attributes are supported on structs and enums: /// /// - ##### `#[deluxe(attributes(...))]` /// /// Provides a list of paths to match attribute names against. These paths will be checked inside /// the [`ParseAttributes::path_matches`](deluxe_core::ParseAttributes::path_matches) function to /// see if a path matches. This attribute can be specified multiple times to append more paths to /// the list of matching paths. /// /// Omitting this attribute will cause *all* paths to match. This can be useful when doing your /// own filtering on an attribute list before parsing. /// /// ### Extra Field Attributes /// /// The following extra attributes are supported on struct fields and enum fields: /// /// - ##### `#[deluxe(container)]` /// /// Specifies that this field should store the current `obj` being parsed by /// [`parse_attributes`](deluxe_core::ParseAttributes::parse_attributes). The field can be a /// value type, an [`Option`], or a reference type. If the field is not a reference type, the /// container will be [`clone`](Clone::clone)d into the field. /// /// If the type of the container contains a lifetime, Deluxe will try to use it for a lifetime /// bound for [`ParseAttributes`](deluxe_core::ParseAttributes). If the container type is a /// reference, it will try to infer it from the lietime of the reference. Otherwise, it will try /// to take the first lifetime parameter used by the type if one is present. If no lifetimes can /// be inferred, a new lifetime parameter will be generated. See /// [`container(lifetime = ...)`](#deluxecontainerlifetime--t-ty--pathmytype) for instructions on /// how to specify a lifetime parameter manually. /// /// If the struct/enum also derives [`ParseMetaItem`], then /// [`#[deluxe(default)]`](ParseMetaItem#deluxedefault-1) is implied on any container fields. In /// that case, a common pattern is to use an [`Option`] as the container field. It will be set to /// [`None`] when calling [`parse_meta_item`](deluxe_core::ParseMetaItem::parse_meta_item), but /// will be [`Some`] when calling /// [`parse_attributes`](deluxe_core::ParseAttributes::parse_attributes). /// /// If used within an enum, only the first container field will supply the trait bounds. Any /// other container fields in other variants must have a compatible type and lifetime. /// /// Fields with this attribute can safely be added to a struct or variant using /// [`#[deluxe(transparent)]`](ParseMetaItem#deluxetransparent). Container fields do not count as /// a parseable field, as they are never parsed from tokens. /// /// - ##### `#[deluxe(container(lifetime = 't, ty = path::MyType)]` /// /// Specifies that this field should store the current `obj` being parsed by /// [`parse_attributes`](deluxe_core::ParseAttributes::parse_attributes), with a custom lifetime /// and type bound used for the [`ParseAttributes`](deluxe_core::ParseAttributes) trait /// implementation. /// /// Normally the `lifetime` and `ty` are not needed when using /// [`#[deluxe(container)]`](#deluxecontainer) because the macro can infer the lifetime and type /// from the field itself. If Deluxe is unable to infer them, these attributes can be supplied to /// manually provide the lifetime and type. `lifetime` can be omitted if the container is an /// owning type. /// /// This attribute is implemented by calling methods on the /// [`ContainerFrom`](deluxe_core::ContainerFrom) trait. Other container types besides references /// and [`Option`] will also need to provide an implementation for that trait. #[proc_macro_derive(ParseAttributes, attributes(deluxe))] pub fn derive_parse_attributes(item: TokenStream) -> TokenStream { let errors = Errors::new(); let mut tokens = util::parse::(item, &errors) .map(|input| { parse_attributes::impl_parse_attributes(input, &errors, parse_attributes::Mode::Parse) }) .unwrap_or_default(); tokens.extend(errors.into_compile_errors()); tokens.into() } /// Generates [`ParseMetaItem`](deluxe_core::ParseMetaItem) for a struct or enum. /// /// ### Container Attributes /// /// The following attributes are supported on structs and enums: /// /// - ##### `#[deluxe(default)]` /// /// Initializes the container with [`Default::default`] before parsing. /// /// - ##### `#[deluxe(default = expr)]` /// /// Initializes the container with the value of `expr` before parsing. The expression will /// be evaluated every time it is needed to construct the field, and must evaluate to a value of /// the same type as the field. /// /// - ##### `#[deluxe(transparent)]` /// /// Parses a struct with one field as if it were the field. Can only be used on a struct with a /// single parseable field. Analogous to `#[repr(transparent)]`. The struct can still contain /// fields that are [`skip`](#deluxeskip-1), as those will be ignored by `transparent`. /// /// - ##### `#[deluxe(transparent(flatten_named)]` /// /// `#[deluxe(transparent(flatten_unnamed)]` /// /// `#[deluxe(transparent(flatten_unnamed, append)]` /// /// `#[deluxe(transparent(rest)]` /// /// Parses a struct with one field as if it were the field, additionally implementing the traits /// required to use `flatten`, `rest`, or `append`. Can only be used on a struct with a /// single parseable field. /// /// Currently, it is required to provide these additional attributes to generate the trait /// definitions to use [`flatten`](#deluxeflatten-1), [`append`](#deluxeappend) or /// [`rest`](#deluxerest) on this type. /// /// - ##### `#[deluxe(and_then = expr)]` /// /// Executes an additional function ater parsing to perform additional transformations or /// validation on the input. /// /// This attribute is a simple wrapper around /// [`Result::and_then`](deluxe_core::Result::and_then). The function returned by `expr` must /// conform to the signature fn(T) -> [deluxe::Result](deluxe_core::Result)<T> /// where `T` is the type of the struct/enum being parsed. Returning /// [`Err`](deluxe_core::Result::Err) will cause the entire parse to fail. /// /// This attribute can be specified multiple times. When multiple `and_then` attributes are /// present, Deluxe will chain each function in the order the attributes were specified. /// /// ##### Example /// /// ```ignore /// #[derive(deluxe::ParseMetaItem)] /// #[deluxe(and_then = Self::validate)] /// struct Data(i32); /// impl Data { /// fn validate(self) -> deluxe::Result { /// // ... perform some checks here ... /// Ok(self) /// } /// } /// ``` /// /// - ##### `#[deluxe(allow_unknown_fields)]` /// /// Ignore any tokens and do not generate an error when an unknown field is encountered. /// /// - ##### `#[deluxe(crate = path)]` /// /// Specifies `path` as a custom path to the `deluxe` crate. Useful if `proc_macro_crate` is /// unable to find the `deluxe` crate, for instance if the crate is only re-exported inside /// another dependency. /// /// ### Variant Attributes /// /// The following attributes are supported on variants: /// /// - ##### `#[deluxe(rename = ident)]` /// /// Parse the variant with the given `ident` instead of its Rust name. /// /// - ##### `#[deluxe(alias = ident)]` /// /// Parse the variant with the given `ident`, or its Rust name. Can be repeated multiple times to /// provide additional aliases. /// /// - ##### `#[deluxe(transparent)]` /// /// Parses a variant with one field as if it were the field. Can only be used on a variant with a /// single parseable field. Analogous to `#[repr(transparent)]`. The variant can still contain /// fields that are [`skip`](#deluxeskip-1), as those will be ignored by `transparent`. /// /// - ##### `#[deluxe(flatten)]` /// /// Flattens the variant so that its unique fields are used as the key for this variant instead /// of its name. Can be used on multiple variants as long as they each have a unique set of /// parseable fields that can be used to identify the variant. Fields with /// [`flatten`](#deluxeflatten-1), [`append`](#deluxeappend) or [`rest`](#deluxerest) are not /// counted as unique fields as their field names are ignored. /// /// A single variant with no parseable fields can also be flattened. In that case, that variant /// will always be parsed as the default variant. Setting a default variant in this way is /// mutually exclusive with using [`#[deluxe(default)]`](#deluxedefault) on the enum. /// /// - ##### `#[deluxe(skip)]` /// /// Skips this variant from parsing entirely. /// /// - ##### `#[deluxe(allow_unknown_fields)]` /// /// Ignore any tokens and do not generate an error when an unknown field is encountered in this /// variant. /// /// ### Field Attributes /// /// The following attributes are supported on struct fields and enum fields: /// /// - ##### `#[deluxe(rename = ident)]` /// /// Parse the field with the given `ident` instead of its Rust name. /// /// - ##### `#[deluxe(alias = ident)]` /// /// Parse the field with the given `ident`, or its Rust name. Can be repeated multiple times to /// provide additional aliases. /// /// - ##### `#[deluxe(default)]` /// /// Initializes the field with the value of [`Default::default`] if the field is omitted. /// /// It is not necessary to use this on fields of type [`Option`] or [`Flag`](deluxe_core::Flag), /// or any other type that has a top-level [`#[deluxe(default)]`](#deluxedefault) on the type /// itself. /// /// - ##### `#[deluxe(default = expr)]` /// /// Initializes the field with the value of `expr` if the field is omitted. The expression will /// be evaluated every time it is needed to construct the field, and must evaluate to a value of /// the same type as the field. /// /// - ##### `#[deluxe(flatten)]` /// /// Flattens the field so that its fields are parsed inline as part of the current struct or enum /// variant. /// /// When the container uses named fields, only enums or other structs with named fields can be /// flattened. The fields from the flattened field can be freely interspersed with the fields /// from the containing struct or variant. This has the effect of making it so the order of /// flattened fields does not matter when using named fields. /// /// When the container uses unnamed fields, only unnamed structs, tuples, and collections/arrays /// can be flattened. The order of flattened unnamed fields is important. The fields of the /// flattened structure will be consumed starting from the position of the field in the /// containing tuple. Flattening a collection into a tuple struct/variant without a finite size /// will consume all fields from that position until the end. /// /// This attribute is implemented by either calling /// [`ParseMetaFlatUnnamed::parse_meta_flat_unnamed`](deluxe_core::ParseMetaFlatUnnamed::parse_meta_flat_unnamed) /// or /// [`ParseMetaFlatNamed::parse_meta_flat_named`](deluxe_core::ParseMetaFlatNamed::parse_meta_flat_named) /// depending on the type of the containing structure. The appropriate trait will be /// automatically implemented when deriving [`ParseMetaItem`], but some implementations are /// provided for common collection types. Custom container types can support flattening by /// providing implementations of those traits. /// /// - ##### `#[deluxe(flatten(prefix = path)])` /// /// Flattens the field so that its fields are parsed inline as part of the current struct or enum /// variant, only accepting fields that are prefixed with `path`. This can be used if the /// flattened structure contains field names that conflict with the fields in the containing /// structure. /// /// For all other details on this attribute, refer to [`flatten`](#deluxeflatten-1). /// /// - ##### `#[deluxe(append)]` /// /// Allows duplicates of this field. Additional fields parsed with the same name will be appended /// on to the previous value. This attribute is only allowed on named fields. /// /// This attribute is implemented by calling /// [`ParseMetaAppend::parse_meta_append`](deluxe_core::ParseMetaAppend::parse_meta_append). Some /// implementations are provided for common collection types. Custom container types can support /// appending by providing an implementation of that trait. /// /// - ##### `#[deluxe(rest)]` /// /// Inserts all unknown fields into this field. Typically, this field will be a map type with /// [`syn::Path`] as the key. This attribute is only allowed on named fields. /// /// This attribute is implemented by calling /// [`ParseMetaRest::parse_meta_rest`](deluxe_core::ParseMetaRest::parse_meta_rest). Some /// implementations are provided for common collection types. Custom map types can be allowed as /// a rest field by providing an implementation of that trait. /// /// - ##### `#[deluxe(map = expr)]` /// /// `#[deluxe(and_then = expr)]` /// /// Executes additional functions ater parsing to perform additional transformations or /// validation on the input. /// /// These attributes are simple wrappers around [`Result::map`](deluxe_core::Result::map) and /// [`Result::and_then`](deluxe_core::Result::and_then). These attributes can be specified /// multiple times. When multiple are present, Deluxe will chain each function in the order the /// attributes were specified. /// /// For `map`, the function returned by `expr` must conform to the signature `fn(T) -> U`. For /// `and_then`, the function returned by `expr` must conform to the signature fn(T) -> /// [deluxe::Result](deluxe_core::Result)<U>. Returning /// [`Err`](deluxe_core::Result::Err) will cause the entire parse to fail. Arbitrary types can be /// used for `T` and `U` as long as the following constraints hold: /// /// - The first function must have a fully specified type for `T`, which will have its /// [`ParseMetaItem`](deluxe_core::ParseMetaItem) implementation used. /// - The `U from any function in the chain matches the `T` for the following function. /// - The last function must have a type for `U` that matches the type of the field. /// /// ##### Example /// /// ```ignore /// #[derive(deluxe::ParseMetaItem)] /// struct Data { /// // parses as an Ident but stored as a string /// #[deluxe(map = |i: syn::Ident| i.to_string())] /// ident_string: String, /// // converts an Ident to a string and does a validation /// #[deluxe(and_then = Self::check_ident)] /// valid_ident_string: String, /// } /// /// impl Data { /// fn check_ident(i: syn::Ident) -> deluxe::Result { /// let s = i.to_string(); /// if s == "invalid" { /// Err(syn::Error::new(i.span(), "`invalid` not allowed")) /// } else { /// Ok(s) /// } /// } /// } /// ``` /// /// - ##### `#[deluxe(with = module)]` /// /// When parsing, call functions from the path `module` instead of attempting to call /// [`ParseMetaItem`](deluxe_core::ParseMetaItem) functions. The path can be a module path or a /// path to a type containing associated functions. /// /// The functions will be called as `module::parse_meta_item`, `module::parse_meta_item_inline`, /// `module::parse_meta_item_flag`, `module::parse_meta_item_named`, and /// `module::missing_meta_item`. All five functions must be implemented, even if just to return /// an error. The signatures of these functions should match the equivalent functions in /// [`ParseMetaItem`](crate::ParseMetaItem), although they can be generic over the return type. /// Fields using this attribute are not required to implement /// [`ParseMetaItem`](crate::ParseMetaItem). /// /// `parse_meta_item_inline` implementations can call /// [`parse_first`](deluxe_core::parse_helpers::parse_first) to simply delegate the impementation /// to `parse_meta_item`. `parse_meta_item_flag` implementations can call /// [`flag_disallowed_error`](deluxe_core::parse_helpers::flag_disallowed_error) for a standard /// error if flags are not supported by the target type. `parse_meta_item_named` implementations /// can call [`parse_named_meta_item_with!`](deluxe_core::parse_named_meta_item_with) using /// `self` as the last parameter for the standard behavior. /// /// Some common parsers are available in the [`with`](deluxe_core::with) module. /// /// - ##### `#[deluxe(skip)]` /// /// Skips this field from parsing entirely. The field still must receive a default value through /// a `default` attribute either on the struct or the field, so the parse function can still /// construct the object. If not used in a struct with [`default`](#deluxedefault), then this /// implies [`default`](#deluxedefault-1) on the field if it is omitted. #[proc_macro_derive(ParseMetaItem, attributes(deluxe))] pub fn derive_parse_meta_item(item: TokenStream) -> TokenStream { let errors = Errors::new(); let mut tokens = util::parse::(item, &errors) .map(|input| parse_meta_item::impl_parse_meta_item(input, &errors)) .unwrap_or_default(); tokens.extend(errors.into_compile_errors()); tokens.into() } deluxe-macros-0.5.0/parse_attributes.rs000064400000000000000000000301241046102023000163240ustar 00000000000000use std::borrow::Cow; use deluxe_core::Errors; use proc_macro2::{Span, TokenStream}; use crate::types::*; #[derive(PartialEq, Eq, Clone, Copy)] pub enum Mode { Parse, Extract, } impl Mode { fn into_token_mode(self) -> TokenMode { match self { Self::Parse => TokenMode::ParseAttributes, Self::Extract => TokenMode::ExtractAttributes, } } } struct AttrImpl<'i> { pub parse: TokenStream, pub crate_path: syn::Path, pub priv_path: syn::Path, pub attributes: Vec, pub container_field: Option<&'i syn::Field>, pub container_lifetime: Option, pub container_ty: Option, } #[inline] fn impl_for_struct<'i>( input: &'i syn::DeriveInput, struct_: &syn::DataStruct, mode: Mode, errors: &Errors, ) -> Option> { let struct_attr = errors.push_result(>::parse_attributes(input)); let crate_path = super::get_crate_path(struct_attr.as_ref().map(|s| s.crate_.clone()), errors)?; let crate_ = &crate_path; let priv_path: syn::Path = syn::parse_quote! { #crate_::____private }; let priv_ = &priv_path; let parse = struct_attr .as_ref() .map(|s| { let ItemDef { inline, .. } = s.to_parsing_tokens( input, crate_, mode.into_token_mode(), &parse_quote_mixed! { inline(input) }, &parse_quote_mixed! { allowed }, ); let pre = match &struct_.fields { syn::Fields::Named(_) => { let field_names = struct_attr .as_ref() .map(|s| s.to_field_names_tokens(crate_, priv_)) .unwrap_or_else(|| quote_mixed! { &[] }); let accepts_all = struct_attr .as_ref() .and_then(|s| s.to_accepts_all_tokens(crate_)) .unwrap_or_else(|| quote_mixed! { false }); quote_mixed! { let allowed = #field_names; let validate = !(#accepts_all); let prefix = ""; } } syn::Fields::Unnamed(_) => { quote_mixed! { let mut index = 0usize; } } _ => quote::quote! {}, }; quote_mixed! { #pre #inline } }) .unwrap_or_else(|| { quote_mixed! { #priv_::unreachable!() } }); let (container_field, container_lifetime, container_ty) = struct_attr .as_ref() .map(|s| s.fields.as_slice()) .unwrap_or_default() .iter() .find_map(|f| { f.container .as_ref() .map(|c| (Some(f.field), c.lifetime.clone(), c.ty.clone())) }) .unwrap_or_default(); Some(AttrImpl { parse, crate_path, priv_path, attributes: struct_attr.map(|s| s.attributes).unwrap_or_default(), container_field, container_lifetime, container_ty, }) } #[inline] fn impl_for_enum<'i>( input: &'i syn::DeriveInput, mode: Mode, errors: &Errors, ) -> Option> { let enum_attr = errors.push_result( >::parse_attributes(input), ); let crate_path = super::get_crate_path(enum_attr.as_ref().map(|e| e.crate_.clone()), errors)?; let crate_ = &crate_path; let priv_path: syn::Path = syn::parse_quote! { #crate_::____private }; let priv_ = &priv_path; let parse = enum_attr .as_ref() .map(|e| { let parse = e.to_inline_parsing_tokens(crate_, mode.into_token_mode()); let field_names = e.to_field_names_tokens(crate_, priv_); let accepts_all = e .to_accepts_all_tokens(crate_) .unwrap_or_else(|| quote_mixed! { false }); quote_mixed! { let allowed = #field_names; let validate = !(#accepts_all); let prefix = ""; #parse } }) .unwrap_or_else(|| { quote_mixed! { #priv_::unreachable!() } }); let (container_field, container_lifetime, container_ty) = enum_attr .as_ref() .map(|e| e.variants.as_slice()) .unwrap_or_default() .iter() .find_map(|v| { v.fields.iter().find_map(|f| { f.container .as_ref() .map(|c| (Some(f.field), c.lifetime.clone(), c.ty.clone())) }) }) .unwrap_or_default(); Some(AttrImpl { parse, crate_path, priv_path, attributes: enum_attr.map(|s| s.attributes).unwrap_or_default(), container_field, container_lifetime, container_ty, }) } pub fn impl_parse_attributes(input: syn::DeriveInput, errors: &Errors, mode: Mode) -> TokenStream { let attr = match &input.data { syn::Data::Struct(struct_) => impl_for_struct(&input, struct_, mode, errors), syn::Data::Enum(_) => impl_for_enum(&input, mode, errors), syn::Data::Union(union_) => { errors.push_spanned( union_.union_token, match mode { Mode::Parse => "union not supported with derive(ParseAttributes)", Mode::Extract => "union not supported with derive(ExtractAttributes)", }, ); return Default::default(); } }; let AttrImpl { parse, crate_path: crate_, priv_path: priv_, attributes, container_field, mut container_lifetime, container_ty, } = match attr { Some(a) => a, None => return Default::default(), }; let ident = &input.ident; let mut generics = input.generics.clone(); let mut container_is_generic = false; let mut container_is_ref = false; let mut container_ty = container_ty.map(Cow::Owned); if let Some(container_field) = container_field { let mut ty = &container_field.ty; // try to guess some things about the container type. // first infer if this is an option, and if so, use its inner type if_chain::if_chain! { if let syn::Type::Path(path) = ty; if path.qself.is_none(); if let Some(last) = path.path.segments.last(); if last.ident == "Option"; if let syn::PathArguments::AngleBracketed(args) = &last.arguments; if args.args.len() == 1; if let syn::GenericArgument::Type(t) = &args.args[0]; then { ty = t; } } // use inner type and lifetime from reference if let syn::Type::Reference(ref_) = ty { if container_lifetime.is_none() { container_lifetime = ref_.lifetime.clone(); } container_is_ref = true; ty = &*ref_.elem; } // if we still need a lifetime, and inner type has a lifetime, use that if_chain::if_chain! { if container_lifetime.is_none(); if let syn::Type::Path(path) = ty; if let Some(last) = path.path.segments.last(); if let syn::PathArguments::AngleBracketed(args) = &last.arguments; if let Some(syn::GenericArgument::Lifetime(lt)) = args.args.first(); then { container_lifetime = Some(lt.clone()); } } // see if the type matches a generic param if container_ty.is_none() { container_ty = Some(Cow::Borrowed(ty)); if_chain::if_chain! { if let syn::Type::Path(path) = ty; if path.qself.is_none(); if let Some(ident) = path.path.get_ident(); if generics.type_params().any(|p| p.ident == *ident); then { container_is_generic = true; } } } } // if there was no container field, make it generic and add our own type param let container_ty = container_ty.unwrap_or_else(|| { container_is_generic = true; let mut ty = String::from("T"); // ensure the `T` is a unique ident while generics.type_params().any(|p| p.ident == ty) { ty.push('_'); } let ty = syn::Ident::new(&ty, Span::mixed_site()); generics.params.insert(0, syn::parse_quote! { #ty }); Cow::Owned(parse_quote_mixed! { #ty }) }); // value types must be copied into the structure if container_field.is_some() && !container_is_ref { let where_clause = generics.make_where_clause(); where_clause.predicates.push(syn::parse_quote! { #container_ty: #priv_::Clone }); } // must be able to access attributes on the generic type if container_is_generic { let where_clause = generics.make_where_clause(); where_clause.predicates.push(syn::parse_quote! { #container_ty: #crate_::HasAttributes }); } // ParseAttributes needs a lifetime param since the attributes are always referenced if mode == Mode::Parse && container_lifetime.is_none() { let mut lt = String::from("t"); while generics.lifetimes().any(|l| l.lifetime.ident == lt) { lt.push('_'); } lt.insert(0, '\''); container_lifetime = Some(syn::Lifetime::new(<, Span::mixed_site())); generics .params .insert(0, syn::parse_quote! { #container_lifetime }); } let (_, type_generics, _) = input.generics.split_for_impl(); let (impl_generics, _, where_clause) = generics.split_for_impl(); let matches = if attributes.is_empty() { quote_mixed! { true } } else { let matches = attributes.iter().map(|p| { let segs = p.segments.iter().map(|s| s.ident.to_string()); quote_mixed! { &[#(#segs),*] } }); quote_mixed! { #(#priv_::parse_helpers::path_matches(path, #matches))||* } }; let sig = match mode { Mode::Parse => quote_mixed! { fn parse_attributes(obj: &#container_lifetime #container_ty) -> #crate_::Result }, Mode::Extract => quote_mixed! { fn extract_attributes(obj: &mut #container_ty) -> #crate_::Result }, }; let trait_ = match mode { Mode::Parse => quote_mixed! { #crate_::ParseAttributes<#container_lifetime, #container_ty> }, Mode::Extract => quote_mixed! { #crate_::ExtractAttributes<#container_ty> }, }; let get_tokens = match mode { Mode::Parse => quote_mixed! { ref_tokens }, Mode::Extract => quote_mixed! { take_tokens }, }; let tokens_try = match mode { Mode::Parse => None, Mode::Extract => Some(quote_mixed! { ? }), }; let path_name_unwrap = attributes.first().map(|path| { let path = deluxe_core::parse_helpers::key_to_string(path); quote_mixed! { .or(#priv_::Option::Some(#path)) } }); quote_mixed! { impl #impl_generics #trait_ for #ident #type_generics #where_clause { #[inline] fn path_matches(path: &#priv_::Path) -> #priv_::bool { #matches } #sig { #priv_::parse_helpers::parse_struct_attr_tokens( #priv_::parse_helpers::#get_tokens::(obj) #tokens_try, |inputs, spans| { let span = #priv_::parse_helpers::first_span(spans); let path_name = #priv_::parse_helpers::first_path_name(spans) #path_name_unwrap; let _mode = #crate_::ParseMode::Named(span); #parse } ) } } } } deluxe-macros-0.5.0/parse_meta_item.rs000064400000000000000000000347531046102023000161160ustar 00000000000000use deluxe_core::Errors; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::spanned::Spanned; use crate::types::*; struct MetaDef { pub parse: TokenStream, pub inline: Option, pub flag: Option, pub default: Option, pub extra: Option, pub crate_path: syn::Path, pub priv_path: syn::Path, } #[inline] fn impl_for_struct( input: &syn::DeriveInput, struct_: &syn::DataStruct, errors: &Errors, ) -> Option { let mut struct_attr = errors.push_result(>::parse_attributes(input)); let crate_path = super::get_crate_path(struct_attr.as_ref().map(|s| s.crate_.clone()), errors)?; let crate_ = &crate_path; let priv_path: syn::Path = syn::parse_quote! { #crate_::____private }; let priv_ = &priv_path; let any_flat = struct_attr .as_ref() .map(|s| s.fields.iter().any(|f| f.is_flat())) .unwrap_or(false); let (parse, parse_flat, inline, flag, field_names, mut extra) = struct_attr .as_mut() .map(|struct_attr| { for f in &mut struct_attr.fields { if let Some(span) = f.container.as_ref().and_then(|c| c.value.then_some(c.span)) { if f.default.is_none() { f.default = Some(FieldDefault::Default(span)); } } } let fields = struct_attr.fields.as_slice(); let make_inline_expr = |inputs_expr: TokenStream| -> TokenStream { match &struct_.fields { syn::Fields::Named(_) => quote_mixed! { ::parse_meta_flat_named( #inputs_expr, _mode, "", !::ACCEPTS_ALL, ) }, syn::Fields::Unnamed(_) => quote_mixed! { ::parse_meta_flat_unnamed(#inputs_expr, _mode, 0) }, syn::Fields::Unit => quote_mixed! { ::parse_meta_item_inline( #inputs_expr, #crate_::ParseMode::Unnamed, ) }, } }; let ItemDef { parse, inline, flag, extra_traits, } = struct_attr.to_parsing_tokens( input, crate_, TokenMode::ParseMetaItem, &make_inline_expr(quote_mixed! { &[input] }), &parse_quote_mixed! { ::field_names() }, ); let (parse_flat, inline) = if struct_attr.is_transparent() || matches!(&struct_.fields, syn::Fields::Unit) { (None, inline) } else { (inline, Some(make_inline_expr(quote_mixed! { inputs }).into_token_stream())) }; let field_names = match &struct_.fields { syn::Fields::Named(_) => Some(struct_attr.to_field_names_tokens(crate_, priv_)), syn::Fields::Unnamed(_) => { let field_count = fields.iter().filter(|f| !f.is_flat()).count(); let extra_counts = fields.iter().filter_map(|f| { if !f.is_flat() { return None; } let ty = &f.field.ty; Some(quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaFlatUnnamed>::field_count().unwrap_or(0) }) }); Some(quote_mixed! { #priv_::Option::Some(#field_count #( + #extra_counts)*) }) } _ => None, }; (parse, parse_flat, inline, flag, field_names, extra_traits) }) .unwrap_or_else(|| { let fail = quote_mixed! { #priv_::unreachable!() }; ( fail.clone(), Some(fail.clone()), Some(fail.clone()), Some(fail.clone()), Some(fail), None, ) }); match (&struct_.fields, parse_flat) { (syn::Fields::Named(_), Some(parse_flat)) => { let struct_ident = &input.ident; let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); let accepts_all = struct_attr .as_ref() .and_then(|s| s.to_accepts_all_tokens(crate_)) .into_iter(); extra.get_or_insert_with(TokenStream::new).extend(quote_mixed! { impl #impl_generics #crate_::ParseMetaFlatNamed for #struct_ident #type_generics #where_clause { #(const ACCEPTS_ALL: #priv_::bool = #accepts_all;)* #[inline] fn field_names() -> &'static [&'static #priv_::str] { #field_names } #[inline] fn parse_meta_flat_named<'____s, ____S>( inputs: &[____S], _mode: #crate_::ParseMode, prefix: &#priv_::str, validate: #priv_::bool, ) -> #crate_::Result where ____S: #priv_::Borrow<#priv_::ParseBuffer<'____s>>, { let span = _mode.to_full_span(inputs); #parse_flat } } impl #impl_generics #crate_::ParseMetaFlatUnnamed for #struct_ident #type_generics #where_clause { #[inline] fn field_count() -> #priv_::Option<#priv_::usize> { #priv_::Option::None } #[inline] fn parse_meta_flat_unnamed<'____s, ____S>( inputs: &[____S], mode: #crate_::ParseMode, _index: #priv_::usize, ) -> #crate_::Result where ____S: #priv_::Borrow<#priv_::ParseBuffer<'____s>>, { ::parse_meta_flat_named(inputs, mode, "", true) } } }); } (syn::Fields::Unnamed(_), Some(parse_flat)) => { let struct_ident = &input.ident; let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); let index_mut = any_flat.then(|| quote! { mut }); extra.get_or_insert_with(TokenStream::new).extend(quote_mixed! { impl #impl_generics #crate_::ParseMetaFlatUnnamed for #struct_ident #type_generics #where_clause { #[inline] fn field_count() -> #priv_::Option<#priv_::usize> { #field_names } #[inline] fn parse_meta_flat_unnamed<'____s, ____S>( inputs: &[____S], _mode: #crate_::ParseMode, #index_mut index: #priv_::usize, ) -> #crate_::Result where ____S: #priv_::Borrow<#priv_::ParseBuffer<'____s>>, { #parse_flat } } }); } _ => {} } let default = struct_attr.and_then(|s| s.default).map(|d| { d.to_expr(Some(&syn::parse_quote_spanned! { d.span() => Self }), priv_) .into_owned() }); Some(MetaDef { parse, inline, flag, default, extra, crate_path, priv_path, }) } #[inline] fn impl_for_enum(input: &syn::DeriveInput, errors: &Errors) -> Option { let enum_attr = errors.push_result( >::parse_attributes(input), ); let crate_path = super::get_crate_path(enum_attr.as_ref().map(|e| e.crate_.clone()), errors)?; let crate_ = &crate_path; let priv_path: syn::Path = syn::parse_quote! { #crate_::____private }; let priv_ = &priv_path; let enum_ident = &input.ident; let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); let (parse, field_names) = enum_attr .as_ref() .map(|e| { for v in &e.variants { for f in &v.fields { if let Some(container) = f.container.as_ref() { if f.is_container() && f.default.is_none() { errors.push( container.span(), "derive(ParseMetaItem) requires container field to have `default`", ); } } } } let parse = e.to_inline_parsing_tokens(crate_, TokenMode::ParseMetaItem); let field_names = e.to_field_names_tokens(crate_, priv_); ( quote_mixed! { let allowed = ::field_names(); #parse }, field_names, ) }) .unwrap_or_else(|| { let fail = quote_mixed! { #priv_::unreachable!() }; (fail.clone(), fail) }); Some(MetaDef { parse: quote_mixed! { <#priv_::parse_helpers::Brace as #priv_::parse_helpers::ParseDelimited>::parse_delimited_meta_item( input, _mode.to_named(input), ) }, inline: Some(quote_mixed! { ::parse_meta_flat_named( inputs, _mode, "", !::ACCEPTS_ALL, ) }), flag: Some(quote_mixed! { #priv_::parse_helpers::parse_empty_meta_item(span, #crate_::ParseMode::Named(span)) }), default: enum_attr.and_then(|s| s.default).map(|d| { d.to_expr(Some(&syn::parse_quote_spanned! { d.span() => Self }), priv_) .into_owned() }), extra: Some(quote_mixed! { impl #impl_generics #crate_::ParseMetaFlatNamed for #enum_ident #type_generics #where_clause { #[inline] fn field_names() -> &'static [&'static #priv_::str] { #field_names } #[inline] fn parse_meta_flat_named<'____s, ____S>( inputs: &[____S], _mode: #crate_::ParseMode, prefix: &#priv_::str, validate: #priv_::bool, ) -> #crate_::Result where ____S: #priv_::Borrow<#priv_::ParseBuffer<'____s>>, { let span = _mode.to_full_span(inputs); #parse } } impl #impl_generics #crate_::ParseMetaFlatUnnamed for #enum_ident #type_generics #where_clause { #[inline] fn field_count() -> #priv_::Option<#priv_::usize> { #priv_::Option::None } #[inline] fn parse_meta_flat_unnamed<'____s, ____S>( inputs: &[____S], mode: #crate_::ParseMode, _index: #priv_::usize, ) -> #crate_::Result where ____S: #priv_::Borrow<#priv_::ParseBuffer<'____s>>, { ::parse_meta_flat_named(inputs, mode, "", true) } } }), crate_path, priv_path, }) } pub fn impl_parse_meta_item(input: syn::DeriveInput, errors: &Errors) -> TokenStream { let meta = match &input.data { syn::Data::Struct(struct_) => impl_for_struct(&input, struct_, errors), syn::Data::Enum(_) => impl_for_enum(&input, errors), syn::Data::Union(union) => { errors.push_spanned( union.union_token, "union not supported with derive(ParseMetaItem)", ); return Default::default(); } }; let MetaDef { parse, inline, flag, default, extra, crate_path: crate_, priv_path: priv_, } = match meta { Some(m) => m, None => return Default::default(), }; let ident = &input.ident; let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); let inline = inline.map(|inline| { quote_mixed! { #[inline] fn parse_meta_item_inline<'____s, ____S>( inputs: &[____S], _mode: #crate_::ParseMode, ) -> #crate_::Result where ____S: #priv_::Borrow<#priv_::ParseBuffer<'____s>>, { #inline } } }); let flag = flag.map(|flag| { quote_mixed! { #[inline] fn parse_meta_item_flag(span: #priv_::Span) -> #crate_::Result { #flag } } }); let missing = default.map(|default| { quote_mixed! { #[inline] fn missing_meta_item(name: &#priv_::str, span: #priv_::Span) -> #crate_::Result { #crate_::Result::Ok((#default)) } } }); quote_mixed! { impl #impl_generics #crate_::ParseMetaItem for #ident #type_generics #where_clause { #[inline] fn parse_meta_item( input: #priv_::ParseStream, _mode: #crate_::ParseMode, ) -> #crate_::Result { #parse } #inline #flag #missing } #extra } } deluxe-macros-0.5.0/types/enum.rs000064400000000000000000000321571046102023000150640ustar 00000000000000use super::*; use deluxe_core::{ parse_helpers::{self, FieldStatus}, ParseAttributes, ParseMetaItem, Result, }; use proc_macro2::{Span, TokenStream}; use quote::quote_spanned; use std::collections::{BTreeSet, HashSet}; use syn::spanned::Spanned; pub struct Enum<'e> { pub enum_: &'e syn::DataEnum, pub variants: Vec>, pub default: Option, pub crate_: Option, pub attributes: Vec, pub and_thens: Vec, pub allow_unknown_fields: Option, } impl<'e> Enum<'e> { #[inline] pub fn field_names() -> &'static [&'static str] { &["default", "crate", "attributes", "and_then"] } #[inline] pub fn to_inline_parsing_tokens(&self, crate_: &syn::Path, mode: TokenMode) -> TokenStream { let default = self.default.as_ref().map(|d| { let priv_path: syn::Path = syn::parse_quote! { #crate_::____private }; d.to_expr( Some(&syn::parse_quote_spanned! { d.span() => Self }), &priv_path, ) }); Variant::to_parsing_tokens( &self.variants, crate_, mode, default.as_ref().map(|d| d.as_ref()), &self.and_thens, self.allow_unknown_fields.unwrap_or(false), ) } pub fn to_accepts_all_tokens(&self, crate_: &syn::Path) -> Option { if self.allow_unknown_fields.unwrap_or(false) || self.variants.iter().any(|v| { !v.is_skipped() && v.flatten.unwrap_or(false) && v.allow_unknown_fields.unwrap_or(false) }) { return Some(quote_mixed! { true }); } let mut accepts_all = self .variants .iter() .filter_map(|v| { v.flatten .unwrap_or(false) .then(|| Field::to_accepts_all_tokens(&v.fields, crate_)) .flatten() }) .peekable(); accepts_all.peek().is_some().then(|| { quote_mixed! { #(#accepts_all)||* } }) } pub fn to_field_names_tokens(&self, crate_: &syn::Path, priv_: &syn::Path) -> TokenStream { let any_flat_nested = self .variants .iter() .any(|v| v.flatten.unwrap_or(false) && v.fields.iter().any(|f| f.is_flat())); let field_names = self.variants.iter().flat_map(|v| { v.idents .iter() .filter_map(|ident| { if v.flatten.unwrap_or(false) || v.is_skipped() { return None; } let ident = ident.to_string(); Some(if any_flat_nested { quote_mixed! { vec.push(#ident); } } else { quote_mixed! { #ident } }) }) .chain(v.fields.iter().filter_map(|field| { if !v.flatten.unwrap_or(false) { return None; } match &field.flatten { Some(FieldFlatten { value: true, prefix: Some(prefix), .. }) => { let ty = &field.field.ty; let prefix = parse_helpers::key_to_string(prefix); let names = quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaFlatNamed>::field_names() }; Some(quote_mixed! { vec.extend({ static CELL: #priv_::SyncOnceCell<#priv_::Vec<#priv_::String>> = #priv_::SyncOnceCell::new(); CELL.get_or_init(|| #priv_::parse_helpers::join_paths(#prefix, #names)) .iter() .map(|ps| ps.as_str()) }); }) } Some(FieldFlatten { value: true, prefix: None, .. }) => { let ty = &field.field.ty; let names = quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaFlatNamed>::field_names() }; Some(quote_mixed! { vec.extend_from_slice(#names); }) }, _ => None, } })) .chain(v.fields.iter().flat_map(|field| { field.idents.iter().filter_map(|ident| { if !v.flatten.unwrap_or(false) { return None; } match &field.flatten { Some(FieldFlatten { value: true, .. }) => None, _ => { let ident = ident.to_string(); if any_flat_nested { Some(quote_mixed! { vec.push(#ident); }) } else { Some(quote_mixed! { #ident }) } } } }) })) }); if any_flat_nested { quote_mixed! { { static CELL: #priv_::SyncOnceCell<#priv_::Vec<&'static #priv_::str>> = #priv_::SyncOnceCell::new(); CELL.get_or_init(|| { let mut vec = #priv_::Vec::new(); #(#field_names)* vec }).as_slice() } } } else { quote_mixed! { &[#(#field_names),*] } } } } deluxe_core::define_with_collection!(mod mod_path_vec, deluxe_core::with::mod_path, Vec); impl<'e> ParseAttributes<'e, syn::DeriveInput> for Enum<'e> { #[inline] fn path_matches(path: &syn::Path) -> bool { path.is_ident("deluxe") } fn parse_attributes(i: &'e syn::DeriveInput) -> Result { parse_helpers::parse_struct_attr_tokens( parse_helpers::ref_tokens::(i), |inputs, _| { let enum_ = match &i.data { syn::Data::Enum(e) => e, _ => return Err(syn::Error::new_spanned(i, "wrong DeriveInput type")), }; let errors = crate::Errors::new(); let mut default = FieldStatus::::None; let mut crate_ = FieldStatus::None; let mut and_thens = Vec::new(); let mut attributes = Vec::new(); let mut allow_unknown_fields = FieldStatus::None; errors.push_result(parse_helpers::parse_struct(inputs, |input, path, span| { match path { "default" => default.parse_named_item("default", input, span, &errors), "crate" => crate_.parse_named_item("crate", input, span, &errors), "and_then" => { match errors.push_result(<_>::parse_meta_item_named(input, path, span)) { Some(e) => and_thens.push(e), None => parse_helpers::skip_meta_item(input), } } "attributes" => { match errors .push_result(mod_path_vec::parse_meta_item_named(input, path, span)) { Some(attrs) => attributes.extend(attrs.into_iter()), None => parse_helpers::skip_meta_item(input), } } "allow_unknown_fields" => allow_unknown_fields.parse_named_item( "allow_unknown_fields", input, span, &errors, ), _ => { parse_helpers::check_unknown_attribute( path, span, Self::field_names(), &errors, ); parse_helpers::skip_meta_item(input); } } Ok(()) })); let variants = enum_ .variants .iter() .filter_map(|v| { errors.push_result( >::parse_attributes(v), ) }) .collect::>(); let mut all_idents = HashSet::new(); let mut container = None; let mut variant_keys = BTreeSet::>>::new(); for variant in &variants { if let Some(c) = variant .fields .iter() .find_map(|f| f.container.as_ref().and_then(|c| c.value.then_some(c))) { if container.is_some() { if let Some(lifetime) = c.lifetime.as_ref() { errors.push_spanned( lifetime, "only the first `container` field can contain a `lifetime` parameter" ); } if let Some(ty) = c.ty.as_ref() { errors.push_spanned( ty, "only the first `container` field can contain a `type` parameter", ); } } else { container = Some(c); } } if !variant.flatten.unwrap_or(false) { variant_keys.insert( [variant.idents.iter().map(|i| i.to_string()).collect()].into(), ); for ident in &variant.idents { if all_idents.contains(&ident) { errors.push_spanned( ident, format_args!("duplicate variant name for `{ident}`"), ); } else { all_idents.insert(ident); } } } } for variant in &variants { if variant.flatten.unwrap_or(false) { if matches!(variant.variant.fields, syn::Fields::Named(_)) { let key = variant.field_key(); if variant_keys.contains(&key) { errors.push_spanned( variant.variant, "additional flattened variants must have at least one unique non-flattened, non-default field", ); } else { variant_keys.insert(key); } } else { errors.push_spanned( variant.variant, "only enum variants with named fields can have `flatten`", ); } } } if let FieldStatus::Some(default) = &default { if variant_keys.contains(&BTreeSet::new()) { errors.push( default.span(), "`default` cannot be used when a flattened variant has no unique field", ); } } errors.check()?; Ok(Self { enum_, variants, default: default.into(), crate_: crate_.into(), attributes, and_thens, allow_unknown_fields: allow_unknown_fields.into(), }) }, ) } } deluxe-macros-0.5.0/types/field.rs000064400000000000000000001410761046102023000152040ustar 00000000000000use deluxe_core::{ parse_helpers::{self, FieldStatus}, ParseAttributes, ParseMetaItem, ParseMode, Result, SpannedValue, }; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, TokenStreamExt}; use std::borrow::{Borrow, Cow}; use syn::{ parse::{ParseBuffer, ParseStream}, spanned::Spanned, }; pub enum FieldDefault { Default(Span), Expr(TokenStream), } impl FieldDefault { pub fn to_expr(&self, ty: Option<&syn::Type>, priv_: &syn::Path) -> Cow { match self { FieldDefault::Default(span) => { let ty = if let Some(ty) = ty { quote_spanned! { ty.span() => #ty } } else { quote_spanned! { *span => _ } }; Cow::Owned(quote_spanned! { *span => <#ty as #priv_::Default>::default() }) } FieldDefault::Expr(expr) => Cow::Borrowed(expr), } } } impl ParseMetaItem for FieldDefault { #[inline] fn parse_meta_item(input: ParseStream, _mode: ParseMode) -> Result { Ok(Self::Expr(input.parse()?)) } #[inline] fn parse_meta_item_flag(span: Span) -> Result { Ok(Self::Default(span)) } } impl quote::ToTokens for FieldDefault { #[inline] fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Default(span) => tokens.append_all(quote_spanned!(*span => Default::default())), Self::Expr(expr) => tokens.append_all(expr.clone()), } } } #[derive(Debug)] pub struct FieldFlatten { pub span: Span, pub value: bool, pub prefix: Option, } impl quote::ToTokens for FieldFlatten { #[inline] fn to_tokens(&self, tokens: &mut TokenStream) { syn::LitBool::new(self.value, self.span).to_tokens(tokens); } } impl ParseMetaItem for FieldFlatten { fn parse_meta_item(input: ParseStream, mode: ParseMode) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(syn::LitBool) { Ok(Self { span: input.span(), value: input.parse::()?.value(), prefix: None, }) } else if lookahead.peek(syn::token::Brace) { ::parse_delimited_meta_item( input, mode, ) } else { Err(lookahead.error()) } } fn parse_meta_item_inline<'s, S: Borrow>>( inputs: &[S], mode: ParseMode, ) -> Result { let errors = crate::Errors::new(); let span = mode.to_full_span(inputs); let mut prefix = FieldStatus::None; errors.push_result(parse_helpers::parse_struct(inputs, |input, path, span| { match path { "prefix" => prefix.parse_named_item_with( "prefix", input, span, &errors, deluxe_core::with::any_path::parse_meta_item_named, ), _ => { errors.push_syn(parse_helpers::unknown_error(path, span, &["prefix"])); parse_helpers::skip_meta_item(input); } } Ok(()) })); errors.check()?; Ok(Self { span, value: true, prefix: prefix.into(), }) } #[inline] fn parse_meta_item_flag(span: Span) -> Result { Ok(Self { span, value: true, prefix: None, }) } } pub struct FieldContainer { pub span: Span, pub value: bool, pub lifetime: Option, pub ty: Option, } impl quote::ToTokens for FieldContainer { #[inline] fn to_tokens(&self, tokens: &mut TokenStream) { syn::LitBool::new(self.value, self.span).to_tokens(tokens); } } impl ParseMetaItem for FieldContainer { fn parse_meta_item(input: ParseStream, mode: ParseMode) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(syn::LitBool) { let value = input.parse::()?; Ok(Self { span: value.span(), value: value.value(), lifetime: None, ty: None, }) } else if lookahead.peek(syn::token::Brace) { ::parse_delimited_meta_item( input, mode, ) } else { Err(lookahead.error()) } } fn parse_meta_item_inline<'s, S: Borrow>>( inputs: &[S], mode: ParseMode, ) -> Result { let errors = crate::Errors::new(); let span = mode.to_full_span(inputs); let mut lifetime = FieldStatus::None; let mut ty = FieldStatus::None; errors.push_result(parse_helpers::parse_struct(inputs, |input, path, span| { match path { "lifetime" => lifetime.parse_named_item("lifetime", input, span, &errors), "ty" => ty.parse_named_item("ty", input, span, &errors), _ => { errors.push_syn(parse_helpers::unknown_error( path, span, &["lifetime", "ty"], )); parse_helpers::skip_meta_item(input); } } Ok(()) })); errors.check()?; Ok(Self { span, value: true, lifetime: lifetime.into(), ty: ty.into(), }) } #[inline] fn parse_meta_item_flag(span: Span) -> Result { Ok(Self { span, value: true, lifetime: None, ty: None, }) } } pub enum Transform { Map(TokenStream), AndThen(TokenStream), } pub struct Field<'f> { pub field: &'f syn::Field, pub idents: Vec, pub default: Option, pub with: Option, pub flatten: Option, pub append: Option>, pub rest: Option>, pub container: Option, pub skip: Option>, pub transforms: Vec, } pub struct ItemDef { pub parse: TokenStream, pub inline: Option, pub flag: Option, pub extra_traits: Option, } #[derive(PartialEq, Eq, Clone, Copy)] pub enum TokenMode { ParseMetaItem, ParseAttributes, ExtractAttributes, } pub enum ParseTarget<'t> { Init(Option<&'t syn::Ident>), Var(&'t TokenStream), } pub(super) struct FieldData<'t, 'i, 'a> { pub mode: TokenMode, pub target: ParseTarget<'t>, pub inline_expr: &'i TokenStream, pub allowed_expr: &'a TokenStream, pub transparent: bool, pub variant: bool, pub allow_unknown_fields: bool, } impl<'f> Field<'f> { #[inline] pub fn is_flat(&self) -> bool { self.flatten.as_ref().map(|f| f.value).unwrap_or(false) || self.append.map(|v| *v).unwrap_or(false) || self.rest.map(|v| *v).unwrap_or(false) } #[inline] pub fn is_container(&self) -> bool { self.container.as_ref().map(|f| f.value).unwrap_or(false) } #[inline] pub fn is_parsable(&self) -> bool { !self.is_container() && !self.skip.map(|v| *v).unwrap_or(false) } #[inline] pub fn constraint_ty(&self) -> TokenStream { let ty = &self.field.ty; if self.transforms.is_empty() { quote_spanned! { ty.span() => #ty } } else { quote_spanned! { ty.span() => _ } } } pub fn parse_path(&self, crate_: &syn::Path, trait_: &str) -> TokenStream { self.with .as_ref() .map(|m| quote_spanned! { m.span() => #m }) .unwrap_or_else(|| { let ty = self.constraint_ty(); let trait_ = syn::Ident::new(trait_, Span::mixed_site()); quote_spanned! { ty.span() => <#ty as #crate_::#trait_> } }) } pub fn field_names() -> &'static [&'static str] { &[ "rename", "flatten", "append", "rest", "default", "alias", "with", "container", "skip", "map", "and_then", ] } pub(super) fn to_accepts_all_tokens( fields: &[Self], crate_: &syn::Path, ) -> Option { if fields.iter().any(|f| f.rest.map(|v| *v).unwrap_or(false)) { return Some(quote_mixed! { true }); } let mut flat_tys = fields .iter() .filter(|f| f.flatten.as_ref().map(|f| f.value).unwrap_or(false)) .map(|f| &f.field.ty) .peekable(); flat_tys.peek().is_some().then(|| { quote_mixed! { #(<#flat_tys as #crate_::ParseMetaFlatNamed>::ACCEPTS_ALL)||* } }) } pub(super) fn to_field_names_tokens( fields: &[Self], crate_: &syn::Path, priv_: &syn::Path, ) -> TokenStream { if fields .iter() .any(|f| f.flatten.as_ref().map(|f| f.value).unwrap_or(false)) { let names = fields.iter().filter(|f| f.is_parsable()).map(|f| match &f.flatten { Some(FieldFlatten { value: true, prefix: Some(prefix), .. }) => { let ty = f.constraint_ty(); let prefix = parse_helpers::key_to_string(prefix); let names = quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaFlatNamed>::field_names() }; quote_mixed! { #priv_::parse_helpers::extend_from_owned( &mut vec, { static CELL: #priv_::SyncOnceCell<#priv_::Vec<#priv_::Cow<'static, #priv_::str>>> = #priv_::SyncOnceCell::new(); CELL.get_or_init(|| #priv_::parse_helpers::join_paths(#prefix, #names)) }, ); } } Some(FieldFlatten { value: true, prefix: None, .. }) => { let ty = f.constraint_ty(); let names = quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaFlatNamed>::field_names() }; quote_mixed! { vec.extend_from_slice(#names); } } _ => { let names = f.idents.iter().map(|i| i.to_string()); quote_mixed! { #(vec.push(#names);)* } } }); quote_mixed! { { static CELL: #priv_::SyncOnceCell<#priv_::Vec<&'static #priv_::str>> = #priv_::SyncOnceCell::new(); CELL.get_or_init(|| { let mut vec = #priv_::Vec::new(); #(#names)* vec }).as_slice() } } } else { let names = fields.iter().filter_map(|f| { if !f.is_parsable() { return None; } let names = f.idents.iter().map(|i| i.to_string()); Some(quote_mixed! { #(#names),* }) }); quote_mixed! { &[#(#names),*] } } } fn to_parse_call_tokens( &self, inputs_expr: &TokenStream, allowed_expr: &TokenStream, crate_: &syn::Path, priv_: &syn::Path, ) -> TokenStream { let ty = &self.field.ty; if self.append.map(|v| *v).unwrap_or(false) { let path = self.parse_path(crate_, "ParseMetaAppend"); let idents = self.idents.iter().map(|i| i.to_string()); quote_mixed! { #path::parse_meta_append( #inputs_expr, &#priv_::parse_helpers::join_paths(prefix, &[#(#idents),*]), ) } } else if self.rest.map(|v| *v).unwrap_or(false) { let path = self.parse_path(crate_, "ParseMetaRest"); quote_mixed! { #path::parse_meta_rest(#inputs_expr, #allowed_expr) } } else if let Some(FieldFlatten { value: true, prefix, .. }) = self.flatten.as_ref() { if self.field.ident.is_some() { let prefix = match prefix { Some(prefix) => { let prefix = parse_helpers::key_to_string(prefix); quote_mixed! { &#priv_::parse_helpers::join_prefix(prefix, #prefix) } } None => quote_mixed! { "" }, }; let path = self.parse_path(crate_, "ParseMetaFlatNamed"); quote_mixed! { #path::parse_meta_flat_named( #inputs_expr, #crate_::ParseMode::Named(span), #prefix, false ) } } else { let path = self.parse_path(crate_, "ParseMetaFlatUnnamed"); quote_mixed! { #path::parse_meta_flat_unnamed(inputs, #crate_::ParseMode::Unnamed, index) } } } else if self.field.ident.is_some() { let path = self.parse_path(crate_, "ParseMetaItem"); // named field match &self.with { Some(m) => { let value_ident = syn::Ident::new("value", Span::mixed_site()); let input_ident = syn::Ident::new("input", Span::mixed_site()); let p_ident = syn::Ident::new("p", Span::mixed_site()); let span_ident = syn::Ident::new("span", Span::mixed_site()); // bind the return to a variable to span a type conversion error properly quote_spanned! { m.span() => { let #value_ident = #path::parse_meta_item_named(#input_ident, #p_ident, #span_ident); #value_ident } } } None => { let func = quote_spanned! { ty.span() => #path::parse_meta_item_named }; quote_mixed! { #func(input, p, span) } } } } else { let path = self.parse_path(crate_, "ParseMetaItem"); // unnamed field match &self.with { Some(m) => { let value_ident = syn::Ident::new("value", Span::mixed_site()); let input_ident = syn::Ident::new("input", Span::mixed_site()); quote_spanned! { m.span() => { let #value_ident = #path::parse_meta_item(#input_ident, #crate_::ParseMode::Unnamed); #value_ident } } } None => { quote_mixed! { #path::parse_meta_item(input, #crate_::ParseMode::Unnamed) } } } } } pub(super) fn to_pre_post_tokens( fields: &[Self], orig: &syn::Fields, crate_: &syn::Path, data: &FieldData, ) -> (TokenStream, TokenStream) { let FieldData { mode, target, allowed_expr, transparent, variant, .. } = data; let priv_path: syn::Path = syn::parse_quote! { #crate_::____private }; let priv_ = &priv_path; let is_unnamed = matches!(orig, syn::Fields::Unnamed(_)); let names = fields .iter() .enumerate() .map(|(i, _)| quote::format_ident!("field{i}", span = Span::mixed_site())) .collect::>(); let container_def = fields.iter().enumerate().filter_map(|(i, f)| { (f.is_container() && *mode != TokenMode::ParseMetaItem).then(|| { let name = names[i].clone(); let func = match mode { TokenMode::ParseAttributes => quote! { container_from }, TokenMode::ExtractAttributes => quote! { container_from_mut }, _ => unreachable!(), }; quote_mixed! { #name = #priv_::FieldStatus::Some(#crate_::ContainerFrom::#func(obj)); } }) }); let field_errors = { let mut cur_index = 0usize; let mut extra_counts = quote! {}; let last_flat = is_unnamed .then(|| { fields .iter() .enumerate() .filter(|(_, f)| f.is_parsable() && f.is_flat()) .last() .map(|(i, _)| i) }) .flatten(); fields .iter() .enumerate() .filter_map(|(i, f)| { if f.is_container() || *transparent { return None; } let push = (f.default.is_none() && !matches!(target, ParseTarget::Var(_))) .then(|| { let name = &names[i]; if is_unnamed { if f.is_flat() { let inputs_expr = if Some(i) == last_flat { quote_mixed! { inputs } } else { quote_mixed! { &#priv_::parse_helpers::fork_inputs(inputs) } }; let call = f.to_parse_call_tokens(&inputs_expr, allowed_expr, crate_, priv_); let span = if *variant { quote_mixed! { span } } else { quote_mixed! { #priv_::Span::call_site() } }; let flat_path = f.parse_path(crate_, "ParseMetaFlatUnnamed"); quote_mixed! { if #name.is_none() { let _mode = #crate_::ParseMode::Unnamed; #priv_::parse_helpers::parse_empty(#span, |input| { let inputs = &[input]; match errors.push_result(#call) { #priv_::Option::Some(v) => #name = #priv_::FieldStatus::Some(v), #priv_::Option::None => #name = #priv_::FieldStatus::ParseError, } #crate_::Result::Ok(()) })?; if let #priv_::Option::Some(count) = #flat_path::field_count() { index += count; } } } } else { let path = f.parse_path(crate_, "ParseMetaItem"); let name_format = quote_mixed! { #priv_::format!("{}", name) }; let name_format = if *mode == TokenMode::ParseMetaItem { name_format } else { quote_mixed! { if let #priv_::Option::Some(path_name) = path_name { #priv_::format!("{} on #[{}]", name, path_name) } else { #name_format } } }; quote_mixed! { if #name.is_none() { let span = _mode.to_full_span(inputs); let name = index + #cur_index #extra_counts; let name = #name_format; if let #priv_::Option::Some(v) = errors.push_result( #path::missing_meta_item( &name, span, ), ) { #name = #priv_::FieldStatus::Some(v); } } } } } else if !f.is_flat() { let field = f.field.ident.as_ref().unwrap().to_string(); let path = f.parse_path(crate_, "ParseMetaItem"); let name_format = quote_mixed! { #priv_::format!("`{}`", name) }; let name_format = if *mode == TokenMode::ParseMetaItem { name_format } else { quote_mixed! { if let #priv_::Option::Some(path_name) = path_name { #priv_::format!("#[{}({})]", path_name, name) } else { #name_format } } }; quote_mixed! { if #name.is_none() { let name = #priv_::parse_helpers::join_path(prefix, #field); let name = #name_format; if let #priv_::Option::Some(v) = errors.push_result( #path::missing_meta_item( &name, span, ), ) { #name = #priv_::FieldStatus::Some(v); } } } } else { quote! {} } }); if is_unnamed { let ty = f.constraint_ty(); if f.flatten.as_ref().map(|f| f.value).unwrap_or(false) { extra_counts.extend(quote_spanned! { ty.span() => + <#ty as #crate_::ParseMetaFlatUnnamed>::field_count().unwrap_or(0) }); } else { cur_index += 1; } } push }) .collect::>() }; let field_sets = fields.iter().enumerate().map(|(i, f)| { let name = &names[i]; let ty = &f.field.ty; let transforms = f.transforms.iter().enumerate().map(|(ti, t)| { let constraint = match ti + 1 == f.transforms.len() { true => quote_spanned! { ty.span() => #ty }, false => quote_spanned! { ty.span() => _ }, }; match t { Transform::Map(expr) => quote_mixed! { let #name: #priv_::FieldStatus<#constraint> = #name.map((#expr)); }, Transform::AndThen(expr) => quote_mixed! { let #name: #priv_::FieldStatus<#constraint> = #name.and_then(|v| { match errors.push_result((#expr)(v)) { #priv_::Option::Some(v) => #priv_::FieldStatus::Some(v), _ => #priv_::FieldStatus::ParseError, } }); }, } }); let set_default = f.default.as_ref().map(|def| { let expr = def.to_expr(f.transforms.is_empty().then_some(ty), priv_); quote_mixed! { let #name = #name.or_else(|| #priv_::FieldStatus::Some((#expr))); } }); quote_mixed! { #set_default #(#transforms)* } }); let field_unwraps = fields.iter().enumerate().filter_map(|(i, _)| { (!matches!(target, ParseTarget::Var(_))).then(|| { let name = &names[i]; quote_mixed! { let #name = #name.unwrap_or_else(|| #priv_::unreachable!()); } }) }); let option_inits = fields.iter().enumerate().map(|(i, f)| { let name = &names[i]; let ty = f.constraint_ty(); quote_mixed! { let mut #name: #priv_::FieldStatus::<#ty> = #priv_::FieldStatus::None; } }); let errors_init = (!transparent).then(|| { quote_mixed! { let errors = #crate_::Errors::new(); } }); let errors_check = (!transparent).then(|| { quote_mixed! { errors.check()?; } }); match orig { syn::Fields::Named(_) => { let last_flat = fields .iter() .enumerate() .filter(|(_, f)| f.is_parsable() && f.is_flat()) .last() .map(|(i, _)| i); let flat_fields = fields.iter().enumerate().filter_map(|(i, f)| { if !f.is_flat() || !f.is_parsable() { return None; } let inputs_expr = if Some(i) == last_flat { quote_mixed! { inputs } } else { quote_mixed! { &#priv_::parse_helpers::fork_inputs(inputs) } }; let call = f.to_parse_call_tokens(&inputs_expr, allowed_expr, crate_, priv_); let name = &names[i]; Some(quote_mixed! { match errors.push_result(#call) { #priv_::Option::Some(val) => #name = #priv_::FieldStatus::Some(val), #priv_::Option::None => #name = #priv_::FieldStatus::ParseError, } }) }); let ret = match target { ParseTarget::Init(variant) => { let field_defs = fields.iter().enumerate().map(|(i, f)| { let ident = f.field.ident.as_ref().unwrap(); let name = &names[i]; quote_mixed! { #ident: #name } }); let variant = variant.iter(); quote_mixed! { #crate_::Result::Ok(Self #(::#variant)* { #(#field_defs),* }) } } ParseTarget::Var(target) => { let field_defs = fields.iter().enumerate().map(|(i, f)| { let ident = f.field.ident.as_ref().unwrap(); let name = &names[i]; quote_mixed! { if let #priv_::FieldStatus::Some(val) = #name { #target.#ident = val; } } }); quote_mixed! { #(#field_defs)* #crate_::Result::Ok(#target) } } }; let pre = quote_mixed! { #(#option_inits)* #errors_init }; let post = quote_mixed! { #(#flat_fields)* #(#container_def)* #(#field_errors)* #(#field_sets)* #errors_check #(#field_unwraps)* #ret }; (pre, post) } syn::Fields::Unnamed(_) => { let ret = match target { ParseTarget::Init(variant) => { let variant = variant.iter(); quote_mixed! { #crate_::Result::Ok(Self #(::#variant)* (#(#names),*)) } } ParseTarget::Var(target) => { let field_defs = fields.iter().enumerate().map(|(i, _)| { let name = &names[i]; let i = syn::Index::from(i); quote_mixed! { if let #priv_::FieldStatus::Some(val) = #name { #target.#i = val; } } }); quote_mixed! { #(#field_defs)* #crate_::Result::Ok(#target) } } }; let pre = quote_mixed! { #(#option_inits)* #errors_init }; let post = quote_mixed! { #(#container_def)* #(#field_errors)* #(#field_sets)* #errors_check #(#field_unwraps)* #ret }; (pre, post) } syn::Fields::Unit => { let variant = match target { ParseTarget::Init(variant) => *variant, _ => None, } .into_iter(); let pre = quote_mixed! {}; let post = quote_mixed! { #crate_::Result::Ok(Self #(::#variant)*) }; (pre, post) } } } pub(super) fn to_parsing_tokens( fields: &[Self], orig: &syn::Fields, crate_: &syn::Path, (pre, post): (&TokenStream, &TokenStream), data: FieldData, ) -> ItemDef { let FieldData { mode, target, inline_expr, allowed_expr, allow_unknown_fields, .. } = data; let priv_path: syn::Path = syn::parse_quote! { #crate_::____private }; let priv_ = &priv_path; let pub_fields = fields.iter().filter(|f| f.is_parsable()); let any_flat = pub_fields.clone().any(|f| f.is_flat()); let names = fields .iter() .enumerate() .map(|(i, _)| quote::format_ident!("field{i}", span = Span::mixed_site())) .collect::>(); let inputs_expr = any_flat .then(|| { quote_mixed! { &#priv_::parse_helpers::fork_inputs(inputs) } }) .unwrap_or_else(|| { quote_mixed! { inputs } }); let (parse, inline, flag) = match orig { syn::Fields::Named(_) => { let field_matches = fields.iter().enumerate().filter_map(|(i, f)| { if f.is_flat() || !f.is_parsable() { return None; } let name = names[i].clone(); let first_ident = f.idents.first().unwrap().to_string(); let idents = f.idents.iter().map(|i| i.to_string()); let call = f.to_parse_call_tokens(&inputs_expr, allowed_expr, crate_, priv_); Some(quote_mixed! { #(#priv_::Option::Some(#idents))|* => { #name.parse_named_item_with(#first_ident, input, span, &errors, |input, p, span| { #call }); } }) }); let validate = (!allow_unknown_fields).then(|| { quote_mixed! { if validate { #priv_::parse_helpers::check_unknown_attribute( p, span, #allowed_expr, &errors ); } } }); ( quote_mixed! { <#priv_::parse_helpers::Brace as #priv_::parse_helpers::ParseDelimited>::parse_delimited_with( input, move |input| { #inline_expr }, ) }, Some(quote_mixed! { #pre errors.push_result(#priv_::parse_helpers::parse_struct( #inputs_expr, |input, p, span| { match p.strip_prefix(prefix) { #(#field_matches)* _ => { #validate #priv_::parse_helpers::skip_meta_item(input); } } #crate_::Result::Ok(()) }, )); #post }), Some(quote_mixed! { let _mode = #crate_::ParseMode::Named(span); #priv_::parse_helpers::parse_empty(span, move |input| { #inline_expr }) }), ) } syn::Fields::Unnamed(_) => { let field_matches = fields.iter().enumerate().filter(|(_, f)| { f.is_parsable() && !f.append.map(|v| *v).unwrap_or(false) && !f.rest.map(|v| *v).unwrap_or(false) }).enumerate().map(|(index, (real_index, f))| { let name = &names[real_index]; let call = f.to_parse_call_tokens(&inputs_expr, allowed_expr, crate_, priv_); let increment = any_flat.then(|| { let ty = f.constraint_ty(); match f.is_flat() { true => quote_mixed! { if let #priv_::Option::Some(count) = <#ty as #crate_::ParseMetaFlatUnnamed>::field_count() { index += count; } }, false => quote_mixed! { index += 1; } } }); quote_mixed! { #index => { #name.parse_unnamed_item_with(input, &errors, |input, mode| { #call }); #increment } } }); let parse_fields = pub_fields.clone().next().is_some().then(|| { let field_count = pub_fields.clone().count(); quote_mixed! { errors.push_result(#priv_::parse_helpers::parse_tuple_struct(inputs, #field_count, |input, inputs, i| { match i { #(#field_matches)* _ => #priv_::unreachable!(), } #crate_::Result::Ok(()) })); } }).unwrap_or_else(|| { quote_mixed! { for input in inputs { #priv_::parse_helpers::parse_eof_or_trailing_comma( #priv_::Borrow::borrow(input), )?; } } }); let allows_empty = matches!(target, ParseTarget::Var(_)) || pub_fields .clone() .all(|f| f.is_flat() || f.default.is_some()); ( quote_mixed! { <#priv_::parse_helpers::Paren as #priv_::parse_helpers::ParseDelimited>::parse_delimited_with( input, move |input| { #inline_expr }, ) }, Some(quote_mixed! { #pre #parse_fields #post }), allows_empty.then(|| { quote_mixed! { let _mode = #crate_::ParseMode::Unnamed; #priv_::parse_helpers::parse_empty(span, move |input| { #inline_expr }) } }), ) } syn::Fields::Unit => { let inline = if mode == TokenMode::ParseMetaItem { quote_mixed! { #pre <() as #crate_::ParseMetaItem>::parse_meta_item_inline(inputs, _mode)?; #post } } else { quote_mixed! { #pre for input in inputs { #priv_::parse_helpers::parse_eof_or_trailing_comma( #priv_::Borrow::borrow(input), )?; } #post } }; ( quote_mixed! { <#priv_::parse_helpers::Paren as #priv_::parse_helpers::ParseDelimited>::parse_delimited_with( input, move |input| { #inline_expr }, ) }, Some(inline), Some(quote_mixed! { #pre #post }), ) } }; ItemDef { parse, inline, flag, extra_traits: None, } } } impl<'f> ParseAttributes<'f, syn::Field> for Field<'f> { #[inline] fn path_matches(path: &syn::Path) -> bool { path.is_ident("deluxe") } fn parse_attributes(field: &'f syn::Field) -> Result { parse_helpers::parse_struct_attr_tokens( parse_helpers::ref_tokens::(field), |inputs, _| { let named = field.ident.is_some(); let errors = crate::Errors::new(); let mut alias_span = FieldStatus::None; let mut idents = Vec::new(); let mut default = FieldStatus::None; let mut with = FieldStatus::None; let mut flatten = FieldStatus::::None; let mut append = FieldStatus::None; let mut rest = FieldStatus::None; let mut rename = FieldStatus::None; let mut container = FieldStatus::None; let mut skip = FieldStatus::None; let mut transforms = Vec::new(); errors.push_result(parse_helpers::parse_struct(inputs, |input, path, span| { match path { "flatten" => flatten.parse_named_item("flatten", input, span, &errors), "append" => { if !named { errors.push(span, "`append` not allowed on tuple struct field"); parse_helpers::skip_meta_item(input); } else { append.parse_named_item("append", input, span, &errors); } } "rest" => { if !named { errors.push(span, "`rest` not allowed on tuple struct field"); parse_helpers::skip_meta_item(input); } else { rest.parse_named_item("rest", input, span, &errors); } } "default" => default.parse_named_item("default", input, span, &errors), "with" => with.parse_named_item("with", input, span, &errors), "rename" => { if !named { errors.push(span, "`rename` not allowed on tuple struct field"); } else { rename.parse_named_item_with( "rename", input, span, &errors, |input, _, span| { let name = <_>::parse_meta_item_named(input, path, span)?; if field.ident.as_ref() == Some(&name) { Err(syn::Error::new( span, "cannot rename field to its own name", )) } else if idents.contains(&name) { Err(syn::Error::new( span, format_args!("alias already given for `{name}`"), )) } else { idents.insert(0, name); Ok(span) } }, ); } } "alias" => { if !named { errors.push(span, "`alias` not allowed on tuple struct field"); } else { match errors .push_result(<_>::parse_meta_item_named(input, path, span)) { Some(alias) => { if field.ident.as_ref() == Some(&alias) { errors.push(span, "cannot alias field to its own name"); } else if idents.contains(&alias) { errors.push( span, format_args!("duplicate alias for `{alias}`"), ); } else { idents.push(alias); if alias_span.is_none() { alias_span = FieldStatus::Some(span); } } } None => parse_helpers::skip_meta_item(input), } } } "container" => { container.parse_named_item("container", input, span, &errors) } "skip" => skip.parse_named_item("container", input, span, &errors), "map" => { match errors.push_result(<_>::parse_meta_item_named(input, path, span)) { Some(e) => transforms.push(Transform::Map(e)), None => parse_helpers::skip_meta_item(input), } } "and_then" => { match errors.push_result(<_>::parse_meta_item_named(input, path, span)) { Some(e) => transforms.push(Transform::AndThen(e)), None => parse_helpers::skip_meta_item(input), } } _ => { parse_helpers::check_unknown_attribute( path, span, Self::field_names(), &errors, ); parse_helpers::skip_meta_item(input); } } Ok(()) })); deluxe_core::only_one!("", &errors, flatten, append, rest); deluxe_core::only_one!("", &errors, container, flatten); deluxe_core::only_one!("", &errors, container, rename); deluxe_core::only_one!("", &errors, container, with); deluxe_core::only_one!("", &errors, container, ("alias", alias_span.as_ref())); deluxe_core::only_one!("", &errors, default, flatten); deluxe_core::only_one!("", &errors, flatten, rename); deluxe_core::only_one!("", &errors, flatten, ("alias", alias_span.as_ref())); if rename.is_none() && !flatten.as_ref().map(|f| f.value).unwrap_or(false) { if let Some(ident) = field.ident.as_ref() { idents.insert(0, syn::ext::IdentExt::unraw(ident)); } } errors.check()?; Ok(Self { field, idents: idents.into_iter().collect(), default: default.into(), with: with.into(), flatten: flatten.into(), append: append.into(), rest: rest.into(), container: container.into(), skip: skip.into(), transforms, }) }, ) } } deluxe-macros-0.5.0/types/struct.rs000064400000000000000000000551151046102023000154430ustar 00000000000000use super::*; use deluxe_core::{ parse_helpers::{self, FieldStatus}, ParseAttributes, ParseMetaItem, ParseMode, Result, }; use proc_macro2::{Span, TokenStream}; use quote::quote_spanned; use std::{borrow::Borrow, collections::HashSet}; use syn::{ parse::{ParseBuffer, ParseStream}, spanned::Spanned, }; pub struct StructTransparent { pub span: Span, pub value: bool, pub flatten_named: Option, pub flatten_unnamed: Option, pub append: Option, pub rest: Option, } impl quote::ToTokens for StructTransparent { #[inline] fn to_tokens(&self, tokens: &mut TokenStream) { syn::LitBool::new(self.value, self.span).to_tokens(tokens); } } impl ParseMetaItem for StructTransparent { fn parse_meta_item(input: ParseStream, mode: ParseMode) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(syn::LitBool) { let value = input.parse::()?; Ok(Self { span: value.span(), value: value.value(), flatten_named: None, flatten_unnamed: None, append: None, rest: None, }) } else if lookahead.peek(syn::token::Brace) { ::parse_delimited_meta_item( input, mode, ) } else { Err(lookahead.error()) } } fn parse_meta_item_inline<'s, S: Borrow>>( inputs: &[S], mode: ParseMode, ) -> Result { let errors = crate::Errors::new(); let span = mode.to_full_span(inputs); let mut flatten_named = FieldStatus::None; let mut flatten_unnamed = FieldStatus::None; let mut append = FieldStatus::None; let mut rest = FieldStatus::None; errors.push_result(parse_helpers::parse_struct(inputs, |input, path, span| { match path { "flatten_named" => { flatten_named.parse_named_item("flatten_named", input, span, &errors) } "flatten_unnamed" => { flatten_unnamed.parse_named_item("flatten_unnamed", input, span, &errors) } "append" => append.parse_named_item("append", input, span, &errors), "rest" => rest.parse_named_item("rest", input, span, &errors), _ => { errors.push_syn(parse_helpers::unknown_error( path, span, &["flatten_named", "flatten_unnamed", "append", "rest"], )); parse_helpers::skip_meta_item(input); } } Ok(()) })); errors.check()?; Ok(Self { span, value: true, flatten_named: flatten_named.into(), flatten_unnamed: flatten_unnamed.into(), append: append.into(), rest: rest.into(), }) } #[inline] fn parse_meta_item_flag(span: Span) -> Result { Ok(Self { span, value: true, flatten_named: None, flatten_unnamed: None, append: None, rest: None, }) } } pub struct Struct<'s> { pub struct_: &'s syn::DataStruct, pub fields: Vec>, pub default: Option, pub crate_: Option, pub transparent: Option, pub allow_unknown_fields: Option, pub attributes: Vec, pub and_thens: Vec, } impl<'s> Struct<'s> { #[inline] pub fn is_transparent(&self) -> bool { self.transparent.as_ref().map(|t| t.value).unwrap_or(false) && self .fields .iter() .filter(|f| f.is_parsable() && !f.is_flat()) .take(2) .count() == 1 } #[inline] pub fn field_names() -> &'static [&'static str] { &[ "transparent", "default", "crate", "attributes", "and_then", "allow_unknown_fields", ] } #[inline] pub fn to_accepts_all_tokens(&self, crate_: &syn::Path) -> Option { if self.allow_unknown_fields.unwrap_or(false) { return Some(quote_mixed! { true }); } Field::to_accepts_all_tokens(&self.fields, crate_) } #[inline] pub fn to_field_names_tokens(&self, crate_: &syn::Path, priv_: &syn::Path) -> TokenStream { Field::to_field_names_tokens(&self.fields, crate_, priv_) } pub fn to_parsing_tokens( &self, orig: &syn::DeriveInput, crate_: &syn::Path, mode: TokenMode, inline_expr: &TokenStream, allowed_expr: &TokenStream, ) -> ItemDef { let priv_path: syn::Path = syn::parse_quote! { #crate_::____private }; let priv_ = &priv_path; let target = self.default.as_ref().map(|_| quote_mixed! { target }); let target = target .as_ref() .map(ParseTarget::Var) .unwrap_or_else(|| ParseTarget::Init(None)); let transparent = self.is_transparent(); let allow_unknown_fields = self.allow_unknown_fields.unwrap_or(false); let field_data = FieldData { mode, target, inline_expr, allowed_expr, transparent, variant: false, allow_unknown_fields, }; let orig_fields = match &orig.data { syn::Data::Struct(s) => &s.fields, _ => unreachable!(), }; let (mut pre, post) = Field::to_pre_post_tokens(&self.fields, orig_fields, crate_, &field_data); let post = if self.and_thens.is_empty() { post } else { let and_thens = &self.and_thens; quote_mixed! { let ret = { #post }; let errors = #crate_::Errors::new(); let ret = ret.ok(); #(let ret = ret.and_then(|v| { let f = #and_thens; errors.push_result(f(v)) });)* errors.check()?; #crate_::Result::Ok(ret.unwrap()) } }; let default_set = self.default.as_ref().map(|d| { let expr = d.to_expr(Some(&syn::parse_quote_spanned! { d.span() => Self }), priv_); quote_mixed! { let mut target: Self = (#expr); } }); if transparent { let (field, name) = self .fields .iter() .enumerate() .find_map(|(i, f)| { (f.is_parsable() && !f.is_flat()).then(|| { ( f, quote::format_ident!("field{i}", span = Span::mixed_site()), ) }) }) .unwrap(); let ty = &field.field.ty; let module = field .with .as_ref() .map(|m| quote_spanned! { m.span() => #m }); let parse_ty = module.clone().unwrap_or_else(|| { quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaItem> } }); pre.extend(default_set); if matches!(orig_fields, syn::Fields::Unnamed(_)) { pre.extend(quote_mixed! { let index = 0usize; }); } let parse = quote_mixed! { #pre #name = #priv_::FieldStatus::Some(#parse_ty::parse_meta_item(input, _mode)?); #post }; let inline = Some(quote_mixed! { #pre #name = #priv_::FieldStatus::Some(#parse_ty::parse_meta_item_inline(inputs, _mode)?); #post }); let flag = Some(quote_mixed! { #pre #name = #priv_::FieldStatus::Some(#parse_ty::parse_meta_item_flag(span)?); #post }); let struct_ident = &orig.ident; let (impl_generics, type_generics, where_clause) = orig.generics.split_for_impl(); let flatten_named = self .transparent .as_ref() .and_then(|t| t.flatten_named) .unwrap_or(false); let flatten_unnamed = self .transparent .as_ref() .and_then(|t| t.flatten_unnamed) .unwrap_or(false); let rest = self .transparent .as_ref() .and_then(|t| t.rest) .unwrap_or(false); let append = self .transparent .as_ref() .and_then(|t| t.append) .unwrap_or(false); let mut extra_traits = Vec::new(); if flatten_named { let flat_ty = module.clone().unwrap_or_else(|| { quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaFlatNamed> } }); extra_traits.push(quote_mixed! { impl #impl_generics #crate_::ParseMetaFlatNamed for #struct_ident #type_generics #where_clause { const ACCEPTS_ALL: #priv_::bool = #flat_ty::ACCEPTS_ALL; #[inline] fn field_names() -> &'static [&'static #priv_::str] { #flat_ty::field_names() } #[inline] fn parse_meta_flat_named<'s, S: #priv_::Borrow<#priv_::ParseBuffer<'s>>>( inputs: &[S], mode: #crate_::ParseMode, prefix: &#priv_::str, validate: #priv_::bool, ) -> #crate_::Result { #pre #name = #priv_::FieldStatus::Some(#flat_ty::parse_meta_flat_named(inputs, mode, prefix, validate)?); #post } } }); } if flatten_unnamed { let flat_ty = module.clone().unwrap_or_else(|| { quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaFlatUnnamed> } }); extra_traits.push(quote_mixed! { impl #impl_generics #crate_::ParseMetaFlatUnnamed for #struct_ident #type_generics #where_clause { #[inline] fn field_count() -> #priv_::Option<#priv_::usize> { #flat_ty::field_count() } #[inline] fn parse_meta_flat_unnamed<'s, S: #priv_::Borrow<#priv_::ParseBuffer<'s>>>( inputs: &[S], mode: #crate_::ParseMode, index: #priv_::usize, ) -> #crate_::Result { #pre #name = #priv_::FieldStatus::Some(#flat_ty::parse_meta_flat_unnamed(inputs, mode, index)?); #post } } }); } if rest { let rest_ty = module.clone().unwrap_or_else(|| { quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaRest> } }); extra_traits.push(quote_mixed! { impl #impl_generics #crate_::ParseMetaRest for #struct_ident #type_generics #where_clause { #[inline] fn parse_meta_rest<'s, S: #priv_::Borrow<#priv_::ParseBuffer<'s>>>( inputs: &[S], exclude: &[&#priv_::str], ) -> #crate_::Result { #pre #name = #priv_::FieldStatus::Some(#rest_ty::parse_meta_rest(inputs, exclude)?); #post } } }); } if append { let append_ty = module.unwrap_or_else(|| { quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaAppend> } }); extra_traits.push(quote_mixed! { impl #impl_generics #crate_::ParseMetaAppend for #struct_ident #type_generics #where_clause { #[inline] fn parse_meta_append<'s, S, I, P>(inputs: &[S], paths: I) -> #crate_::Result where S: #priv_::Borrow<#priv_::ParseBuffer<'s>>, I: #priv_::IntoIterator, I::IntoIter: #priv_::Clone, P: #priv_::AsRef<#priv_::str> { #pre #name = #priv_::FieldStatus::Some(#append_ty::parse_meta_append(inputs, paths)?); #post } } }); } let extra_traits = (!extra_traits.is_empty()).then(|| { quote_mixed! { #(#extra_traits)* } }); ItemDef { parse, inline, flag, extra_traits, } } else { let mut def = Field::to_parsing_tokens( &self.fields, orig_fields, crate_, (&pre, &post), field_data, ); def.inline = def.inline.map(|inline| { quote_mixed! { #default_set #inline } }); def } } } deluxe_core::define_with_collection!(mod mod_path_vec, deluxe_core::with::mod_path, Vec); impl<'s> ParseAttributes<'s, syn::DeriveInput> for Struct<'s> { #[inline] fn path_matches(path: &syn::Path) -> bool { path.is_ident("deluxe") } fn parse_attributes(i: &'s syn::DeriveInput) -> Result { parse_helpers::parse_struct_attr_tokens( parse_helpers::ref_tokens::(i), |inputs, _| { let struct_ = match &i.data { syn::Data::Struct(s) => s, _ => return Err(syn::Error::new_spanned(i, "wrong DeriveInput type")), }; let errors = crate::Errors::new(); let mut transparent = FieldStatus::None; let mut allow_unknown_fields = FieldStatus::None; let mut default = FieldStatus::None; let mut crate_ = FieldStatus::None; let mut and_thens = Vec::new(); let mut attributes = Vec::new(); let fields = struct_ .fields .iter() .filter_map(|f| errors.push_result(Field::parse_attributes(f))) .collect::>(); errors.push_result(parse_helpers::parse_struct(inputs, |input, path, span| { match path { "transparent" => { transparent.parse_named_item_with( "transparent", input, span, &errors, |input, _, span| { let mut iter = fields.iter().filter(|f| f.is_parsable()); if let Some(first) = iter.next() { if first.flatten.as_ref().map(|f| f.value).unwrap_or(false) { return Err(syn::Error::new( span, "`transparent` struct field cannot be `flat`", )); } else if first.append.map(|v| *v).unwrap_or(false) { return Err(syn::Error::new( span, "`transparent` struct field cannot be `append`", )); } else if iter.next().is_none() { return <_>::parse_meta_item_named(input, path, span); } } Err(syn::Error::new( span, "`transparent` struct must have only one parseable field", )) }, ); } "allow_unknown_fields" => { if matches!(struct_.fields, syn::Fields::Unnamed(_)) { errors.push( span, "`allow_unknown_fields` not allowed on tuple struct", ); parse_helpers::skip_meta_item(input); } else { allow_unknown_fields.parse_named_item( "allow_unknown_fields", input, span, &errors, ); } } "default" => { if matches!(struct_.fields, syn::Fields::Unit) { errors.push(span, "`default` not allowed on unit struct"); parse_helpers::skip_meta_item(input); } else { default.parse_named_item("default", input, span, &errors); } } "crate" => crate_.parse_named_item("crate", input, span, &errors), "and_then" => { match errors.push_result(<_>::parse_meta_item_named(input, path, span)) { Some(e) => and_thens.push(e), None => parse_helpers::skip_meta_item(input), } } "attributes" => { match errors .push_result(mod_path_vec::parse_meta_item_named(input, path, span)) { Some(attrs) => attributes.extend(attrs.into_iter()), None => parse_helpers::skip_meta_item(input), } } _ => { parse_helpers::check_unknown_attribute( path, span, Self::field_names(), &errors, ); parse_helpers::skip_meta_item(input); } } Ok(()) })); let fields = { let mut fields = fields; if default.is_none() { for field in &mut fields { if let Some(span) = field.skip.and_then(|skip| (*skip).then_some(skip.span())) { if field.default.is_none() { field.default = Some(FieldDefault::Default(span)); } } } } fields }; let mut all_idents = HashSet::new(); let mut container = None; for field in &fields { for ident in &field.idents { if all_idents.contains(ident) { errors.push_spanned( ident, format_args!("duplicate field name for `{ident}`"), ); } else { all_idents.insert(ident.clone()); } } if let Some(c) = field.container.as_ref() { if container.is_some() { errors.push(c.span(), "Duplicate `container` field") } else { container = Some(c); } } } if matches!(struct_.fields, syn::Fields::Unnamed(_)) { let mut has_default_gap = false; for field in fields.iter().rev() { if field.is_parsable() && !field.is_flat() { if let Some(default) = &field.default { if has_default_gap { errors.push( default.span(), "`default` fields can only be at the end of a tuple struct", ); } } else { has_default_gap = true; } } } } errors.check()?; Ok(Self { struct_, fields, default: default.into(), crate_: crate_.into(), transparent: transparent.into(), allow_unknown_fields: allow_unknown_fields.into(), attributes, and_thens, }) }, ) } } deluxe-macros-0.5.0/types/variant.rs000064400000000000000000000716271046102023000155710ustar 00000000000000use super::*; use deluxe_core::{ parse_helpers::{self, FieldStatus}, ParseAttributes, ParseMetaItem, Result, SpannedValue, }; use proc_macro2::{Span, TokenStream}; use quote::quote_spanned; use std::collections::{BTreeMap, BTreeSet}; use syn::spanned::Spanned; pub struct Variant<'v> { pub variant: &'v syn::Variant, pub fields: Vec>, pub idents: Vec, pub flatten: Option, pub transparent: Option, pub skip: Option>, pub allow_unknown_fields: Option, } impl<'v> Variant<'v> { #[inline] pub fn field_names() -> &'static [&'static str] { &[ "skip", "rename", "flatten", "alias", "transparent", "allow_unknown_fields", ] } #[inline] pub fn is_skipped(&self) -> bool { self.skip.map(|v| *v).unwrap_or(false) } pub(super) fn field_key(&self) -> BTreeSet> { self.fields .iter() .filter_map(|f| { let idents = f .idents .iter() .filter_map(|i| { if f.default.is_some() || f.is_flat() || !f.is_parsable() { return None; } Some(i.to_string()) }) .collect::>(); (!idents.is_empty()).then_some(idents) }) .collect() } fn to_field_parsing_tokens( &self, crate_: &syn::Path, priv_: &syn::Path, mode: TokenMode, flat: Option, ) -> TokenStream { let allow_unknown_fields = self.allow_unknown_fields.unwrap_or(false); let mut field_data = FieldData { mode, target: ParseTarget::Init(Some(&self.variant.ident)), inline_expr: &parse_quote_mixed! { inline(&[input], _mode) }, allowed_expr: &parse_quote_mixed! { allowed }, transparent: self.transparent.unwrap_or(false), variant: true, allow_unknown_fields, }; let (pre, post) = Field::to_pre_post_tokens(&self.fields, &self.variant.fields, crate_, &field_data); if field_data.transparent { let name = self .fields .iter() .enumerate() .find_map(|(i, f)| { (f.is_parsable() && !f.is_flat()) .then(|| quote::format_ident!("field{i}", span = Span::mixed_site())) }) .unwrap(); return quote_mixed! { #pre if let #priv_::Option::Some(v) = errors.push_result( #crate_::ParseMetaItem::parse_meta_item_named(input, p, span).and_then(|v| { #name = #priv_::FieldStatus::Some(v); #post }) ) { if !value.is_some() { value = #priv_::FieldStatus::Some(v); } } else { #priv_::parse_helpers::skip_meta_item(input); if value.is_none() { value = #priv_::FieldStatus::ParseError; } } }; } if flat.is_none() { field_data.mode = TokenMode::ParseMetaItem; } let ItemDef { parse, inline, flag, .. } = Field::to_parsing_tokens( &self.fields, &self.variant.fields, crate_, (&pre, &post), field_data, ); let pre = match &self.variant.fields { syn::Fields::Named(_) => { let field_names = Field::to_field_names_tokens(&self.fields, crate_, priv_); let accepts_all = if allow_unknown_fields { quote_mixed! { true } } else { Field::to_accepts_all_tokens(&self.fields, crate_) .unwrap_or_else(|| quote_mixed! { false }) }; Some(quote_mixed! { let allowed = #field_names; let validate = !(#accepts_all); }) } syn::Fields::Unnamed(_) => Some(quote_mixed! { let mut index = 0usize; }), _ => None, }; if let Some(flat) = flat { // unknown fields in an empty key really means the variant is unknown, // so error out early and don't continue parsing let validate_empty = match flat { FlatMode::Empty { all_keys } if !allow_unknown_fields => Some(quote_mixed! { if validate { if !errors.is_empty() { return #crate_::Result::Err(errors.check().unwrap_err()); } let _ = #priv_::parse_helpers::parse_struct( &#priv_::parse_helpers::fork_inputs(inputs), |input, p, span| { #priv_::parse_helpers::check_unknown_attribute( p, span, allowed, &errors, ); #priv_::parse_helpers::skip_meta_item(input); #crate_::Result::Ok(()) }, ); if !errors.is_empty() { #priv_::parse_helpers::variant_required( span, prefix, &#all_keys, &errors ); return #crate_::Result::Err(errors.check().unwrap_err()); } } }), _ => None, }; let unset_validate = validate_empty.as_ref().map(|_| { quote_mixed! { let validate = false; } }); quote_mixed! { #pre #validate_empty let ret = (|| { #unset_validate #inline })(); if let #priv_::Option::Some(v) = errors.push_result(ret) { value = #priv_::FieldStatus::Some(v); } else if value.is_none() { value = #priv_::FieldStatus::ParseError; } } } else { let flag = flag.unwrap_or_else(|| { quote_mixed! { #crate_::Result::Err(#priv_::parse_helpers::flag_disallowed_error(span)) } }); quote_mixed! { #pre let mut inline = |inputs: &[#priv_::ParseStream<'_>], _mode: #crate_::ParseMode| { #inline }; let res = match #priv_::parse_helpers::try_parse_named_meta_item(input) { #crate_::Result::Ok(#priv_::parse_helpers::NamedParse::Equals) => { let _mode = #crate_::ParseMode::Named(span); #parse }, #crate_::Result::Ok(#priv_::parse_helpers::NamedParse::Paren(buffer)) => { inline(&[&buffer], #crate_::ParseMode::Named(span)) .and_then(|v| { #priv_::parse_helpers::parse_eof_or_trailing_comma(&buffer)?; #crate_::Result::Ok(v) }) }, #crate_::Result::Ok(#priv_::parse_helpers::NamedParse::Flag) => { #flag }, #crate_::Result::Err(e) => #crate_::Result::Err(e), }; if let #priv_::Option::Some(v) = errors.push_result(res) { value = #priv_::FieldStatus::Some(v); } else { #priv_::parse_helpers::skip_meta_item(input); if value.is_none() { value = #priv_::FieldStatus::ParseError; } } } } } fn all_variant_key_messages( variants: &[Self], all_keys: &[Option>>], ) -> TokenStream { let msgs = variants .iter() .filter(|v| !v.is_skipped()) .enumerate() .map(|(i, v)| { if v.flatten.unwrap_or(false) { let keys = all_keys[i].as_ref().unwrap(); if keys.is_empty() { let ident = &v.variant.ident; let msg = format!("fields from `{ident}`"); quote_mixed! { &[&[#msg]] } } else { let key_exprs = keys.iter().map(|idents| { let idents = idents.iter().map(|i| format!("`{i}`")); quote_mixed! { &[#(#idents),*] } }); quote_mixed! { &[#(#key_exprs),*] } } } else { let ident = v.idents.first().map(|i| format!("`{i}`")); quote_mixed! { &[&[#ident]] } } }); quote_mixed! { [#(#msgs,)*] } } pub(super) fn to_parsing_tokens( variants: &[Self], crate_: &syn::Path, mode: TokenMode, target: Option<&TokenStream>, and_thens: &[TokenStream], allow_unknown_fields: bool, ) -> TokenStream { let priv_path: syn::Path = syn::parse_quote! { #crate_::____private }; let priv_ = &priv_path; let variant_matches = variants.iter().filter_map(|v| { if v.flatten.unwrap_or(false) || v.is_skipped() { return None; } let idents = v.idents.iter().map(|i| i.to_string()).collect::>(); let parse = v.to_field_parsing_tokens(crate_, priv_, mode, None); Some(quote_mixed! { #(#priv_::Option::Some(k @ #idents))|* => { if let #priv_::Option::Some(key) = key { #priv_::parse_helpers::only_one_variant( span, prefix, (key, k), &errors ); } if key.is_none() { match k { #(#idents => { key = #priv_::Option::Some(#idents); })* _ => {} } } #parse }, }) }); let any_flat = variants .iter() .any(|v| v.flatten.unwrap_or(false) && !v.is_skipped()); let paths_ident = any_flat.then(|| syn::Ident::new("paths", Span::mixed_site())); let paths_ident = paths_ident.as_ref().into_iter().collect::>(); let all_flat_keys = variants .iter() .filter(|v| !v.is_skipped()) .map(|v| v.flatten.unwrap_or(false).then(|| v.field_key())) .collect::>(); let mut flat_matches = variants .iter() .filter(|v| !v.is_skipped()) .enumerate() .filter_map(|(i, v)| { if !v.flatten.unwrap_or(false) { return None; } let key = all_flat_keys[i].as_ref().unwrap(); let paths_key = key.iter().map(|idents| { quote_mixed! { &[#(#idents),*] } }); let cond = (!key.is_empty()).then(|| { quote_mixed! { if #priv_::parse_helpers::has_paths(&paths, &[#(#paths_key),*]) } }); let flat_mode = if key.is_empty() { let all_keys = Self::all_variant_key_messages(variants, &all_flat_keys); FlatMode::Empty { all_keys } } else { FlatMode::Tagged }; let parse = v.to_field_parsing_tokens(crate_, priv_, mode, Some(flat_mode)); let disallow_paths = paths_ident.iter().filter_map(|paths_ident| { if allow_unknown_fields || v.allow_unknown_fields.unwrap_or(false) { return None; } let ident = v.variant.ident.to_string(); let cur_flat_names = v.to_flat_field_names_tokens(crate_); let other_flat_names = variants.iter().flat_map(|ov| { if std::ptr::eq(ov, v) { return Vec::new().into_iter(); } ov.to_flat_field_names_tokens(crate_).into_iter() }); // only check these if the parent is validating, because it cannot know which // individual flattened fields are allowed Some(quote_mixed! { if !validate { #priv_::parse_helpers::remove_paths( &mut #paths_ident, &[#(#cur_flat_names),*], ); #priv_::parse_helpers::disallow_paths( &#paths_ident, &[#(#other_flat_names),*], #ident, &errors, ); } }) }); Some(( key, quote_mixed! { #cond { #parse #(#disallow_paths)* } }, )) }) .collect::>(); let validate = (!allow_unknown_fields).then(|| { quote_mixed! { if validate { let _ = #priv_::parse_helpers::parse_struct(inputs, |input, p, span| { #priv_::parse_helpers::check_unknown_attribute(p, span, allowed, &errors); #priv_::parse_helpers::skip_meta_item(input); #crate_::Result::Ok(()) }); } } }); let inputs_expr = (any_flat || validate.is_some()) .then(|| { quote_mixed! { &#priv_::parse_helpers::fork_inputs(inputs) } }) .unwrap_or_else(|| { quote_mixed! { inputs } }); let empty_match = flat_matches.remove(&BTreeSet::new()).unwrap_or_else(|| { if let Some(target) = target { return quote_mixed! { { value = #priv_::FieldStatus::Some((#target)); } }; } let all_keys = Self::all_variant_key_messages(variants, &all_flat_keys); quote_mixed! { { #validate #priv_::parse_helpers::variant_required( span, prefix, &#all_keys, &errors ); } } }); let disallow_flats = paths_ident.iter().filter_map(|paths_ident| { if allow_unknown_fields { return None; } let flat_names = variants .iter() .flat_map(|v| v.to_flat_field_names_tokens(crate_).into_iter()); Some(quote_mixed! { if !validate { #paths_ident.remove(key.unwrap()); #priv_::parse_helpers::disallow_paths( &#paths_ident, &[#(#flat_names),*], key.unwrap(), &errors, ); } }) }); let flat_matches = flat_matches.values(); quote_mixed! { let mut key: #priv_::Option<&'static #priv_::str> = #priv_::Option::None; let mut value = #priv_::FieldStatus::None; #(let mut #paths_ident = #priv_::HashMap::<#priv_::SmallString<'static>, #priv_::Span>::new();)* let errors = #crate_::Errors::new(); errors.push_result(#priv_::parse_helpers::parse_struct(#inputs_expr, |input, p, span| { let inputs = [input]; let inputs = inputs.as_slice(); let cur = p.strip_prefix(prefix); #(if let #priv_::Option::Some(cur) = cur { #paths_ident.insert(<#priv_::SmallString as #priv_::From<_>>::from(cur).into_owned(), span); })* match cur { #(#variant_matches)* _ => { #priv_::parse_helpers::skip_meta_item(input); } } #crate_::Result::Ok(()) })); if value.is_some() { #(#disallow_flats)* #validate } else if value.is_none() { #(#flat_matches else)* #empty_match } let value = value.into_option(); #(let value = value.and_then(|v| { let f = #and_thens; errors.push_result(f(v)) });)* #priv_::parse_helpers::skip_all(inputs); errors.check()?; #crate_::Result::Ok(value.unwrap_or_else(|| #priv_::unreachable!())) } } pub fn to_flat_field_names_tokens(&self, crate_: &syn::Path) -> Vec { if !self.flatten.unwrap_or(false) { return Vec::new(); } self.fields .iter() .map(|field| match &field.flatten { Some(FieldFlatten { value: true, prefix, .. }) => { let ty = &field.field.ty; let names = quote_spanned! { ty.span() => <#ty as #crate_::ParseMetaFlatNamed>::field_names() }; let prefix = prefix .as_ref() .map(parse_helpers::key_to_string) .unwrap_or_default(); quote_mixed! { (#prefix, #names) } } _ => { let idents = field.idents.iter().map(|i| i.to_string()); quote_mixed! { ("", &[#(#idents),*]) } } }) .collect() } } impl<'v> ParseAttributes<'v, syn::Variant> for Variant<'v> { #[inline] fn path_matches(path: &syn::Path) -> bool { path.is_ident("deluxe") } #[inline] fn parse_attributes(variant: &'v syn::Variant) -> Result { parse_helpers::parse_struct_attr_tokens( parse_helpers::ref_tokens::(variant), |inputs, _| { let errors = crate::Errors::new(); let mut alias_span = FieldStatus::None; let mut idents = Vec::new(); let mut flatten = FieldStatus::>>::None; let mut rename = FieldStatus::None; let mut transparent = FieldStatus::>>::None; let mut skip = FieldStatus::None; let mut allow_unknown_fields = FieldStatus::None; let fields = variant .fields .iter() .filter_map(|f| errors.push_result(Field::parse_attributes(f))) .collect::>(); errors.push_result(parse_helpers::parse_struct(inputs, |input, path, span| { match path { "transparent" => { transparent.parse_named_item_with( "transparent", input, span, &errors, |input, _, span| { let mut iter = fields.iter().filter(|f| f.is_parsable()); if let Some(first) = iter.next() { if first.flatten.as_ref().map(|f| f.value).unwrap_or(false) { return Err(syn::Error::new( span, "`transparent` variant field cannot be `flat`", )); } else if first.append.map(|v| *v).unwrap_or(false) { return Err(syn::Error::new( span, "`transparent` variant field cannot be `append`", )); } else if iter.next().is_none() { return <_>::parse_meta_item_named(input, path, span); } } Err(syn::Error::new( span, "`transparent` variant must have only one parseable field", )) }, ); } "allow_unknown_fields" => { if matches!(variant.fields, syn::Fields::Unnamed(_)) { errors.push( span, "`allow_unknown_fields` not allowed on tuple variant", ); parse_helpers::skip_meta_item(input); } else { allow_unknown_fields.parse_named_item( "allow_unknown_fields", input, span, &errors, ); } } "flatten" => flatten.parse_named_item("flatten", input, span, &errors), "rename" => { rename.parse_named_item_with( "rename", input, span, &errors, |input, _, span| { let name = <_>::parse_meta_item_named(input, path, span)?; if variant.ident == name { Err(syn::Error::new( span, "cannot rename variant to its own name", )) } else if idents.contains(&name) { Err(syn::Error::new( span, format_args!("alias already given for `{name}`"), )) } else { idents.insert(0, name); Ok(span) } }, ); } "alias" => { match errors.push_result(<_>::parse_meta_item_named(input, path, span)) { Some(alias) => { if variant.ident == alias { errors.push(span, "cannot alias variant to its own name"); } else if idents.contains(&alias) { errors.push( span, format_args!("duplicate alias for `{alias}`"), ); } else { idents.push(alias); if alias_span.is_none() { alias_span = FieldStatus::Some(span); } } } None => parse_helpers::skip_meta_item(input), } } "skip" => skip.parse_named_item("skip", input, span, &errors), _ => { parse_helpers::check_unknown_attribute( path, span, Self::field_names(), &errors, ); parse_helpers::skip_meta_item(input); } } Ok(()) })); let transparent = transparent.flatten(); let flatten = flatten.flatten(); deluxe_core::only_one!("", &errors, flatten, rename); deluxe_core::only_one!("", &errors, flatten, ("alias", alias_span.as_ref())); if rename.is_none() && !flatten.map(|v| *v).unwrap_or(false) { let ident = heck::ToSnakeCase::to_snake_case( syn::ext::IdentExt::unraw(&variant.ident) .to_string() .as_str(), ); idents.insert(0, syn::Ident::new(&ident, variant.ident.span())); } let fields = { let mut fields = fields; for field in &mut fields { if let Some(span) = field.skip.and_then(|skip| (*skip).then_some(skip.span())) { if field.default.is_none() { field.default = Some(FieldDefault::Default(span)); } } } fields }; let mut container = None; for field in &fields { if let Some(c) = field.container.as_ref() { if container.is_some() { errors.push(c.span(), "Duplicate `container` field") } else { container = Some(c); } } } if matches!(variant.fields, syn::Fields::Unnamed(_)) { let mut has_default_gap = false; for field in fields.iter().rev() { if field.is_parsable() && !field.is_flat() { if let Some(default) = &field.default { if has_default_gap { errors.push( default.span(), "`default` fields can only be at the end of a tuple variant", ); } } else { has_default_gap = true; } } } } errors.check()?; Ok(Self { variant, fields, idents: idents.into_iter().collect(), flatten: flatten.map(|v| *v).into(), transparent: transparent.map(|v| *v).into(), skip: skip.into(), allow_unknown_fields: allow_unknown_fields.into(), }) }, ) } } pub enum FlatMode { Tagged, Empty { all_keys: TokenStream }, } deluxe-macros-0.5.0/types.rs000064400000000000000000000002001046102023000141000ustar 00000000000000mod field; pub use field::*; mod r#struct; pub use r#struct::*; mod variant; pub use variant::*; mod r#enum; pub use r#enum::*; deluxe-macros-0.5.0/util.rs000064400000000000000000000025061046102023000137240ustar 00000000000000use syn::parse::{Parse, Parser}; use deluxe_core::Errors; #[inline] pub fn parse(input: proc_macro::TokenStream, errors: &Errors) -> Option { errors.push_result(::parse.parse(input)) } fn crate_path(errors: Option<&Errors>) -> Option { use proc_macro_crate::FoundCrate; const CRATE_NAME: &str = "deluxe"; let crate_name = match proc_macro_crate::crate_name(CRATE_NAME) { Ok(FoundCrate::Name(name)) => name, Ok(FoundCrate::Itself) => CRATE_NAME.into(), Err(e) => { if let Some(errors) = errors { errors.push(proc_macro2::Span::call_site(), e.to_string()); } return None; } }; let ident = syn::Ident::new(&crate_name, proc_macro2::Span::call_site()); Some(syn::parse_quote! { ::#ident }) } #[inline] pub fn get_crate_path(path: Option>, errors: &Errors) -> Option { match path { Some(Some(path)) => Some(path), Some(None) => crate_path(Some(errors)), None => crate_path(None), } } macro_rules! quote_mixed { ($($tt:tt)*) => { quote::quote_spanned! { Span::mixed_site() => $($tt)* } }; } macro_rules! parse_quote_mixed { ($($tt:tt)*) => { syn::parse_quote_spanned! { Span::mixed_site() => $($tt)* } }; }