Skip to content

Commit fc35f8f

Browse files
committed
Derive AttributeIdent by default
1 parent 34ba680 commit fc35f8f

File tree

5 files changed

+171
-100
lines changed

5 files changed

+171
-100
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88
### Added
9+
- derive `AttributeIdent` by default, converting the `StructIdent` to `snake_case`
910
- `AttributeIdent` implementation for `Option<impl AttributeIdent>`
1011

1112
## [0.9.2] - 2024-06-22

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ edition = "2021"
1818

1919
[dependencies]
2020
derive-where = "1.2.7"
21-
manyhow = "0.11.2"
21+
manyhow = "0.11.3"
2222
proc-macro2 = "1"
2323
quote = "1"
2424
syn = "2"
@@ -34,6 +34,7 @@ path = "macro"
3434
[dev-dependencies]
3535
insta = "1.39.0"
3636
quote = "1"
37+
static_assertions = "1.1.0"
3738
syn = { version = "2", features = ["extra-traits"] }
3839

3940
[package.metadata.release]

macro/src/lib.rs

Lines changed: 141 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::borrow::Cow;
22
use std::collections::{HashMap, HashSet};
33
use std::fmt::Display;
44
use std::iter;
5+
use std::ops::Range;
56

67
use collection_literals::hash;
78
use interpolator::{format, Formattable};
@@ -10,8 +11,8 @@ use proc_macro2::{Literal, Span, TokenStream};
1011
use proc_macro_utils::{TokenParser, TokenStream2Ext};
1112
use quote::{format_ident, ToTokens};
1213
use quote_use::quote_use as quote;
13-
use syn::{spanned::Spanned, Visibility};
14-
use syn::{DataStruct, DeriveInput, Field, Fields, Generics, Ident, LitStr, Type};
14+
use syn::spanned::Spanned;
15+
use syn::{DataStruct, DeriveInput, Field, Fields, Generics, Ident, LitStr, Type, Visibility};
1516

1617
const ATTRIBUTE_IDENT: &str = "attribute";
1718

@@ -170,8 +171,13 @@ struct StructAttrs {
170171
}
171172

172173
impl StructAttrs {
173-
fn from_attrs(attrs: impl IntoIterator<Item = syn::Attribute>) -> Result<Self> {
174-
const VALID_FORMAT: &str = r#"expected `#[attribute(ident=attribute_name, aliases=[alias1, alias2], error="..", error(unknown_field="..", unknown_field_single="..", unknown_field_empty="..", duplicate_field="..", missing_field="..", field_help=".."))]`"#;
174+
fn from_attrs(
175+
struct_ident: &Ident,
176+
attrs: impl IntoIterator<Item = syn::Attribute>,
177+
) -> Result<Self> {
178+
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=".."))]`"#;
179+
180+
let mut ident_span: Option<Range<Span>> = None;
175181
let mut ident: Option<Ident> = None;
176182
let mut aliases: Vec<String> = vec![];
177183
let mut error = StructError::Specific(Default::default());
@@ -188,105 +194,152 @@ impl StructAttrs {
188194
.clone()
189195
.parser();
190196
while !parser.is_empty() {
191-
let field = parser
192-
.next_ident()
193-
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
194-
match field.to_string().as_str() {
195-
"ident" => {
196-
parser
197-
.next_tt_eq()
198-
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
199-
ident = Some(
200-
parser
201-
.next_ident()
202-
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?,
203-
)
204-
}
205-
"aliases" => {
206-
parser
207-
.next_tt_eq()
208-
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
209-
let mut parser = parser
210-
.next_bracketed()
211-
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?
212-
.stream()
213-
.parser();
214-
aliases.extend(iter::from_fn(|| {
215-
_ = parser.next_tt_comma();
216-
parser.next_ident().map(|t| t.to_string())
217-
}));
218-
if !parser.is_empty() {
219-
bail!("{VALID_FORMAT}")
197+
eprintln!("{}", parser.to_token_stream());
198+
if let Some(not) = parser.next_tt_not() {
199+
if let Some(kw) = parser.next_keyword("ident") {
200+
if let Some(ident_span) = ident_span {
201+
bail!(
202+
error_message!(ident_span, "ident is specified twice")
203+
+ error_message!(
204+
not.span()..kw.span(),
205+
"ident was already specified"
206+
)
207+
)
208+
} else {
209+
ident_span = Some(not.span()..kw.span());
220210
}
221211
}
222-
"error" => {
223-
if parser.next_tt_eq().is_some() {
224-
error = StructError::Generic(
225-
FormatString::parse(parser)
212+
} else {
213+
let field = parser
214+
.next_ident()
215+
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
216+
match field.to_string().as_str() {
217+
"ident" => {
218+
parser
219+
.next_tt_eq()
220+
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
221+
ident = Some(
222+
parser
223+
.next_ident()
226224
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?,
227225
);
228-
} else {
229-
let parser = &mut parser
230-
.next_parenthesized()
226+
if let Some(ident_span) = ident_span {
227+
bail!(
228+
error_message!(ident_span, "ident is specified twice")
229+
+ error_message!(
230+
field.span()..ident.unwrap().span(),
231+
"ident was already specified"
232+
)
233+
)
234+
}
235+
}
236+
"aliases" => {
237+
parser
238+
.next_tt_eq()
239+
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
240+
let mut parser = parser
241+
.next_bracketed()
231242
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?
232243
.stream()
233244
.parser();
234-
let error = if let StructError::Specific(error) = &mut error {
235-
error
245+
aliases.extend(iter::from_fn(|| {
246+
_ = parser.next_tt_comma();
247+
parser.next_ident().map(|t| t.to_string())
248+
}));
249+
if !parser.is_empty() {
250+
bail!("{VALID_FORMAT}")
251+
}
252+
}
253+
"error" => {
254+
if parser.next_tt_eq().is_some() {
255+
error = StructError::Generic(
256+
FormatString::parse(parser)
257+
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?,
258+
);
236259
} else {
237-
error = StructError::Specific(Default::default());
238-
if let StructError::Specific(error) = &mut error {
260+
let parser = &mut parser
261+
.next_parenthesized()
262+
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?
263+
.stream()
264+
.parser();
265+
let error = if let StructError::Specific(error) = &mut error {
239266
error
240267
} else {
241-
unreachable!()
242-
}
243-
};
244-
while !parser.is_empty() {
245-
let field = parser
246-
.next_ident()
247-
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
248-
let mut string = |f: &mut _| -> Result<()> {
249-
parser
250-
.next_tt_eq()
251-
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
252-
*f = FormatString::parse(parser)
253-
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
254-
Ok(())
255-
};
256-
match field.to_string().as_str() {
257-
"unknown_field" => string(&mut error.unknown_field),
258-
"unknown_field_empty" => string(&mut error.unknown_field_empty),
259-
"unknown_field_single" => {
260-
string(&mut error.unknown_field_single)
268+
error = StructError::Specific(Default::default());
269+
if let StructError::Specific(error) = &mut error {
270+
error
271+
} else {
272+
unreachable!()
261273
}
262-
"duplicate_field" => string(&mut error.duplicate_field),
263-
"missing_field" => string(&mut error.missing_field),
264-
"field_help" => string(&mut error.field_help),
265-
"conflict" => string(&mut error.conflict),
266-
_ => bail!(field, "{VALID_FORMAT}"),
267-
}?;
268-
_ = parser.next_tt_comma();
274+
};
275+
while !parser.is_empty() {
276+
let field = parser
277+
.next_ident()
278+
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
279+
let mut string = |f: &mut _| -> Result<()> {
280+
parser
281+
.next_tt_eq()
282+
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
283+
*f = FormatString::parse(parser)
284+
.ok_or_else(|| ErrorMessage::call_site(VALID_FORMAT))?;
285+
Ok(())
286+
};
287+
match field.to_string().as_str() {
288+
"unknown_field" => string(&mut error.unknown_field),
289+
"unknown_field_empty" => {
290+
string(&mut error.unknown_field_empty)
291+
}
292+
"unknown_field_single" => {
293+
string(&mut error.unknown_field_single)
294+
}
295+
"duplicate_field" => string(&mut error.duplicate_field),
296+
"missing_field" => string(&mut error.missing_field),
297+
"field_help" => string(&mut error.field_help),
298+
"conflict" => string(&mut error.conflict),
299+
_ => bail!(field, "{VALID_FORMAT}"),
300+
}?;
301+
_ = parser.next_tt_comma();
302+
}
269303
}
270304
}
305+
// "duplicate" => {
306+
// parser.next_eq() .ok_or_else(||
307+
// ErrorMessage::call_site(VALID_FORMAT))?; let strategy
308+
// = parser.next_ident() .ok_or_else(||
309+
// ErrorMessage::call_site(VALID_FORMAT))?; duplicate =
310+
// match strategy.to_string().as_str() {
311+
// "AggregateOrError" => DuplicateStrategy::AggregateOrError,
312+
// "Error" => DuplicateStrategy::Error,
313+
// "AggregateOrOverride" => DuplicateStrategy::AggregateOrOverride,
314+
// "Override" => DuplicateStrategy::Override,
315+
// _ => abort!(strategy, VALID_FORMAT),
316+
// }
317+
// }
318+
_ => bail!(field, "{VALID_FORMAT}"),
271319
}
272-
// "duplicate" => {
273-
// parser.next_eq() .ok_or_else(||
274-
// ErrorMessage::call_site(VALID_FORMAT))?; let strategy
275-
// = parser.next_ident() .ok_or_else(||
276-
// ErrorMessage::call_site(VALID_FORMAT))?; duplicate =
277-
// match strategy.to_string().as_str() {
278-
// "AggregateOrError" => DuplicateStrategy::AggregateOrError,
279-
// "Error" => DuplicateStrategy::Error,
280-
// "AggregateOrOverride" => DuplicateStrategy::AggregateOrOverride,
281-
// "Override" => DuplicateStrategy::Override,
282-
// _ => abort!(strategy, VALID_FORMAT),
283-
// }
284-
// }
285-
_ => bail!(field, "{VALID_FORMAT}"),
286320
}
287321
_ = parser.next_tt_comma();
288322
}
289323
}
324+
325+
if ident_span.is_none() && ident.is_none() {
326+
let ident_string = struct_ident.to_string();
327+
let mut out = String::with_capacity(ident_string.len());
328+
let mut ident_string = ident_string.chars();
329+
330+
out.extend(ident_string.next().into_iter().flat_map(char::to_lowercase));
331+
for c in ident_string {
332+
if c.is_uppercase() {
333+
out.push('_');
334+
out.extend(c.to_lowercase());
335+
} else {
336+
out.push(c);
337+
}
338+
}
339+
340+
ident = Some(Ident::new(&out, struct_ident.span()));
341+
}
342+
290343
Ok(Self {
291344
ident,
292345
aliases,
@@ -790,11 +843,11 @@ pub fn from_attr_derive(
790843
mut aliases,
791844
error: ref struct_error,
792845
// duplicate,
793-
} = StructAttrs::from_attrs(attrs)?;
846+
} = StructAttrs::from_attrs(&ident, attrs)?;
794847

795848
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
796849

797-
if let Some(attribute_ident) = attribute_ident.clone() {
850+
if let Some(ref attribute_ident) = attribute_ident {
798851
aliases.insert(0, attribute_ident.to_string());
799852
}
800853

@@ -815,7 +868,7 @@ pub fn from_attr_derive(
815868
fields: fields @ (Fields::Named(_) | Fields::Unnamed(_)),
816869
..
817870
}) => AttrField::parse_fields(fields, struct_error, attribute_ident)?,
818-
_ => bail!("only works on structs with named fields"),
871+
_ => bail!("only works on structs with fields"),
819872
};
820873

821874
let conflicts = conflicts.to_tokens(struct_error)?;

src/lib.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,23 +127,20 @@
127127
//!
128128
//! For helper attributes there is:
129129
//! - [`FromAttr::from_attributes`] which takes in an [`IntoIterator<Item = &'a
130-
//! syn::Attribute`](syn::Attribute)
131-
//! (e.g. a [`&Vec<syn::Attribute>`](syn::Attribute)). Most useful for derive
132-
//! macros.
130+
//! syn::Attribute`](syn::Attribute) (e.g. a
131+
//! [`&Vec<syn::Attribute>`](syn::Attribute)). Most useful for derive macros.
133132
//! - [`FromAttr::remove_attributes`] which takes a [`&mut
134-
//! Vec<syn::Attribute>`](syn::Attribute)
135-
//! and does not only parse the attributes, but also removes those matching.
136-
//! Useful for helper attributes for attribute macros, where the helper
137-
//! attributes need to be removed.
133+
//! Vec<syn::Attribute>`](syn::Attribute) and does not only parse the
134+
//! attributes, but also removes those matching. Useful for helper attributes
135+
//! for attribute macros, where the helper attributes need to be removed.
138136
//!
139137
//! For parsing a single [`TokenStream`] e.g. for parsing the proc macro input
140138
//! there are two ways:
141139
//!
142140
//! - [`FromAttr::from_args`] taking in a [`TokenStream`]
143141
//! - As `derive(FromAttr)` also derives [`Parse`] so you can use the
144-
//! [parse](mod@syn::parse) API,
145-
//! e.g. with [`parse_macro_input!(tokens as
146-
//! Attribute)`](syn::parse_macro_input!).
142+
//! [parse](mod@syn::parse) API, e.g. with [`parse_macro_input!(tokens as
143+
//! Attribute)`](syn::parse_macro_input!).
147144
//!
148145
//! [interpolator]: https://docs.rs/interpolator/latest/interpolator/
149146
use std::borrow::Borrow;

tests/derive.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use attribute_derive::parsing::AttributeNamed;
2-
use attribute_derive::{FlagOrValue, FromAttr};
2+
use attribute_derive::{FlagOrValue, FromAttr, AttributeIdent};
33
use proc_macro2::TokenStream;
44
use quote::quote;
55
use syn::parse::{ParseStream, Parser};
@@ -271,3 +271,22 @@ fn attribute_ident_option() {
271271
let attr: Attribute = parse_quote!(#[not_test("hello")]);
272272
assert!(Option::<Test>::from_attributes([attr]).unwrap().is_none());
273273
}
274+
275+
#[test]
276+
#[allow(unused)]
277+
fn attribute_ident_default() {
278+
#[derive(FromAttr)]
279+
#[attribute(ident = a)]
280+
struct WithIdent(String);
281+
282+
assert_eq!(WithIdent::IDENTS, ["a"]);
283+
284+
#[derive(FromAttr)]
285+
#[attribute(!ident)]
286+
struct WithOutIdent(String);
287+
static_assertions::assert_not_impl_any!(WithOutIdent: AttributeIdent);
288+
289+
#[derive(FromAttr)]
290+
struct WithDefault(String);
291+
assert_eq!(WithDefault::IDENTS, ["with_default"]);
292+
}

0 commit comments

Comments
 (0)