1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
    parse::{Parse, ParseStream},
    parse2, parse_macro_input, parse_quote,
    spanned::Spanned,
    AttrStyle, Attribute, Data, DeriveInput, Field, Fields, GenericParam, Generics, Ident, LitStr,
    Token, Type,
};

#[proc_macro_derive(Eg, attributes(eg))]
pub fn derive_eg(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let name = input.ident;

    let generics = add_trait_bounds(input.generics);
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let example = example(input.data);

    let expanded = quote! {
        impl #impl_generics eg::Eg for #name #ty_generics #where_clause {
            fn eg() -> Self {
                #example
            }
        }
    };

    proc_macro::TokenStream::from(expanded)
}

fn add_trait_bounds(mut generics: Generics) -> Generics {
    for param in &mut generics.params {
        if let GenericParam::Type(ref mut type_param) = *param {
            type_param.bounds.push(parse_quote!(eg::Eg));
        }
    }
    generics
}

fn example(data: Data) -> TokenStream {
    match data {
        Data::Struct(data) => match data.fields {
            Fields::Named(fields) => {
                let egs = fields.named.iter().map(create_eg);
                quote! {
                    Self {
                        #(#egs)*
                    }
                }
            }
            Fields::Unnamed(ref fields) => {
                let examples = fields.unnamed.iter().map(|field| {
                    let ty = &field.ty;
                    quote_spanned! { field.span() => #ty::eg() }
                });
                quote! {
                    Self(#(#examples),*)
                }
            }
            Fields::Unit => {
                quote!(Self)
            }
        },
        Data::Enum(_) => unimplemented!(),
        Data::Union(_) => unimplemented!(),
    }
}

fn create_eg(field: &Field) -> TokenStream {
    let name = field.ident.as_ref().unwrap();
    let expr = if let Some(attr) = get_attr(field) {
        custom_expr(field, attr)
    } else {
        let ty = &field.ty;
        quote! {
            #ty::eg()
        }
    };
    quote! {
        #name: #expr,
    }
}

fn get_attr(field: &Field) -> Option<&Attribute> {
    field.attrs.iter().find_map(|attr| {
        if !matches!(attr.style, AttrStyle::Outer) {
            return None;
        }
        (attr.path.get_ident()?.to_string().as_str() == "eg").then(|| attr)
    })
}

fn custom_expr(field: &Field, attr: &Attribute) -> TokenStream {
    let custom_eg: CustomExpr = parse2(attr.tokens.clone()).unwrap();
    let literal = custom_eg.string;
    if let Type::Path(type_path) = &field.ty {
        if let Some(ty_ident) = type_path.path.get_ident() {
            if ty_ident == "String" {
                return quote! {
                    #literal.to_string()
                };
            }
        }
    };
    let expr: TokenStream = literal.value().parse().unwrap();
    if let Ok(ident) = parse2::<Ident>(expr.clone()) {
        quote! {
            #ident()
        }
    } else {
        expr
    }
}

struct CustomExpr {
    string: LitStr,
}

impl Parse for CustomExpr {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        input.parse::<Token![=]>()?;
        Ok(Self {
            string: input.parse()?,
        })
    }
}