structmeta_derive/
to_tokens.rs

1use crate::{syn_utils::*, to_tokens_attribute::*};
2use proc_macro2::{Delimiter, Ident, Span, TokenStream};
3use quote::{format_ident, quote, quote_spanned};
4use std::unreachable;
5use syn::{
6    parse_quote, spanned::Spanned, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, Result,
7};
8
9pub fn derive_to_tokens(input: DeriveInput) -> Result<TokenStream> {
10    let mut dump = false;
11    for attr in &input.attrs {
12        if attr.path().is_ident("to_tokens") {
13            let attr: ToTokensAttribute = attr.parse_args()?;
14            dump = dump || attr.dump.is_some();
15        }
16    }
17
18    let ts = match &input.data {
19        Data::Struct(data) => code_from_struct(data)?,
20        Data::Enum(data) => code_from_enum(data)?,
21        Data::Union(_) => {
22            bail!(Span::call_site(), "Not supported for union.")
23        }
24    };
25    let ts = quote! {
26        fn to_tokens(&self, tokens: &mut ::structmeta::helpers::exports::proc_macro2::TokenStream) {
27            #ts
28        }
29    };
30    let ts = impl_trait_result(
31        &input,
32        &parse_quote!(::structmeta::helpers::exports::quote::ToTokens),
33        &[],
34        ts,
35        dump,
36    )?;
37    Ok(ts)
38}
39fn code_from_struct(data: &DataStruct) -> Result<TokenStream> {
40    let p = to_pattern(quote!(Self), &data.fields);
41    let ts = code_from_fields(&data.fields)?;
42    let ts = quote! {
43        let #p = self;
44        #ts
45    };
46    Ok(ts)
47}
48fn code_from_enum(data: &DataEnum) -> Result<TokenStream> {
49    let mut arms = Vec::new();
50    for variant in &data.variants {
51        let ident = &variant.ident;
52        let p = to_pattern(quote!(Self::#ident), &variant.fields);
53        let code = code_from_fields(&variant.fields)?;
54        arms.push(quote! {
55            #p => {
56                #code
57            }
58        });
59    }
60    Ok(quote! {
61        match self {
62            #(#arms)*
63        }
64    })
65}
66fn to_pattern(self_path: TokenStream, fields: &Fields) -> TokenStream {
67    let mut vars = Vec::new();
68    match fields {
69        Fields::Unit => self_path,
70        Fields::Unnamed(_) => {
71            for (index, field) in fields.iter().enumerate() {
72                let var_ident = to_var_ident(Some(index), &field.ident);
73                vars.push(quote!(#var_ident));
74            }
75            quote!( #self_path( #(#vars,)*))
76        }
77        Fields::Named(_) => {
78            for field in fields.iter() {
79                let field_ident = &field.ident;
80                let var_ident = to_var_ident(None, field_ident);
81                vars.push(quote!(#field_ident : #var_ident));
82            }
83            quote!( #self_path { #(#vars,)* } )
84        }
85    }
86}
87struct Scope<'a> {
88    ts: TokenStream,
89    surround: Option<Surround<'a>>,
90}
91struct Surround<'a> {
92    ident: Ident,
93    field: &'a Field,
94    delimiter: Delimiter,
95}
96
97fn delimiter_from_open_char(value: char) -> Option<Delimiter> {
98    match value {
99        '[' => Some(Delimiter::Bracket),
100        '{' => Some(Delimiter::Brace),
101        '(' => Some(Delimiter::Parenthesis),
102        _ => None,
103    }
104}
105fn delimiter_from_close_char(value: char) -> Option<Delimiter> {
106    match value {
107        ']' => Some(Delimiter::Bracket),
108        '}' => Some(Delimiter::Brace),
109        ')' => Some(Delimiter::Parenthesis),
110        _ => None,
111    }
112}
113
114impl<'a> Scope<'a> {
115    fn new(surround: Option<Surround<'a>>) -> Self {
116        Self {
117            ts: TokenStream::new(),
118            surround,
119        }
120    }
121}
122fn close_char_of(delimiter: Delimiter) -> char {
123    match delimiter {
124        Delimiter::Bracket => ']',
125        Delimiter::Brace => '}',
126        Delimiter::Parenthesis => ')',
127        _ => unreachable!("unsupported delimiter"),
128    }
129}
130impl<'a> Surround<'a> {
131    fn token_type_ident(&self) -> Ident {
132        match self.delimiter {
133            Delimiter::Bracket => parse_quote!(Bracket),
134            Delimiter::Brace => parse_quote!(Brace),
135            Delimiter::Parenthesis => parse_quote!(Paren),
136            _ => unreachable!("unsupported delimiter"),
137        }
138    }
139}
140
141impl<'a> Scope<'a> {
142    fn into_code(self, delimiter: Option<Delimiter>, span: Span) -> Result<TokenStream> {
143        if let Some(s) = self.surround {
144            if let Some(delimiter) = delimiter {
145                if s.delimiter != delimiter {
146                    bail!(
147                        span,
148                        "mismatched closing delimiter expected `{}`, found `{}`.",
149                        close_char_of(s.delimiter),
150                        close_char_of(delimiter),
151                    )
152                }
153            }
154            let ident = &s.ident;
155            let ts = self.ts;
156            let ty = &s.field.ty;
157            let span = s.field.span();
158            let func = if is_macro_delimiter(ty) {
159                quote_spanned!(span=> ::structmeta::helpers::surround_macro_delimiter)
160            } else {
161                let ty = s.token_type_ident();
162                quote_spanned!(span=> ::structmeta::helpers::exports::syn::token::#ty::surround)
163            };
164            let code = quote_spanned!(span=> #func(#ident, tokens, |tokens| { #ts }););
165            return Ok(code);
166        }
167        Ok(quote!())
168    }
169}
170fn code_from_fields(fields: &Fields) -> Result<TokenStream> {
171    let mut scopes = vec![Scope::new(None)];
172    for (index, field) in fields.iter().enumerate() {
173        let ident = to_var_ident(Some(index), &field.ident);
174        let mut field_to_tokens = true;
175        for attr in &field.attrs {
176            if attr.path().is_ident("to_tokens") {
177                let attr: ToTokensAttribute = attr.parse_args()?;
178                for token in &attr.token {
179                    for c in token.value().chars() {
180                        if let Some(delimiter) = delimiter_from_open_char(c) {
181                            scopes.push(Scope::new(Some(Surround {
182                                ident: ident.clone(),
183                                field,
184                                delimiter,
185                            })));
186                            field_to_tokens = false;
187                        } else if let Some(delimiter) = delimiter_from_close_char(c) {
188                            let scope = scopes.pop().unwrap();
189                            scopes
190                                .last_mut()
191                                .unwrap()
192                                .ts
193                                .extend(scope.into_code(Some(delimiter), token.span())?);
194                        } else {
195                            bail!(
196                                token.span(),
197                                "expected '(', ')', '[', ']', '{{' or '}}', found `{}`.",
198                                c
199                            );
200                        }
201                    }
202                }
203            }
204        }
205        if field_to_tokens {
206            let code = quote_spanned!(field.span()=> ::structmeta::helpers::exports::quote::ToTokens::to_tokens(#ident, tokens););
207            scopes.last_mut().unwrap().ts.extend(code);
208        }
209    }
210    while let Some(scope) = scopes.pop() {
211        if scopes.is_empty() {
212            return Ok(scope.ts);
213        }
214        scopes
215            .last_mut()
216            .unwrap()
217            .ts
218            .extend(scope.into_code(None, Span::call_site())?);
219    }
220    unreachable!()
221}
222fn to_var_ident(index: Option<usize>, ident: &Option<Ident>) -> Ident {
223    if let Some(ident) = ident {
224        format_ident!("_{}", ident)
225    } else {
226        format_ident!("_{}", index.unwrap())
227    }
228}