Skip to main content

derive_field_attrs/
derive_field_attrs.rs

1use 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}