derive_field_attrs/
derive_field_attrs.rs1use attribute_dsl::{AttributeChain, substitute_infer_in_path};
2use proc_macro2::TokenStream;
3use quote::quote;
4use syn::{Data, DeriveInput, Field, Fields, parse_quote};
5
6fn main() -> syn::Result<()> {
7 let input: DeriveInput = parse_quote! {
8 struct Input {
9 #[attribute_dsl(RootType::<_>.first(1).)]
10 value: i32,
11 }
12 };
13
14 let expanded = expand_derive(&input)?;
15 assert_eq!(
16 compact(&expanded),
17 "implInput{fn__attribute_dsl_probe(){let_=RootType::<i32>::builder_for(stringify!(value)).first(1).raCompletionMarker;}}"
18 );
19
20 Ok(())
21}
22
23fn expand_derive(input: &DeriveInput) -> syn::Result<TokenStream> {
24 let struct_ident = &input.ident;
25 let mut field_expansions = Vec::new();
26
27 for field in named_fields(input)? {
28 field_expansions.extend(expand_field_attrs(field)?);
29 }
30
31 Ok(quote! {
32 impl #struct_ident {
33 fn __attribute_dsl_probe() {
34 #(#field_expansions)*
35 }
36 }
37 })
38}
39
40fn named_fields(
41 input: &DeriveInput,
42) -> syn::Result<&syn::punctuated::Punctuated<Field, syn::token::Comma>> {
43 match &input.data {
44 Data::Struct(data) => match &data.fields {
45 Fields::Named(fields) => Ok(&fields.named),
46 _ => Err(syn::Error::new_spanned(
47 input,
48 "example expects a struct with named fields",
49 )),
50 },
51 _ => Err(syn::Error::new_spanned(input, "example expects a struct")),
52 }
53}
54
55fn expand_field_attrs(field: &Field) -> syn::Result<Vec<TokenStream>> {
56 let field_ident = field
57 .ident
58 .as_ref()
59 .expect("named_fields only returns named struct fields");
60 let mut expansions = Vec::new();
61
62 for attr in &field.attrs {
63 if !attr.path().is_ident("attribute_dsl") {
64 continue;
65 }
66
67 let chain = attr.parse_args::<AttributeChain>()?;
68 let root = substitute_infer_in_path(chain.root_path(), &field.ty);
69
70 let calls = chain.calls().iter().map(|call| {
71 let method = call.method();
72 let turbofish = call.turbofish();
73 let args = call.args();
74 quote! { .#method #turbofish (#(#args),*) }
75 });
76
77 let completion = chain
78 .completion_marker()
79 .map(|marker| quote! { .#marker })
80 .unwrap_or_default();
81
82 expansions.push(quote! {
83 let _ = #root::builder_for(stringify!(#field_ident)) #(#calls)* #completion;
84 });
85 }
86
87 Ok(expansions)
88}
89
90fn compact(tokens: &TokenStream) -> String {
91 tokens
92 .to_string()
93 .chars()
94 .filter(|ch| !ch.is_whitespace())
95 .collect()
96}