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