attribute-derive-macro-0.10.5/.cargo_vcs_info.json0000644000000001430000000000100154730ustar { "git": { "sha1": "b676fb397927bb1bad94b095feac5515c9cf52a3" }, "path_in_vcs": "macro" }attribute-derive-macro-0.10.5/Cargo.lock0000644000000071070000000000100134550ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "attribute-derive-macro" version = "0.10.5" dependencies = [ "collection_literals", "interpolator", "manyhow", "proc-macro-utils 0.10.0", "proc-macro2", "quote", "quote-use", "syn", ] [[package]] name = "collection_literals" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" [[package]] name = "derive-where" version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "interpolator" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" [[package]] name = "manyhow" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5b8ea82a2287fe7b26aea89c0c02957886d7e97eabffc1f9d2031feaa6f82e6" dependencies = [ "manyhow-macros", "proc-macro2", "quote", "syn", ] [[package]] name = "manyhow-macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae36e8d9b4095531e43de72ed424f0f4a98cba40f7e5a99366f9818769489272" dependencies = [ "proc-macro-utils 0.8.0", "proc-macro2", "quote", ] [[package]] name = "proc-macro-utils" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" dependencies = [ "proc-macro2", "quote", "smallvec", ] [[package]] name = "proc-macro-utils" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" dependencies = [ "proc-macro2", "quote", "smallvec", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "quote-use" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e96ac59974192a2fa6ee55a41211cf1385c5b2a8636a4c3068b3b3dd599ece" dependencies = [ "quote", "quote-use-macros", ] [[package]] name = "quote-use-macros" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c57308e9dde4d7be9af804f6deeaa9951e1de1d5ffce6142eb964750109f7e" dependencies = [ "derive-where", "proc-macro-utils 0.8.0", "proc-macro2", "quote", "syn", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "syn" version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" attribute-derive-macro-0.10.5/Cargo.toml0000644000000030240000000000100134720ustar # 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 = "attribute-derive-macro" version = "0.10.5" build = false include = [ "src/**/*", "LICENSE", "README.md", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Clap for proc macro attributes" documentation = "https://docs.rs/attribute-derive" readme = "README.md" keywords = [ "derive", "macro", ] categories = [ "rust-patterns", "development-tools::procedural-macro-helpers", "parsing", ] license = "MIT" repository = "https://github.com/ModProg/attribute-derive" [package.metadata.release] tag = false shared-version = true [lib] name = "attribute_derive_macro" path = "src/lib.rs" proc-macro = true [dependencies.collection_literals] version = "1" [dependencies.interpolator] version = "0.5.0" features = ["iter"] [dependencies.manyhow] version = "0.11" [dependencies.proc-macro-utils] version = "0.10.0" [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.quote-use] version = "0.8" [dependencies.syn] version = "2" attribute-derive-macro-0.10.5/Cargo.toml.orig000064400000000000000000000015611046102023000171570ustar 00000000000000[package] categories = ["rust-patterns", "development-tools::procedural-macro-helpers", "parsing"] description = "Clap for proc macro attributes" documentation = "https://docs.rs/attribute-derive" include = ["src/**/*", "LICENSE", "README.md"] keywords = ["derive", "macro"] license = "MIT" readme = "README.md" repository = "https://github.com/ModProg/attribute-derive" version = "0.10.5" edition = "2021" name = "attribute-derive-macro" [lib] proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] proc-macro2 = "1" quote = "1" quote-use = "0.8" syn = "2" proc-macro-utils = "0.10.0" # proc-macro-utils = {path = "../../proc-macro-utils"} interpolator = { version = "0.5.0", features = ["iter"] } collection_literals = "1" manyhow = "0.11" [package.metadata.release] tag = false shared-version = true attribute-derive-macro-0.10.5/LICENSE000064400000000000000000000020631046102023000152730ustar 00000000000000MIT License Copyright (c) 2022 Roland Fredenhagen 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. attribute-derive-macro-0.10.5/README.md000064400000000000000000000001131046102023000155370ustar 00000000000000proc-macro crate for [`attribute-derive`](https://lib.rs/attribute-derive) attribute-derive-macro-0.10.5/src/lib.rs000064400000000000000000001116571046102023000162030ustar 00000000000000use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::fmt::Display; use std::iter; use std::ops::Range; use collection_literals::hash; use interpolator::{format, Formattable}; use manyhow::{bail, error_message, manyhow, span_range, ErrorMessage, Result}; use proc_macro2::{Literal, Span, TokenStream}; use proc_macro_utils::{TokenParser, TokenStream2Ext}; use quote::{format_ident, ToTokens}; use quote_use::quote_use as quote; use syn::spanned::Spanned; use syn::{DataStruct, DeriveInput, Field, Fields, Generics, Ident, LitStr, Type, Visibility}; const ATTRIBUTE_IDENTS: &[&str] = &["from_attr", "attribute", "attr"]; #[allow(clippy::large_enum_variant)] enum StructError { Generic(FormatString), Specific(StructErrorSpecific), } struct FormatString { format: Cow<'static, str>, span: Span, default: bool, } impl>> From for FormatString { fn from(value: T) -> Self { Self { format: value.into(), span: Span::call_site(), default: true, } } } impl FormatString { fn format(&self, values: HashMap<&str, Formattable>) -> Result { Ok(LitStr::new( &format(&self.format, &values).map_err(|e| error_message!(self.span, "{e}"))?, self.span, )) } fn parse(parser: &mut TokenParser) -> Option { Some(Self { span: parser.span(), format: parser.next_string()?.into(), default: false, }) } } impl StructError { fn duplicate_field(&self) -> &FormatString { match self { StructError::Generic(g) => g, StructError::Specific(s) => &s.duplicate_field, } } fn missing_field(&self) -> &FormatString { match self { StructError::Generic(g) => g, StructError::Specific(s) => &s.missing_field, } } fn field_help(&self) -> Option<&FormatString> { if let StructError::Specific(s) = self { Some(&s.field_help) } else { None } } fn conflict(&self) -> &FormatString { match self { StructError::Generic(g) => g, StructError::Specific(s) => &s.conflict, } } fn unknown_field(&self) -> &FormatString { match self { StructError::Generic(g) => g, StructError::Specific(s) => &s.unknown_field, } } fn unknown_field_single(&self) -> &FormatString { match self { StructError::Generic(g) => g, StructError::Specific(s) => &s.unknown_field_single, } } fn unknown_field_empty(&self) -> &FormatString { match self { StructError::Generic(g) => g, StructError::Specific(s) => &s.unknown_field_empty, } } fn unknown_field_error(&self, fields: &[AttrField]) -> Result { let fields: Vec<_> = fields .iter() .filter(|field| !field.positional) .map(|field| Formattable::display(&field.ident)) .collect(); match fields.len() { 0 => self.unknown_field_empty().format(HashMap::new()), 1 => self .unknown_field_single() .format(hash!("expected_field" => fields[0])), _ => self .unknown_field() .format(hash!("expected_fields" => Formattable::iter(&fields))), } } } struct StructErrorSpecific { /// `expected_fields` unknown_field: FormatString, /// `expected_field` unknown_field_single: FormatString, unknown_field_empty: FormatString, /// `field` duplicate_field: FormatString, /// `field` missing_field: FormatString, /// `field`, `attribute`, `example` field_help: FormatString, /// `first`, `second` conflict: FormatString, } impl Default for StructErrorSpecific { fn default() -> Self { Self { unknown_field_empty: "expected empty attribute".into(), unknown_field_single: "expected supported field `{expected_field}`".into(), unknown_field: "supported fields are {expected_fields:i..-1(`{}`)(, )} and \ `{expected_fields:i-1}`" .into(), duplicate_field: "`{field}` is specified multiple times".into(), missing_field: "required `{field}` is not specified".into(), field_help: "try `#[{attribute}({field}{open_or_eq}{example}{close_or_empty})]`".into(), conflict: "`{first}` conflicts with mutually exclusive `{second}`".into(), } } } // enum DuplicateStrategy { // AggregateOrError, // Error, // AggregateOrOverride, // Override, // } struct StructAttrs { ident: Option, aliases: Vec, error: StructError, // duplicate: DuplicateStrategy, } impl StructAttrs { fn from_attrs( struct_ident: &Ident, attrs: impl IntoIterator, ) -> Result { const VALID_FORMAT: &str = r#"expected `#[attribute(ident=attribute_name/!ident, aliases=[alias1, alias2], error="..", error(unknown_field="..", unknown_field_single="..", unknown_field_empty="..", duplicate_field="..", missing_field="..", field_help=".."))]`"#; let mut ident_span: Option> = None; let mut ident: Option = None; let mut aliases: Vec = vec![]; let mut error = StructError::Specific(Default::default()); // let mut duplicate: DuplicateStrategy = DuplicateStrategy::AggregateOrError; for attr in attrs .into_iter() .filter(|attr| ATTRIBUTE_IDENTS.iter().any(|a| attr.path().is_ident(a))) { let parser = &mut attr .meta .require_list() .map_err(|_| ErrorMessage::call_site(VALID_FORMAT))? .tokens .clone() .parser(); while !parser.is_empty() { if let Some(not) = parser.next_tt_not() { if let Some(kw) = parser.next_keyword("ident") { if let Some(ident_span) = ident_span { bail!( error_message!(ident_span, "ident is specified twice") + error_message!( not.span()..kw.span(), "ident was already specified" ) ) } else { ident_span = Some(not.span()..kw.span()); } } } else { let field = parser .next_ident() .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?; match field.to_string().as_str() { "ident" => { parser .next_tt_eq() .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?; ident = Some( parser .next_ident() .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?, ); if let Some(ident_span) = ident_span { bail!( error_message!(ident_span, "ident is specified twice") + error_message!( field.span()..ident.unwrap().span(), "ident was already specified" ) ) } } "aliases" => { parser .next_tt_eq() .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?; let mut parser = parser .next_bracketed() .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))? .stream() .parser(); aliases.extend(iter::from_fn(|| { _ = parser.next_tt_comma(); parser.next_ident().map(|t| t.to_string()) })); if !parser.is_empty() { bail!("{VALID_FORMAT}") } } "error" => { if parser.next_tt_eq().is_some() { error = StructError::Generic( FormatString::parse(parser) .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?, ); } else { let parser = &mut parser .next_parenthesized() .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))? .stream() .parser(); let error = if let StructError::Specific(error) = &mut error { error } else { error = StructError::Specific(Default::default()); if let StructError::Specific(error) = &mut error { error } else { unreachable!() } }; while !parser.is_empty() { let field = parser .next_ident() .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?; let mut string = |f: &mut _| -> Result<()> { parser .next_tt_eq() .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?; *f = FormatString::parse(parser) .ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?; Ok(()) }; match field.to_string().as_str() { "unknown_field" => string(&mut error.unknown_field), "unknown_field_empty" => { string(&mut error.unknown_field_empty) } "unknown_field_single" => { string(&mut error.unknown_field_single) } "duplicate_field" => string(&mut error.duplicate_field), "missing_field" => string(&mut error.missing_field), "field_help" => string(&mut error.field_help), "conflict" => string(&mut error.conflict), _ => bail!(field, "{VALID_FORMAT}"), }?; _ = parser.next_tt_comma(); } } } // "duplicate" => { // parser.next_eq() .ok_or_else(|| // ErrorMessage::call_site(VALID_FORMAT))?; let strategy // = parser.next_ident() .ok_or_else(|| // ErrorMessage::call_site(VALID_FORMAT))?; duplicate = // match strategy.to_string().as_str() { // "AggregateOrError" => DuplicateStrategy::AggregateOrError, // "Error" => DuplicateStrategy::Error, // "AggregateOrOverride" => DuplicateStrategy::AggregateOrOverride, // "Override" => DuplicateStrategy::Override, // _ => abort!(strategy, VALID_FORMAT), // } // } _ => bail!(field, "{VALID_FORMAT}"), } } _ = parser.next_tt_comma(); } } if ident_span.is_none() && ident.is_none() { let ident_string = struct_ident.to_string(); let mut out = String::with_capacity(ident_string.len()); let mut ident_string = ident_string.chars(); out.extend(ident_string.next().into_iter().flat_map(char::to_lowercase)); for c in ident_string { if c.is_uppercase() { out.push('_'); out.extend(c.to_lowercase()); } else { out.push(c); } } ident = Some(Ident::new(&out, struct_ident.span())); } Ok(Self { ident, aliases, error, // duplicate, }) } } struct FieldAttrs { optional: bool, default: Option, // aggregate: bool, conflicts: Vec, example: Option, positional: bool, } impl FieldAttrs { fn from_attrs(attrs: impl IntoIterator) -> Result { const VALID_FORMAT: &str = r#"expected `#[attribute(optional, positional, default=1+5, conflicts=[a, b], example="22")]`"#; let mut optional = false; let mut default: Option = None; // let mut aggregate: bool = true; let mut conflicts: Vec = Vec::new(); let mut example: Option = None; let mut positional = false; for attr in attrs .into_iter() .filter(|attr| ATTRIBUTE_IDENTS.iter().any(|a| attr.path().is_ident(a))) { let mut parser = attr .meta .require_list() .map_err(|e| error_message!(e.span(), "{VALID_FORMAT}"))? .tokens .clone() .parser(); while !parser.is_empty() { let field = parser .next_ident() .ok_or_else(|| error_message!(parser.next(), "{VALID_FORMAT}"))?; match field.to_string().as_str() { "optional" => optional = true, "positional" => positional = true, "default" => { parser .next_tt_eq() .ok_or_else(|| error_message!(parser.next(), "{VALID_FORMAT}"))?; default = Some( parser .next_expression() .ok_or_else(|| error_message!(parser.next(), "{VALID_FORMAT}"))?, ); optional = true; } "example" => { parser .next_tt_eq() .ok_or_else(|| error_message!(parser.next(), "{VALID_FORMAT}"))?; // panic!("{:?}", parser.next_string()); example = Some( parser .next_string() .ok_or_else(|| error_message!(parser.next(), "{VALID_FORMAT}"))?, ); } // "aggregate" => { // parser.next_eq() .ok_or_else(|| // error_message!(parser.next(), "{VALID_FORMAT}"))?; aggregate &= // parser.next_bool() .ok_or_else(|| // error_message!(parser.next(), "{VALID_FORMAT}"))?; } "conflicts" => { parser .next_tt_eq() .ok_or_else(|| error_message!(parser.next(), "{VALID_FORMAT}"))?; let mut parser = parser .next_bracketed() .ok_or_else(|| error_message!(parser.next(), "{VALID_FORMAT}"))? .stream() .parser(); conflicts.extend(iter::from_fn(|| { let ident = parser.next_ident(); _ = parser.next_tt_comma(); ident })); if !parser.is_empty() { bail!(parser.next(), "{VALID_FORMAT}") } } _ => bail!(field, "{VALID_FORMAT}"), } _ = parser.next_tt_comma(); } } Ok(Self { optional, default, // aggregate, conflicts, example, positional, }) } } // TODO generally should use fully qualified names for trait function calls #[derive(Default)] struct Conflicts(HashSet<(Ident, Ident)>); impl Conflicts { fn insert(&mut self, a: Ident, b: Ident) { if a < b { self.0.insert((a, b)); } else { self.0.insert((b, a)); } } fn to_tokens(&self, struct_error: &StructError) -> Result { let mut conflicts = self.0.iter().collect::>(); // The order must be deterministic to be friendly to tools like sccache conflicts.sort(); conflicts .iter() .map(|(a, b)| { let af = Formattable::display(&a); let bf = Formattable::display(&b); let error_a_to_b = struct_error .conflict() .format(hash!("first" => af, "second" => bf))?; let error_b_to_a = struct_error .conflict() .format(hash!("first" => bf, "second" => af))?; Ok(quote! { # use ::attribute_derive::__private::proc_macro2; # use ::attribute_derive::__private::syn::{Error, Spanned}; if let (Some(__a), Some(__b)) = (&__partial.#a, &__partial.#b) { if let Some(__joined_span) = __a.error_span().join(__b.error_span()) { return Err(Error::new(__joined_span, #error_a_to_b)); } else { let mut __error = Error::new(__a.error_span(), #error_a_to_b); __error.combine(Error::new(__b.error_span(), #error_b_to_a)); return Err(__error); } } }) }) .collect() } } enum IdentOrIdx { Ident(Ident), Idx(usize), } impl Display for IdentOrIdx { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { IdentOrIdx::Ident(i) => write!(f, "{i}"), IdentOrIdx::Idx(i) => write!(f, "{i}"), } } } impl ToTokens for IdentOrIdx { fn to_tokens(&self, tokens: &mut TokenStream) { match self { IdentOrIdx::Ident(i) => i.to_tokens(tokens), IdentOrIdx::Idx(i) => Literal::usize_unsuffixed(*i).to_tokens(tokens), } } } struct AttrField { ident: IdentOrIdx, duplicate: LitStr, help: Option, missing: String, default: Option, ty: Type, positional: bool, } impl AttrField { fn parse_fields( input: impl IntoIterator, struct_error: &StructError, attribute_ident: Option, ) -> Result<(Vec, Conflicts)> { let mut conflicts = Conflicts::default(); Ok(( input .into_iter() .enumerate() .map( |( idx, Field { attrs, ident, ty, .. }, )| { let field_name = ident .as_ref() .map_or_else(|| format!("positional_{idx}"), ToString::to_string); let FieldAttrs { optional, default, conflicts: field_conflicts, example, mut positional, } = FieldAttrs::from_attrs(attrs)?; positional |= ident.is_none(); for conflict in field_conflicts { if positional { bail!( ident.map_or_else( || span_range!(ty), |ident| ident.span().span_range() ), "positional fields do not support conflicts" ) } conflicts.insert( ident .as_ref() .expect("unnamed fields should be positional") .clone(), conflict, ); } let duplicate = struct_error .duplicate_field() .format(hash!("field" => Formattable::display(&field_name)))?; let help = if let Some(help) = struct_error.field_help() { if attribute_ident.is_none() && help.default { None } else { let example = &example.as_deref().unwrap_or("..."); let mut context = hash!( "field" => Formattable::display(&field_name), "example" => Formattable::display(example), "open_or_eq" => Formattable::display(&"{open_or_eq}"), "close_or_empty" => Formattable::display(&"{close_or_empty}")); if let Some(ident) = &attribute_ident { context.insert("attribute", Formattable::display(ident)); } Some(format!("\n\n= help: {}", help.format(context)?.value())) } } else { None }; let missing = struct_error .missing_field() .format(hash!("field" => Formattable::display(&field_name)))? .value() + help.as_deref().unwrap_or_default(); let default = if let Some(default) = default { Some(default.to_token_stream()) } else if optional { Some(quote!(<#ty as Default>::default())) } else { None }; Ok(AttrField { positional, ident: ident.map_or(IdentOrIdx::Idx(idx), IdentOrIdx::Ident), duplicate, help, missing, default, ty, }) }, ) .collect::>()?, conflicts, )) } fn map_error(&self, ty: &Type) -> TokenStream { self.help .as_ref() .map(|help| { // Precision of `.0` hides the entries but surpresses error when not used in // `help` string. let fmt = format!("{{__error}}{help}{{open_or_eq:.0}}{{close_or_empty:.0}}"); let ty = quote!(<#ty as ::attribute_derive::parsing::AttributeNamed>); quote! { .map_err(|__error| { ::attribute_derive::__private::syn::Error::new( __error.span(), format!(#fmt, open_or_eq=#ty::PREFERRED_OPEN_DELIMITER, close_or_empty=#ty::PREFERRED_CLOSE_DELIMITER) ) })} }) .unwrap_or_default() } fn partial(&self) -> TokenStream { let ty = &self.ty; if let IdentOrIdx::Ident(ref ident) = self.ident { if self.positional { quote!(#ident: Option<::attribute_derive::parsing::SpannedValue< <#ty as ::attribute_derive::parsing::AttributeBase>::Partial>>) } else { quote!(#ident: Option<::attribute_derive::parsing::Named< ::attribute_derive::parsing::SpannedValue< <#ty as ::attribute_derive::parsing::AttributeBase>::Partial>>>) } } else { quote!(Option<::attribute_derive::parsing::SpannedValue< <#ty as ::attribute_derive::parsing::AttributeBase>::Partial>>) } } fn parse_positional(&self) -> Option { let Self { ident, ty, positional, .. } = self; positional.then(|| { let parse_comma = parse_comma(); // TODO let map_error = self.map_error(); quote! { # use attribute_derive::parsing::AttributePositional; if let Some(__field) = <#ty as AttributePositional>::parse_positional(__input)? { __partial.#ident = Some(__field); #parse_comma; } } }) } fn parse_named(&self) -> Option { let Self { ident, ty, positional, duplicate, .. } = self; (!positional).then(|| { let parse_comma = parse_comma(); let map_error = self.map_error(ty); let s_ident = ident.to_string(); quote! { # use attribute_derive::parsing::{AttributeBase, AttributeNamed}; # use attribute_derive::from_partial::FromPartial; if let Some(__field) = <#ty as AttributeNamed>::parse_named(#s_ident, __input) #map_error? { let __name = __field.name; __partial.#ident = <#ty as FromPartial<<#ty as AttributeBase>::Partial>>::join( std::mem::take(&mut __partial.#ident).map(|__v| __v.value), __field.value, #duplicate, )? .map(|__v| ::attribute_derive::parsing::Named { name: __name, value: __v, }); #parse_comma; continue; } } }) } fn assign_partial(&self) -> TokenStream { let Self { ident, missing, default, ty, .. } = self; let unwrap = default.as_ref().map_or_else( || quote!(?), |default| quote!(.unwrap_or_else(|_| #default)), ); let fmt = format!("{missing}{{open_or_eq:.0}}{{close_or_empty:.0}}"); let attr_named = quote!(<#ty as ::attribute_derive::parsing::AttributeNamed>); quote! { #ident: <#ty as ::attribute_derive::from_partial::FromPartial< <#ty as ::attribute_derive::parsing::AttributeBase>::Partial>>::from_option( __partial.#ident.map(|__v| __v.value()), &format!(#fmt, open_or_eq=#attr_named::PREFERRED_OPEN_DELIMITER, close_or_empty=#attr_named::PREFERRED_CLOSE_DELIMITER) )#unwrap } } fn join_field(&self) -> TokenStream { let Self { ident, ty, duplicate, .. } = self; let join = quote! { __first.#ident = <#ty as ::attribute_derive::from_partial::FromPartial< <#ty as ::attribute_derive::parsing::AttributeBase>::Partial>>::join( __first_value, __second_value, #duplicate)? }; if self.positional { quote! { if let Some(__second_value) = __second.#ident { let __first_value = ::std::mem::take(&mut __first.#ident); #join; } } } else { quote! { if let Some(__second) = __second.#ident { let (__first_name, __first_value) = if let Some(__first) = ::std::mem::take(&mut __first.#ident) { (Some(__first.name), Some(__first.value)) } else {(None, None)}; let __name = __first_name.unwrap_or(__second.name); let __second_value = __second.value; #join.map(|__v| ::attribute_derive::parsing::Named{name: __name, value: __v}); } } } } } fn parse_comma() -> TokenStream { quote! { if __input.is_empty() { return Ok(__partial); } else { <::attribute_derive::__private::syn::Token![,] as ::attribute_derive::__private::syn::parse::Parse>::parse(__input)?; } } } fn partial_attribute( partial: &Ident, vis: &Visibility, fields: &[AttrField], generics: &Generics, ) -> Result { let Some(first_field) = fields.first() else { return Ok(quote!(#[derive(Default)] struct #partial #generics {})); }; let fields = fields.iter().map(AttrField::partial); let fields = if matches!(first_field.ident, IdentOrIdx::Ident(_)) { quote!({#(#fields),*}) } else { quote!((#(#fields),*);) }; Ok(quote! { #[derive(Default)] #vis struct #partial #generics #fields }) } #[manyhow(impl_fn)] #[deprecated = "use `FromAttr` instead"] #[proc_macro_derive(Attribute, attributes(attribute, attr, from_attr))] pub fn attribute_derive(input: DeriveInput) -> Result { from_attr_derive_impl(input) } #[manyhow(impl_fn)] #[proc_macro_derive(FromAttr, attributes(attribute, attr, from_attr))] pub fn from_attr_derive( DeriveInput { attrs, vis, ident, generics, data, .. }: DeriveInput, ) -> Result { let partial_ident = &format_ident!("{ident}Partial"); let StructAttrs { ident: attribute_ident, mut aliases, error: ref struct_error, // duplicate, } = StructAttrs::from_attrs(&ident, attrs)?; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); if let Some(ref attribute_ident) = attribute_ident { aliases.insert(0, attribute_ident.to_string()); } let attribute_ident_trait = (!aliases.is_empty()) .then(|| { quote! { # use attribute_derive::AttributeIdent; impl #impl_generics AttributeIdent for #ident #ty_generics #where_clause { const IDENTS: &'static [&'static str] = &[#(#aliases),*]; } } }) .unwrap_or_default(); let (fields, conflicts) = match data { syn::Data::Struct(DataStruct { fields: fields @ (Fields::Named(_) | Fields::Unnamed(_)), .. }) => AttrField::parse_fields(fields, struct_error, attribute_ident)?, _ => bail!("only works on structs with fields"), }; let conflicts = conflicts.to_tokens(struct_error)?; let partial_struct = partial_attribute(partial_ident, &vis, &fields, &generics)?; let error_invalid_name = struct_error.unknown_field_error(&fields)?; let partial_fields = fields.iter().map(AttrField::assign_partial); let join_fields = fields.iter().map(AttrField::join_field); let from_attr = if fields.len() == 1 && fields[0].positional { // newtype struct let AttrField { ref ty, .. } = fields[0]; quote! { # use ::attribute_derive::FromAttr; # use ::attribute_derive::parsing::SpannedValue; # use ::attribute_derive::__private::syn::parse::{ParseStream, Parse}; # use ::attribute_derive::__private::syn::{Result, Error}; impl #impl_generics FromAttr for #ident #ty_generics #where_clause { fn parse_partial(input: ParseStream) -> Result { <#ty as FromAttr>::parse_partial(input) .map(SpannedValue::call_site) .map(Some) .map(#partial_ident) } } } } else { let parse_positionals: Vec<_> = fields .iter() .filter_map(AttrField::parse_positional) .collect(); let parse_named: Vec<_> = fields.iter().filter_map(AttrField::parse_named).collect(); quote! { # use ::attribute_derive::parsing::AttributeMeta; # use ::attribute_derive::__private::syn::parse::{ParseStream, Parse}; # use ::attribute_derive::__private::syn::{Result, Error}; impl #impl_generics AttributeMeta for #ident #ty_generics #where_clause { fn parse_inner(__input: ParseStream) -> Result { let mut __partial = #partial_ident::default(); #(#parse_positionals)* while !__input.is_empty() { #(#parse_named)* return Err(__input.error(#error_invalid_name)); } Ok(__partial) } } } }; Ok(quote! { # use ::attribute_derive::parsing::{AttributeBase, SpannedValue}; # use ::attribute_derive::from_partial::FromPartial; # use ::attribute_derive::__private::syn::parse::{ParseStream, Parse}; # use ::attribute_derive::__private::syn::{Result, Error}; #[allow(clippy::field_reassign_with_default, remove_unnecessary_else)] const _: () = { #attribute_ident_trait #partial_struct impl #impl_generics AttributeBase for #ident #ty_generics #where_clause { type Partial = #partial_ident #ty_generics; } #from_attr impl #impl_generics Parse for #ident #ty_generics #where_clause { fn parse(__input: ParseStream) -> Result { ::parse_input(__input) } } impl #impl_generics FromPartial<#partial_ident #ty_generics> for #ident #ty_generics #where_clause { fn from(__partial: #partial_ident #ty_generics) -> Result { #conflicts Ok(Self { #(#partial_fields),* }) } fn from_option(__partial: Option<#partial_ident #ty_generics>, _: &str) -> Result { >::from( __partial.unwrap_or_default()) } fn join( __first: Option>, __second: SpannedValue<#partial_ident #ty_generics>, _: &str) -> Result>> { let mut __first = __first.unwrap_or_default().value; let __span = __second.span; let __second = __second.value; #(#join_fields;)* Ok(Some(SpannedValue { span: __span, value: __first})) } } }; }) }