Skip to content

Commit 1b1232f

Browse files
committed
Support aggregating array type attributes
This makes it a lot easier to use with additive feature flags. Bumping Version, as this is technically a breaking change.
1 parent a055f22 commit 1b1232f

File tree

7 files changed

+102
-111
lines changed

7 files changed

+102
-111
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
Cargo.lock

Cargo.lock

Lines changed: 0 additions & 87 deletions
This file was deleted.

Cargo.toml

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

1414
[lib]

macro/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ keywords = ["derive", "macro"]
77
license = "MIT"
88
readme = "README.md"
99
repository = "https://github.com/ModProg/attribute-derive"
10-
version = "0.1.0"
10+
version = "0.2.0"
1111
edition = "2021"
1212
name = "attribute-derive-macro"
1313

macro/src/lib.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use syn::{
1313
pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1414
let syn: Path = parse_quote!(::attribute_derive::__private::syn);
1515
let pm2: Path = parse_quote!(::attribute_derive::__private::proc_macro2);
16-
let none: Path = parse_quote!(::core::option::Option::None);
1716
let some: Path = parse_quote!(::core::option::Option::Some);
1817
let ok: Path = parse_quote!(::core::result::Result::Ok);
1918
let err: Path = parse_quote!(::core::result::Result::Err);
@@ -130,18 +129,7 @@ pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
130129
let error1 = format!("`{ident}` is specified multiple times");
131130
let error2 = format!("`{ident}` was already specified");
132131
option_assignments.push(quote! {
133-
match (&self.#ident, __other.#ident) {
134-
(#none, __value @ #some(_)) => self.#ident = __value,
135-
(#some(__first), #some(__second)) => {
136-
let mut __error = <<#ty as ::attribute_derive::ConvertParsed>::Type as ::attribute_derive::Error>::error(__first, #error1);
137-
#syn::Error::combine(&mut __error, <<#ty as ::attribute_derive::ConvertParsed>::Type as ::attribute_derive::Error>::error(
138-
&__second,
139-
#error2,
140-
));
141-
return #err(__error);
142-
}
143-
_ => {}
144-
}
132+
self.#ident = <#ty as ::attribute_derive::ConvertParsed>::aggregate(self.#ident.take(), __other.#ident, #error1, #error2)?;
145133
});
146134

147135
let error = if let Some(expected) = expected {

src/lib.rs

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
//! - other syntaxes, maybe something like `key: value`
4141
use std::fmt::Display;
4242

43-
use proc_macro2::{Literal, Span};
4443
#[doc(hidden)]
4544
pub use attribute_derive_macro::Attribute;
45+
use proc_macro2::{Literal, Span, TokenStream};
4646
use syn::{
4747
bracketed, parse::Parse, punctuated::Punctuated, Expr, Lit, LitBool, LitByteStr, LitChar,
4848
LitFloat, LitInt, LitStr, Path, Result, Token, Type, __private::ToTokens, parse_quote,
@@ -113,7 +113,7 @@ where
113113
pub trait ConvertParsed
114114
where
115115
Self: Sized,
116-
Self::Type: Error,
116+
Self::Type: Error + Clone,
117117
{
118118
/// The type this can be converted from
119119
type Type;
@@ -128,14 +128,52 @@ where
128128
}
129129
/// The default value, this is necessary to implement the implicit default behavior of
130130
/// [`Option`]
131+
///
132+
/// This is necessary as the [`Default`] trait cannot be used in expanded code, but normally you
133+
/// can easily implement it using it:
134+
/// ```
135+
/// # use attribute_derive::ConvertParsed;
136+
/// # use syn::{Result, LitBool};
137+
/// # #[derive(Default)]
138+
/// # struct bool;
139+
/// impl ConvertParsed for bool {
140+
/// # type Type = LitBool;
141+
/// # fn convert(value: Self::Type) -> Result<Self> {
142+
/// # unimplemented!()
143+
/// # }
144+
/// fn default() -> Self {
145+
/// Default::default()
146+
/// }
147+
/// }
148+
/// ```
131149
fn default() -> Self {
132150
unreachable!("default_by_default should only return true if this is overridden")
133151
}
134-
/// Should values of this type be able to be defined as flag i.e. just `#[attr(default)]`
135-
/// instead of `#[attr(default=true)]`
152+
/// Returns the value when this type is specified as flag i.e. just `#[attr(default)]`
153+
/// instead of `#[attr(default=true)]`. This relies on [`Self::default`].
136154
fn as_flag() -> Option<Self::Type> {
137155
None
138156
}
157+
/// Should values of this type be aggregated instead of conflict if specified multiple times
158+
///
159+
/// Currently this is only implemented for [`Arrays`](Array)
160+
#[allow(unused)]
161+
fn aggregate(
162+
this: Option<Self::Type>,
163+
other: Option<Self::Type>,
164+
error1: &str,
165+
error2: &str,
166+
) -> Result<Option<Self::Type>> {
167+
match (this, other) {
168+
(None, value) => Ok(value),
169+
(value, None) => Ok(value),
170+
(Some(this), Some(other)) => {
171+
let mut error = this.error(error1);
172+
syn::Error::combine(&mut error, other.error(error2));
173+
Err(error)
174+
}
175+
}
176+
}
139177
}
140178

141179
/// Helper trait to generate sensible errors
@@ -156,7 +194,8 @@ where
156194

157195
/// Macro to easily implement [`ConvertParsed`] for syn types
158196
macro_rules! convert_parsed {
159-
($type:path) => {
197+
($(#[$meta:meta])* $type:path) => {
198+
$(#[$meta])*
160199
impl ConvertParsed for $type {
161200
type Type = $type;
162201
fn convert(s: Self) -> Result<Self> {
@@ -206,7 +245,7 @@ macro_rules! convert_parsed {
206245
impl<Output, Parsed> ConvertParsed for Option<Output>
207246
where
208247
Output: ConvertParsed<Type = Parsed>,
209-
Parsed: Error,
248+
Parsed: Error + Clone,
210249
{
211250
type Type = Parsed;
212251
fn convert(s: Parsed) -> Result<Self> {
@@ -225,11 +264,29 @@ where
225264
impl<Output, Parsed> ConvertParsed for Vec<Output>
226265
where
227266
Output: ConvertParsed<Type = Parsed>,
267+
Parsed: Clone,
228268
{
229269
type Type = Array<Parsed>;
230270
fn convert(array: Array<Parsed>) -> Result<Self> {
231271
array.data.into_iter().map(ConvertParsed::convert).collect()
232272
}
273+
fn aggregate(
274+
this: Option<Self::Type>,
275+
other: Option<Self::Type>,
276+
_: &str,
277+
_: &str,
278+
) -> Result<Option<Self::Type>> {
279+
Ok(match (this, other) {
280+
(None, None) => None,
281+
(None, value) => value,
282+
(value, None) => value,
283+
(Some(mut this), Some(other)) => {
284+
this.data.extend_from_slice(&other.data);
285+
this.span = this.span.join(other.span).unwrap_or(this.span);
286+
Some(this)
287+
}
288+
})
289+
}
233290
}
234291

235292
impl ConvertParsed for bool {
@@ -254,6 +311,7 @@ impl ConvertParsed for bool {
254311

255312
/// Helper struct to parse array literals:
256313
/// `[a, b, c]`
314+
#[derive(Clone)]
257315
pub struct Array<T> {
258316
data: Vec<T>,
259317
span: Span,
@@ -286,6 +344,16 @@ convert_parsed!(Lit);
286344
convert_parsed![LitStr, LitByteStr, LitChar, LitInt, LitFloat, LitBool, Literal];
287345
convert_parsed!(Expr);
288346

347+
// TODO make this warning better visable
348+
convert_parsed! {
349+
/// Try to avoid using this, as it will consume everything behind, so it needs to be defined as the
350+
/// last parameter.
351+
///
352+
/// In the future there might be something to allow better handling of this (maybe by puttin it
353+
/// into `()`)
354+
TokenStream
355+
}
356+
289357
convert_parsed!(LitStr => String: LitStr::value);
290358
// TODO convert_parsed!(LitByteStr => Vec<u8>: LitByteStr::value);
291359
convert_parsed!(LitChar => char: LitChar::value);

tests/derive.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use attribute_derive::Attribute;
2+
use proc_macro2::TokenStream;
23
use syn::parse_quote;
34

45
#[test]
@@ -17,10 +18,11 @@ fn test() {
1718
f: Vec<Type>,
1819
g: bool,
1920
h: bool,
21+
i: TokenStream,
2022
}
2123

2224
let parsed = Test::from_attributes([
23-
parse_quote!(#[test(b="hi", c="ho", oc="xD", d=(), e=if true{ "a" } else { "b" }, f= [(), Debug], g)]),
25+
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)]),
2426
])
2527
.unwrap();
2628
assert_eq!(parsed.b.value(), "hi");
@@ -32,6 +34,7 @@ fn test() {
3234
assert!(parsed.f.len() == 2);
3335
assert!(parsed.g);
3436
assert!(!parsed.h);
37+
assert_eq!(parsed.i.to_string(), "smth :: hello + 24 / 3 'a' , b = c");
3538
}
3639

3740
#[test]
@@ -156,10 +159,28 @@ fn error_specified() {
156159
fn default() {
157160
#[derive(Attribute, Debug)]
158161
#[attribute(ident = "test")]
159-
#[attribute(invalid_field = "error message")]
160-
#[allow(dead_code)]
161162
struct Test {
162163
#[attribute(default)]
163164
hi: f32,
164165
}
166+
assert_eq!(Test::from_attributes([]).unwrap().hi, 0.);
167+
}
168+
169+
#[test]
170+
fn aggregate() {
171+
#[derive(Attribute, Debug)]
172+
#[attribute(ident = "test")]
173+
struct Test {
174+
strings: Vec<String>,
175+
}
176+
177+
assert_eq!(
178+
Test::from_attributes([
179+
parse_quote!(#[test(strings=["a"])]),
180+
parse_quote!(#[test(strings=["b"])])
181+
])
182+
.unwrap()
183+
.strings,
184+
vec!["a".to_owned(), "b".to_owned()]
185+
)
165186
}

0 commit comments

Comments
 (0)