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}