Skip to content

Commit 813c49a

Browse files
committed
support for leading positional arguments
1 parent c3e2e6c commit 813c49a

File tree

3 files changed

+65
-42
lines changed

3 files changed

+65
-42
lines changed

macro/src/lib.rs

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
6868
.unwrap_or_default();
6969

7070
let mut options_ty: Punctuated<TokenStream, Token!(,)> = Punctuated::new();
71+
let mut parsing_positional: Punctuated<TokenStream, Token!(;)> = Punctuated::new();
7172
let mut parsing: Punctuated<TokenStream, Token!(,)> = Punctuated::new();
7273
let mut option_assignments: Punctuated<TokenStream, Token!(;)> = Punctuated::new();
7374
let mut assignments: Punctuated<TokenStream, Token!(,)> = Punctuated::new();
@@ -83,13 +84,14 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
8384
} in named.into_iter()
8485
{
8586
let mut default = false;
87+
let mut positional = false;
8688
let mut missing = None;
8789
let mut expected = None;
8890
for attribute in attrs
8991
.into_iter()
9092
.filter(|attribute| attribute.path.is_ident("attribute"))
9193
{
92-
const VALID_FORMAT: &str = r#"Expected `#[attribute(default, missing="error message", expected="error message"])`"#;
94+
const VALID_FORMAT: &str = r#"Expected `#[attribute(default, positional, missing="error message", expected="error message"])`"#;
9395
let meta: Meta = attribute.parse_meta().unwrap_or_abort();
9496
if let Meta::List(meta) = meta {
9597
for meta in meta.nested {
@@ -106,14 +108,16 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
106108
("expected", Lit::Str(lit)) => expected = Some(lit.value()),
107109
_ => abort_call_site!(VALID_FORMAT),
108110
},
109-
110-
Meta::Path(path) => {
111-
if path.is_ident("default") {
112-
default = true;
113-
} else {
114-
abort_call_site!(VALID_FORMAT);
115-
}
116-
}
111+
Meta::Path(path) => match path
112+
.get_ident()
113+
.unwrap_or_else(|| abort_call_site!(VALID_FORMAT))
114+
.to_string()
115+
.as_str()
116+
{
117+
"default" => default = true,
118+
"positional" => positional = true,
119+
_ => abort_call_site!(VALID_FORMAT),
120+
},
117121
_ => abort_call_site!(VALID_FORMAT),
118122
}
119123
} else {
@@ -141,26 +145,44 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
141145
quote!()
142146
};
143147

144-
parsing.push(quote! {
145-
# use ::attribute_derive::__private::{syn, proc_macro2};
146-
#ident_str => {
147-
$parser.#ident = Some(
148-
if let Some(Some(__value)) = $is_flag.then(|| <#ty as ::attribute_derive::ConvertParsed>::as_flag()) {
149-
__value
150-
} else {
151-
$input.step(|__cursor| match __cursor.punct() {
152-
Some((__punct, __rest))
153-
if __punct.as_char() == '=' && __punct.spacing() == proc_macro2::Spacing::Alone =>
154-
{
155-
Ok(((), __rest))
156-
}
157-
_ => Err(__cursor.error("Expected assignment `=`")),
158-
})?;
159-
syn::parse::Parse::parse($input)#error?
148+
if positional {
149+
parsing_positional.push(quote! {
150+
# use ::attribute_derive::__private::{syn, proc_macro2};
151+
$parser.#ident = Some(syn::parse::Parse::parse($input)#error?);
152+
if $input.is_empty() {
153+
break
154+
}
155+
$input.step(|$cursor| match $cursor.punct() {
156+
Some(($punct, $rest))
157+
if $punct.as_char() == ',' =>
158+
{
159+
Ok(((), $rest))
160160
}
161-
);
162-
}
163-
});
161+
_ => Err($cursor.error("Expected ,")),
162+
})?;
163+
})
164+
} else {
165+
parsing.push(quote! {
166+
# use ::attribute_derive::__private::{syn, proc_macro2};
167+
#ident_str => {
168+
$parser.#ident = Some(
169+
if let Some(Some(__value)) = $is_flag.then(|| <#ty as ::attribute_derive::ConvertParsed>::as_flag()) {
170+
__value
171+
} else {
172+
$input.step(|__cursor| match __cursor.punct() {
173+
Some((__punct, __rest))
174+
if __punct.as_char() == '=' && __punct.spacing() == proc_macro2::Spacing::Alone =>
175+
{
176+
Ok(((), __rest))
177+
}
178+
_ => Err(__cursor.error("Expected assignment `=`")),
179+
})?;
180+
syn::parse::Parse::parse($input)#error?
181+
}
182+
);
183+
}
184+
});
185+
}
164186

165187
let error = missing.unwrap_or_else(|| {
166188
format!("Mandatory `{ident}` was not specified via the attributes.")
@@ -214,6 +236,10 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
214236
impl Parse for #parser_ident {
215237
fn parse($input: ParseStream<'_>) -> Result<Self> {
216238
let mut $parser: Self = Default::default();
239+
loop {
240+
#parsing_positional
241+
break;
242+
}
217243
loop {
218244
if $input.is_empty() {
219245
break;
@@ -240,7 +266,7 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
240266
// Parse `,`
241267
$input.step(|__cursor| match __cursor.punct() {
242268
Some((__punct, __rest)) if __punct.as_char() == ',' => Ok(((), __rest)),
243-
_ => Err(__cursor.error("Expected assignment `=`")),
269+
_ => Err(__cursor.error("Expected end of arg `,`")),
244270
})?;
245271
}
246272
Ok($parser)

src/lib.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,6 @@ pub trait Attribute: Sized {
161161
///
162162
/// This is best used for derive macros, where you don't need to remove your attributes.
163163
///
164-
/// # Panics
165-
/// The default implementation panics, when [`IDENT`](Self::IDENT) is not set, when using the
166-
/// derive macro, this can be set via `#[attribute(ident="some_ident")]`.
167-
///
168164
/// # Errors
169165
/// Fails with a [`syn::Error`] so you can conveniently return that as a compiler error in a proc
170166
/// macro in the following cases
@@ -203,10 +199,6 @@ pub trait Attribute: Sized {
203199
/// Use this if you are implementing an attribute macro, and need to remove your helper
204200
/// attributes.
205201
///
206-
/// # Panics
207-
/// The default implementation panics, when [`IDENT`](Self::IDENT) is not set, when using the
208-
/// derive macro, this can be set via `#[attribute(ident="some_ident")]`.
209-
///
210202
/// # Errors
211203
/// Fails with a [`syn::Error`] so you can conveniently return that as a compiler error in a proc
212204
/// macro in the following cases

tests/derive.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ fn test() {
99
#[derive(Attribute)]
1010
#[attribute(ident = "test")]
1111
struct Test {
12-
// a: u8,
12+
#[attribute(positional)]
13+
a: u8,
1314
b: LitStr,
1415
c: String,
1516
oc: Option<String>,
@@ -23,9 +24,10 @@ fn test() {
2324
}
2425

2526
let parsed = Test::from_attributes([
26-
parse_quote!(#[test(b="hi", c="ho", oc="xD", d=(), e=if true{ "a" } else { "b" }, f= [(), Debug], g, i = smth::hello + 24/3'a', b = c)]),
27+
parse_quote!(#[test(8, b="hi", c="ho", oc="xD", d=(), e=if true{ "a" } else { "b" }, f= [(), Debug], g, i = smth::hello + 24/3'a', b = c)]),
2728
].iter())
2829
.unwrap();
30+
assert_eq!(parsed.a, 8);
2931
assert_eq!(parsed.b.value(), "hi");
3032
assert_eq!(parsed.c, "ho");
3133
assert_eq!(parsed.oc, Some("xD".to_owned()));
@@ -38,9 +40,10 @@ fn test() {
3840
assert_eq!(parsed.i.to_string(), "smth :: hello + 24 / 3 'a' , b = c");
3941

4042
let parsed = Test::from_args(
41-
quote!(b="hi", c="ho", oc="xD", d=(), e=if true{ "a" } else { "b" }, f= [(), Debug], g, i = smth::hello + 24/3'a', b = c)
43+
quote!(8, b="hi", c="ho", oc="xD", d=(), e=if true{ "a" } else { "b" }, f= [(), Debug], g, i = smth::hello + 24/3'a', b = c)
4244
)
4345
.unwrap();
46+
assert_eq!(parsed.a, 8);
4447
assert_eq!(parsed.b.value(), "hi");
4548
assert_eq!(parsed.c, "ho");
4649
assert_eq!(parsed.oc, Some("xD".to_owned()));
@@ -54,10 +57,11 @@ fn test() {
5457

5558
let mut attrs = vec![
5659
parse_quote!(#[something]),
57-
parse_quote!(#[test(b="hi", c="ho", oc="xD", d=(), e=if true{ "a" } else { "b" }, f= [(), Debug], g, i = smth::hello + 24/3'a', b = c)]),
60+
parse_quote!(#[test(8, b="hi", c="ho", oc="xD", d=(), e=if true{ "a" } else { "b" }, f= [(), Debug], g, i = smth::hello + 24/3'a', b = c)]),
5861
parse_quote!(#[another(smth)]),
5962
];
6063
let parsed = Test::remove_attributes(&mut attrs).unwrap();
64+
assert_eq!(parsed.a, 8);
6165
assert_eq!(parsed.b.value(), "hi");
6266
assert_eq!(parsed.c, "ho");
6367
assert_eq!(parsed.oc, Some("xD".to_owned()));
@@ -71,9 +75,10 @@ fn test() {
7175
assert_eq!(attrs.len(), 2);
7276

7377
let parsed: Test = parse2(
74-
quote!(b="hi", c="ho", oc="xD", d=(), e=if true{ "a" } else { "b" }, f= [(), Debug], g, i = smth::hello + 24/3'a', b = c)
78+
quote!(8, b="hi", c="ho", oc="xD", d=(), e=if true{ "a" } else { "b" }, f= [(), Debug], g, i = smth::hello + 24/3'a', b = c)
7579
)
7680
.unwrap();
81+
assert_eq!(parsed.a, 8);
7782
assert_eq!(parsed.b.value(), "hi");
7883
assert_eq!(parsed.c, "ho");
7984
assert_eq!(parsed.oc, Some("xD".to_owned()));

0 commit comments

Comments
 (0)