1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use std::{cell::RefCell, collections::hash_map::DefaultHasher, hash::Hasher, iter, mem, thread};
4
5use proc_macro2::TokenStream;
6use quote::format_ident;
7#[cfg(feature = "type_analysis")]
8use syn::Type;
9use syn::{
10 parse::{Parse, ParseStream},
11 parse_quote, Attribute, Error, Expr, Ident, ItemEnum, Macro, Path, Result, Token,
12};
13
14use super::visitor::{Dummy, Visitor};
15use crate::utils::{expr_call, path, replace_expr, unit, Node};
16
17// =================================================================================================
18// Context
19
20/// Config for related to `visitor::Visitor` type.
21#[derive(Clone, Copy, PartialEq)]
22pub(super) enum VisitMode {
23 Default,
24 Return(/* count */ usize),
25 Try,
26}
27
28/// Config for related to `expr::child_expr`.
29#[derive(Clone, Copy, PartialEq)]
30pub(super) enum VisitLastMode {
31 Default,
32 /*
33 local: `let .. = || {};`
34 or
35 expr: `|| {}`
36 not
37 item_fn: `fn _() -> Fn*() { || {} }`
38 */
39 /// `Stmt::Semi(..)` - never visit last expr
40 Never,
41}
42
43/// The default identifier of expression level marker.
44pub(super) const DEFAULT_MARKER: &str = "marker";
45
46pub(super) struct Context {
47 builder: Builder,
48
49 /// The identifier of the marker macro of the current scope.
50 pub(super) current_marker: String,
51 /// All marker macro identifiers that may have effects on the current scope.
52 markers: Vec<String>,
53
54 // TODO: we may be able to replace some fields based on depth.
55 // depth: isize,
56 /// Currently, this is basically the same as `self.markers.len() == 1`.
57 root: bool,
58 /// This is `true` if other `auto_enum` attribute exists in the current scope.
59 pub(super) has_child: bool,
60
61 pub(super) visit_mode: VisitMode,
62 pub(super) visit_last_mode: VisitLastMode,
63
64 /// Span passed to `syn::Error::new_spanned`.
65 pub(super) span: TokenStream,
66 // - `None`: during checking.
67 // - `Some(None)`: there are no errors.
68 // - `Some(Some)`: there are errors.
69 #[allow(clippy::option_option)]
70 error: RefCell<Option<Option<Error>>>,
71
72 pub(super) args: Vec<Path>,
73 // if "type_analysis" feature is disabled, this field is always empty.
74 traits: Vec<Path>,
75}
76
77impl Context {
78 fn new(
79 span: TokenStream,
80 args: TokenStream,
81 root: bool,
82 mut markers: Vec<String>,
83 diagnostic: Option<Error>,
84 ) -> Result<Self> {
85 let Args { args, marker } = syn::parse2(args)?;
86
87 let current_marker = if let Some(marker) = marker {
88 // Currently, there is no reason to preserve the span, so convert `Ident` to `String`.
89 // This should probably be more efficient than calling `to_string` for each comparison.
90 // https://github.com/alexcrichton/proc-macro2/blob/1.0.1/src/wrapper.rs#L706
91 let marker_string = marker.to_string();
92 if markers.contains(&marker_string) {
93 bail!(
94 marker,
95 "a custom marker name is specified that duplicated the name already used in the parent scope",
96 );
97 }
98 marker_string
99 } else {
100 DEFAULT_MARKER.to_string()
101 };
102
103 markers.push(current_marker.clone());
104
105 Ok(Self {
106 builder: Builder::new(&span),
107 current_marker,
108 markers,
109 root,
110 has_child: false,
111 visit_mode: VisitMode::Default,
112 visit_last_mode: VisitLastMode::Default,
113 span,
114 error: RefCell::new(Some(diagnostic)),
115 args,
116 traits: vec![],
117 })
118 }
119
120 /// Make a new `Context` as a root.
121 pub(super) fn root(span: TokenStream, args: TokenStream) -> Result<Self> {
122 Self::new(span, args, true, Vec::with_capacity(1), None)
123 }
124
125 /// Make a new `Context` as a child based on a parent context `self`.
126 pub(super) fn make_child(&mut self, span: TokenStream, args: TokenStream) -> Result<Self> {
127 debug_assert!(self.has_child);
128 Self::new(
129 span,
130 args,
131 false,
132 mem::take(&mut self.markers),
133 self.error.borrow_mut().as_mut().unwrap().take(),
134 )
135 }
136
137 /// Merge a child `Context` into a parent context `self`.
138 pub(super) fn join_child(&mut self, mut child: Self) {
139 debug_assert!(self.markers.is_empty());
140 child.markers.pop();
141 mem::swap(&mut self.markers, &mut child.markers);
142
143 if let Some(message) = child.error.borrow_mut().take().unwrap() {
144 self.error(message);
145 }
146 }
147
148 pub(super) fn error(&self, message: Error) {
149 match self.error.borrow_mut().as_mut().unwrap() {
150 Some(base) => base.combine(message),
151 error @ None => *error = Some(message),
152 }
153 }
154
155 pub(super) fn check(self) -> Result<()> {
156 match self.error.borrow_mut().take().unwrap() {
157 Some(e) => Err(e),
158 None => Ok(()),
159 }
160 }
161
162 /// Returns `true` if one or more errors occurred.
163 pub(super) fn has_error(&self) -> bool {
164 self.error.borrow().as_ref().unwrap().is_some()
165 }
166
167 pub(super) fn visit_last(&self) -> bool {
168 self.visit_last_mode != VisitLastMode::Never && self.visit_mode != VisitMode::Try
169 }
170
171 /// Even if this is `false`, there are cases where this `auto_enum` attribute is handled as a
172 /// dummy. e.g., If `self.has_child && self.builder.variants.is_empty()` is true, this
173 /// `auto_enum` attribute is handled as a dummy.
174 pub(super) fn is_dummy(&self) -> bool {
175 // `auto_enum` attribute with no argument is handled as a dummy.
176 // if "type_analysis" feature is disabled, `self.traits` field is always empty.
177 self.args.is_empty() && self.traits.is_empty()
178 }
179
180 #[cfg(feature = "type_analysis")]
181 pub(super) fn variant_is_empty(&self) -> bool {
182 self.builder.variants.is_empty()
183 }
184
185 /// Returns `true` if `expr` is the marker macro that may have effects on the current scope.
186 pub(super) fn is_marker_expr(&self, expr: &Expr) -> bool {
187 match expr {
188 Expr::Macro(expr) => self.is_marker_macro(&expr.mac),
189 _ => false,
190 }
191 }
192
193 /// Returns `true` if `mac` is the marker macro that may have effects on the current scope.
194 pub(super) fn is_marker_macro(&self, mac: &Macro) -> bool {
195 let exact = self.is_marker_macro_exact(mac);
196 if exact || self.root {
197 return exact;
198 }
199
200 self.markers.iter().any(|marker| mac.path.is_ident(marker))
201 }
202
203 /// Returns `true` if `mac` is the marker macro of the current scope.
204 pub(super) fn is_marker_macro_exact(&self, mac: &Macro) -> bool {
205 mac.path.is_ident(&self.current_marker)
206 }
207
208 /// from `<expr>` into `Enum::VariantN(<expr>)`
209 pub(super) fn next_expr(&mut self, expr: Expr) -> Expr {
210 self.next_expr_with_attrs(vec![], expr)
211 }
212
213 /// from `<expr>` into `<attrs> Enum::VariantN(<expr>)`
214 pub(super) fn next_expr_with_attrs(&mut self, attrs: Vec<Attribute>, expr: Expr) -> Expr {
215 self.builder.next_expr(attrs, expr)
216 }
217
218 pub(super) fn replace_boxed_expr(&mut self, expr: &mut Option<Box<Expr>>) {
219 replace_expr(expr.get_or_insert_with(|| Box::new(unit())), |expr| {
220 if self.is_marker_expr(&expr) {
221 // Skip if `<expr>` is a marker macro.
222 expr
223 } else {
224 self.next_expr(expr)
225 }
226 });
227 }
228
229 // visitors
230
231 pub(super) fn visitor(&mut self, node: &mut impl Node) {
232 node.visited(&mut Visitor::new(self));
233 }
234
235 pub(super) fn dummy(&mut self, node: &mut impl Node) {
236 debug_assert!(self.args.is_empty());
237
238 node.visited(&mut Dummy::new(self));
239 }
240
241 // build
242
243 pub(super) fn build(&mut self, f: impl FnOnce(ItemEnum)) {
244 // As we know that an error will occur, it does not matter if there are not enough variants.
245 if !self.has_error() {
246 match self.builder.variants.len() {
247 1 => {}
248 0 if !self.has_child => {}
249 _ => {
250 if !self.builder.variants.is_empty() {
251 f(self.builder.build(&self.args, &self.traits));
252 }
253 return;
254 }
255 }
256
257 let (msg1, msg2) = match self.visit_last_mode {
258 VisitLastMode::Default => {
259 ("branches or marker macros in total", "branch or marker macro")
260 }
261 VisitLastMode::Never => ("marker macros", "marker macro"),
262 };
263 self.error(format_err!(
264 self.span,
265 "`#[auto_enum]` is required two or more {}, there is {} {} in this statement",
266 msg1,
267 if self.builder.variants.is_empty() { "no" } else { "only one" },
268 msg2
269 ));
270 }
271 }
272
273 // type_analysis feature
274
275 #[cfg(feature = "type_analysis")]
276 pub(super) fn collect_impl_trait(&mut self, ty: &mut Type) -> bool {
277 super::type_analysis::collect_impl_trait(&self.args, &mut self.traits, ty)
278 }
279}
280
281impl Drop for Context {
282 fn drop(&mut self) {
283 if !thread::panicking() && self.error.borrow().is_some() {
284 panic!("context need to be checked");
285 }
286 }
287}
288
289// =================================================================================================
290// Args
291
292mod kw {
293 syn::custom_keyword!(marker);
294}
295
296struct Args {
297 args: Vec<Path>,
298 marker: Option<Ident>,
299}
300
301impl Parse for Args {
302 fn parse(input: ParseStream<'_>) -> Result<Self> {
303 let mut args = Vec::with_capacity(usize::from(!input.is_empty()));
304 let mut marker = None;
305 while !input.is_empty() {
306 if input.peek(kw::marker) && input.peek2(Token![=]) {
307 let i: kw::marker = input.parse()?;
308 let _: Token![=] = input.parse()?;
309 let ident: Ident = input.parse()?;
310 if marker.replace(ident).is_some() {
311 bail!(i, "duplicate `marker` argument");
312 }
313 } else {
314 args.push(input.parse()?);
315 }
316
317 if input.is_empty() {
318 break;
319 }
320 let _: Token![,] = input.parse()?;
321 }
322
323 Ok(Self { args, marker })
324 }
325}
326
327// =================================================================================================
328// Enum builder
329
330struct Builder {
331 ident: Ident,
332 variants: Vec<Ident>,
333}
334
335impl Builder {
336 fn new(input: &TokenStream) -> Self {
337 Self { ident: format_ident!("__Enum{}", hash(input)), variants: Vec::with_capacity(2) }
338 }
339
340 fn next_expr(&mut self, attrs: Vec<Attribute>, expr: Expr) -> Expr {
341 let variant = format_ident!("__Variant{}", self.variants.len());
342
343 let path =
344 path(iter::once(self.ident.clone().into()).chain(iter::once(variant.clone().into())));
345
346 self.variants.push(variant);
347
348 expr_call(attrs, path, expr)
349 }
350
351 fn build(&self, args: &[Path], traits: &[Path]) -> ItemEnum {
352 let derive = args.iter().chain(traits);
353 let ident = &self.ident;
354 let ty_generics = &self.variants;
355 let variants = &self.variants;
356 let fields = &self.variants;
357
358 parse_quote! {
359 #[allow(non_camel_case_types)]
360 #[::auto_enums::enum_derive(#(#derive),*)]
361 enum #ident<#(#ty_generics),*> {
362 #(#variants(#fields),)*
363 }
364 }
365 }
366}
367
368/// Returns the hash value of the input AST.
369fn hash(input: &TokenStream) -> u64 {
370 let mut hasher: DefaultHasher = DefaultHasher::new();
371 hasher.write(input.to_string().as_bytes());
372 hasher.finish()
373}
374