Skip to content

Commit fbff5da

Browse files
committed
feat: add more ways to parse
1 parent a91c5e8 commit fbff5da

File tree

6 files changed

+296
-104
lines changed

6 files changed

+296
-104
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ license = "MIT"
88
readme = "README.md"
99
repository = "https://github.com/ModProg/attribute-derive"
1010
name = "attribute-derive"
11-
version = "0.2.2"
11+
version = "0.3.0"
1212
edition = "2021"
1313

1414
[lib]
1515

1616
[dependencies]
1717
proc-macro2 = "1"
1818
syn = "1"
19+
quote = "1.0.18"
1920

2021
[dependencies.attribute-derive-macro]
2122
version = "0.2.0"

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,21 @@ There are some limitations in syntax parsing that will be lifted future releases
3737
- literals in top level (meaning something like `#[attr(42, 3.14, "hi")]`
3838
- function like arguments (something like `#[attr(view(a = "test"))]`
3939
- other syntaxes, maybe something like `key: value`
40+
41+
## Parse methods
42+
43+
There are multiple ways of parsing a struct deriving [`Attribute`](https://docs.rs/attribute-derive/latest/attribute_derive/trait.Attribute.html).
44+
45+
For helper attributes there is:
46+
- [`Attribute::from_attributes`](https://docs.rs/attribute-derive/latest/attribute_derive/trait.Attribute.html#tymethod.from_attributes) which takes in an [`IntoIterator<Item = &'a
47+
syn::Attribute`](https://docs.rs/syn/latest/syn/struct.Attribute.html)
48+
(e.g. a [`&Vec<syn::Attribute>`](https://docs.rs/syn/latest/syn/struct.Attribute.html)). Most useful for derive macros.
49+
- [`Attribute::remove_attributes`](https://docs.rs/attribute-derive/latest/attribute_derive/trait.Attribute.html#tymethod.remove_attributes) which takes an [`&mut Vec<syn::Attribute>`](https://docs.rs/syn/latest/syn/struct.Attribute.html)
50+
and does not only parse the [`Attribute`](https://docs.rs/attribute-derive/latest/attribute_derive/trait.Attribute.html#tymethod.from_attributes) but also removes those matching. Useful for helper
51+
attributes for proc macros, where the helper attributes need to be removed.
52+
53+
For parsing a single [`TokenStream`](https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html) e.g. for parsing the proc macro input there a two ways:
54+
55+
- [`Attribute::from_args`](https://docs.rs/attribute-derive/latest/attribute_derive/trait.Attribute.html#tymethod.from_args) taking in a [`TokenStream`](https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html)
56+
- As `derive(Attribute)` also derives [`Parse`](https://docs.rs/syn/latest/syn/parse/trait.Parse.html) so you can use the [parse](https://docs.rs/syn/latest/syn/parse/index.html) API,
57+
e.g. with [`parse_macro_input!(tokens as Attribute)`](https://docs.rs/syn/latest/syn/macro.parse_macro_input.html).

macro/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ proc-macro = true
1818
[dependencies]
1919
proc-macro-error = "1.0.4"
2020
proc-macro2 = "1.0.36"
21-
quote = "1.0"
21+
quote = "1.0.18"
22+
quote-use = { version = "0.4.3", features = ["namespace_idents", "prelude_core", "prelude_2021"], default-features = false }
2223
syn = "1.0"

macro/src/lib.rs

Lines changed: 84 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
use proc_macro2::TokenStream;
22
use proc_macro_error::{abort_call_site, proc_macro_error, ResultExt};
3-
use quote::quote;
3+
use quote::format_ident;
44
use syn::{
5-
parse_macro_input, parse_quote, punctuated::Punctuated, DataStruct, DeriveInput, Field, Fields,
6-
FieldsNamed, Lit, Meta, MetaNameValue, NestedMeta, Path, Token,
5+
parse_macro_input, punctuated::Punctuated, DataStruct, DeriveInput, Field, Fields, FieldsNamed,
6+
Lit, Meta, MetaNameValue, NestedMeta, Token,
77
};
88

9+
use quote_use::quote_use as quote;
10+
911
// TODO generally should use fully qualified names for trait function calls
1012

1113
#[proc_macro_error]
1214
#[proc_macro_derive(Attribute, attributes(attribute))]
1315
pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
14-
let syn: Path = parse_quote!(::attribute_derive::__private::syn);
15-
let pm2: Path = parse_quote!(::attribute_derive::__private::proc_macro2);
16-
let some: Path = parse_quote!(::core::option::Option::Some);
17-
let ok: Path = parse_quote!(::core::result::Result::Ok);
18-
let err: Path = parse_quote!(::core::result::Result::Err);
19-
2016
let DeriveInput {
2117
attrs,
2218
ident,
@@ -25,6 +21,8 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
2521
..
2622
} = parse_macro_input!(input as DeriveInput);
2723

24+
let parser_ident = format_ident!("{ident}__Parser");
25+
2826
let mut attribute_ident = None;
2927
let mut invalid_field = None;
3028

@@ -54,16 +52,13 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
5452
}
5553
}
5654
}
57-
let attribute_ident: String = attribute_ident.unwrap_or_else(|| {
58-
abort_call_site!(
59-
r#"You need to specify the attribute path via `#[attribute(ident="name_of_your_attribute")]`"#
60-
);
61-
});
55+
let attribute_ident = attribute_ident
56+
.map(|attribute_ident| quote!(Some(#attribute_ident)))
57+
.unwrap_or_else(|| quote!(None));
6258

6359
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
6460

6561
let mut options_ty: Punctuated<TokenStream, Token!(,)> = Punctuated::new();
66-
let mut options_creation: Punctuated<TokenStream, Token!(,)> = Punctuated::new();
6762
let mut parsing: Punctuated<TokenStream, Token!(,)> = Punctuated::new();
6863
let mut option_assignments: Punctuated<TokenStream, Token!(;)> = Punctuated::new();
6964
let mut assignments: Punctuated<TokenStream, Token!(,)> = Punctuated::new();
@@ -124,35 +119,35 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
124119

125120
options_ty
126121
.push(quote!(#ident: Option<<#ty as ::attribute_derive::ConvertParsed>::Type>));
127-
options_creation.push(quote!(#ident: None));
128122

129123
let error1 = format!("`{ident}` is specified multiple times");
130124
let error2 = format!("`{ident}` was already specified");
131125
option_assignments.push(quote! {
132-
self.#ident = <#ty as ::attribute_derive::ConvertParsed>::aggregate(self.#ident.take(), __other.#ident, #error1, #error2)?;
126+
self.#ident = <#ty as ::attribute_derive::ConvertParsed>::aggregate(self.#ident.take(), $other.#ident, #error1, #error2)?;
133127
});
134128

135129
let error = if let Some(expected) = expected {
136-
quote! {.map_err(|__error| #syn::Error::new(__error.span(), #expected)) }
130+
quote! {.map_err(|__error| ::attribute_derive::__private::syn::Error::new(__error.span(), #expected)) }
137131
} else {
138132
quote!()
139133
};
140134

141135
parsing.push(quote! {
136+
# use ::attribute_derive::__private::{syn, proc_macro2};
142137
#ident_str => {
143-
__options.#ident = #some(
144-
if let #some(#some(__value)) = __is_flag.then(|| <#ty as ::attribute_derive::ConvertParsed>::as_flag()) {
138+
$parser.#ident = Some(
139+
if let Some(Some(__value)) = $is_flag.then(|| <#ty as ::attribute_derive::ConvertParsed>::as_flag()) {
145140
__value
146141
} else {
147-
__input.step(|__cursor| match __cursor.punct() {
148-
#some((__punct, __rest))
149-
if __punct.as_char() == '=' && __punct.spacing() == #pm2::Spacing::Alone =>
142+
$input.step(|__cursor| match __cursor.punct() {
143+
Some((__punct, __rest))
144+
if __punct.as_char() == '=' && __punct.spacing() == proc_macro2::Spacing::Alone =>
150145
{
151-
#ok(((), __rest))
146+
Ok(((), __rest))
152147
}
153-
_ => #err(__cursor.error("Expected assignment `=`")),
148+
_ => Err(__cursor.error("Expected assignment `=`")),
154149
})?;
155-
#syn::parse::Parse::parse(__input)#error?
150+
syn::parse::Parse::parse($input)#error?
156151
}
157152
);
158153
}
@@ -163,14 +158,15 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
163158
});
164159
assignments.push(if default {
165160
quote! {
166-
#ident: __options.#ident.map(|t| ::attribute_derive::ConvertParsed::convert(t)).unwrap_or_else(|| #ok(<#ty as core::default::Default>::default()))?
161+
#ident: $parser.#ident.map(|t| ::attribute_derive::ConvertParsed::convert(t)).unwrap_or_else(|| Ok(<#ty as Default>::default()))?
167162
}
168163
} else {
169164
quote! {
170-
#ident: match __options.#ident.map(|t| ::attribute_derive::ConvertParsed::convert(t)) {
165+
# use ::attribute_derive::__private::{syn, proc_macro2};
166+
#ident: match $parser.#ident.map(|t| ::attribute_derive::ConvertParsed::convert(t)) {
171167
Some(__option) => __option?,
172168
None if <#ty as ::attribute_derive::ConvertParsed>::default_by_default() => <#ty as ::attribute_derive::ConvertParsed>::default(),
173-
_ => #err(#syn::Error::new(#pm2::Span::call_site(), #error))?,
169+
_ => Err(syn::Error::new(proc_macro2::Span::call_site(), #error))?,
174170
}
175171
}
176172
});
@@ -195,66 +191,74 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
195191
});
196192

197193
quote! {
198-
#[allow(unreachable_code)]
199-
impl #impl_generics ::attribute_derive::Attribute for #ident #ty_generics #where_clause {
200-
fn from_attributes(__attrs: impl ::core::iter::IntoIterator<Item = #syn::Attribute>) -> #syn::Result<Self>{
201-
struct __Options{
202-
#options_ty
203-
}
204-
impl __Options {
205-
fn extend_with(&mut self, __other:Self) -> #syn::Result<()>{
206-
#option_assignments
207-
#ok(())
208-
}
209-
}
210-
impl #syn::parse::Parse for __Options {
211-
fn parse(__input: #syn::parse::ParseStream<'_>) -> #syn::Result<Self> {
212-
let mut __options = __Options{
213-
#options_creation
214-
};
215-
loop {
216-
if __input.is_empty() {
217-
break;
218-
}
194+
# use ::attribute_derive::__private::{syn::{self, Result, Ident, Token, parse::{Parse, ParseStream}}, proc_macro2};
195+
# use ::attribute_derive::TryExtendOne;
219196

220-
let __variable = #syn::Ident::parse(__input)?;
197+
#[doc(hidden)]
198+
#[allow(non_camel_case_types)]
199+
#[derive(Default)]
200+
struct #parser_ident {
201+
#options_ty
202+
}
221203

222-
let __is_flag = !__input.peek(#syn::Token!(=));
204+
#[allow(unreachable_code)]
205+
impl Parse for #parser_ident {
206+
fn parse($input: ParseStream<'_>) -> Result<Self> {
207+
let mut $parser: Self = Default::default();
208+
loop {
209+
if $input.is_empty() {
210+
break;
211+
}
223212

224-
match __variable.to_string().as_str() {
225-
#parsing
226-
_ => {
227-
return #err(#syn::Error::new(
228-
__variable.span(),
229-
#error_invalid_name
230-
))
231-
}
232-
}
213+
let $variable = Ident::parse($input)?;
233214

234-
if __input.is_empty() {
235-
break;
236-
}
215+
let $is_flag = !$input.peek(Token!(=));
237216

238-
// Parse `,`
239-
__input.step(|__cursor| match __cursor.punct() {
240-
#some((__punct, __rest)) if __punct.as_char() == ',' => #ok(((), __rest)),
241-
_ => #err(__cursor.error("Expected assignment `=`")),
242-
})?;
217+
match $variable.to_string().as_str() {
218+
#parsing
219+
_ => {
220+
return Err(syn::Error::new(
221+
$variable.span(),
222+
#error_invalid_name
223+
))
243224
}
244-
Ok(__options)
245225
}
246-
}
247-
let mut __options = __Options{
248-
#options_creation
249-
};
250-
for __attribute in __attrs {
251-
if __attribute.path.is_ident(#attribute_ident) {
252-
__options.extend_with(__attribute.parse_args()?)?;
226+
227+
if $input.is_empty() {
228+
break;
253229
}
230+
231+
// Parse `,`
232+
$input.step(|__cursor| match __cursor.punct() {
233+
Some((__punct, __rest)) if __punct.as_char() == ',' => Ok(((), __rest)),
234+
_ => Err(__cursor.error("Expected assignment `=`")),
235+
})?;
254236
}
255-
#ok(Self {
256-
#assignments
257-
})
237+
Ok($parser)
238+
}
239+
}
240+
241+
#[allow(unreachable_code)]
242+
impl TryExtendOne for #parser_ident {
243+
fn try_extend_one(&mut self, $other: Self) -> Result<()>{
244+
#option_assignments
245+
Ok(())
246+
}
247+
}
248+
249+
#[allow(unreachable_code)]
250+
impl #impl_generics ::attribute_derive::Attribute for #ident #ty_generics #where_clause {
251+
const IDENT: Option<&'static str> = #attribute_ident;
252+
type Parser = #parser_ident;
253+
254+
fn from_parser($parser: Self::Parser) -> Result<Self> {
255+
Ok(Self{#assignments})
256+
}
257+
}
258+
259+
impl #impl_generics Parse for #ident #ty_generics #where_clause {
260+
fn parse($input: ParseStream<'_>) -> Result<Self> {
261+
Parse::parse($input).and_then(Self::from_parser)
258262
}
259263
}
260264
}

0 commit comments

Comments
 (0)