| 1 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
| 2 | |
| 3 | mod context; |
| 4 | mod expr; |
| 5 | #[cfg (feature = "type_analysis" )] |
| 6 | mod type_analysis; |
| 7 | mod visitor; |
| 8 | |
| 9 | use proc_macro2::TokenStream; |
| 10 | use quote::ToTokens as _; |
| 11 | #[cfg (feature = "type_analysis" )] |
| 12 | use syn::Pat; |
| 13 | use syn::{ |
| 14 | AngleBracketedGenericArguments, Error, Expr, ExprClosure, GenericArgument, Item, ItemEnum, |
| 15 | ItemFn, Local, LocalInit, PathArguments, ReturnType, Stmt, Type, TypePath, |
| 16 | }; |
| 17 | |
| 18 | use self::{ |
| 19 | context::{Context, VisitLastMode, VisitMode, DEFAULT_MARKER}, |
| 20 | expr::child_expr, |
| 21 | }; |
| 22 | use crate::utils::{block, expr_block, path_eq, replace_expr}; |
| 23 | |
| 24 | /// The attribute name. |
| 25 | const NAME: &str = "auto_enum" ; |
| 26 | /// The annotation for recursively parsing. |
| 27 | const NESTED: &str = "nested" ; |
| 28 | /// The annotation for skipping branch. |
| 29 | const NEVER: &str = "never" ; |
| 30 | |
| 31 | pub(crate) fn attribute(args: TokenStream, input: TokenStream) -> TokenStream { |
| 32 | let mut cx = match Context::root(input.clone(), args) { |
| 33 | Ok(cx) => cx, |
| 34 | Err(e) => return e.to_compile_error(), |
| 35 | }; |
| 36 | |
| 37 | match syn::parse2::<Stmt>(input.clone()) { |
| 38 | Ok(mut stmt) => { |
| 39 | expand_parent_stmt(&mut cx, &mut stmt); |
| 40 | cx.check().map(|()| stmt.into_token_stream()) |
| 41 | } |
| 42 | Err(e) => match syn::parse2::<Expr>(input) { |
| 43 | Err(_e) => { |
| 44 | cx.error(e); |
| 45 | cx.error(format_err!( |
| 46 | cx.span, |
| 47 | "may only be used on expression, statement, or function" |
| 48 | )); |
| 49 | cx.check().map(|()| unreachable!()) |
| 50 | } |
| 51 | Ok(mut expr) => { |
| 52 | expand_parent_expr(&mut cx, &mut expr, false); |
| 53 | cx.check().map(|()| expr.into_token_stream()) |
| 54 | } |
| 55 | }, |
| 56 | } |
| 57 | .unwrap_or_else(Error::into_compile_error) |
| 58 | } |
| 59 | |
| 60 | fn expand_expr(cx: &mut Context, expr: &mut Expr) { |
| 61 | let expr = match expr { |
| 62 | Expr::Closure(ExprClosure { body, .. }) if cx.visit_last() => { |
| 63 | let count = visitor::visit_fn(cx, &mut **body); |
| 64 | if count.try_ >= 2 { |
| 65 | cx.visit_mode = VisitMode::Try; |
| 66 | } else { |
| 67 | cx.visit_mode = VisitMode::Return(count.return_); |
| 68 | } |
| 69 | &mut **body |
| 70 | } |
| 71 | _ => expr, |
| 72 | }; |
| 73 | |
| 74 | child_expr(cx, expr); |
| 75 | |
| 76 | #[cfg (feature = "type_analysis" )] |
| 77 | { |
| 78 | if let VisitMode::Return(count) = cx.visit_mode { |
| 79 | if cx.args.is_empty() && cx.variant_is_empty() && count < 2 { |
| 80 | cx.dummy(expr); |
| 81 | return; |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | cx.visitor(expr); |
| 87 | } |
| 88 | |
| 89 | fn build_expr(expr: &mut Expr, item: ItemEnum) { |
| 90 | replace_expr(this:expr, |expr: Expr| { |
| 91 | expr_block(block(stmts:vec![Stmt::Item(item.into()), Stmt::Expr(expr, None)])) |
| 92 | }); |
| 93 | } |
| 94 | |
| 95 | // ----------------------------------------------------------------------------- |
| 96 | // Expand statement or expression in which `#[auto_enum]` was directly used. |
| 97 | |
| 98 | fn expand_parent_stmt(cx: &mut Context, stmt: &mut Stmt) { |
| 99 | match stmt { |
| 100 | Stmt::Expr(expr: &mut Expr, semi: &mut Option) => expand_parent_expr(cx, expr, has_semi:semi.is_some()), |
| 101 | Stmt::Local(local: &mut Local) => expand_parent_local(cx, local), |
| 102 | Stmt::Item(Item::Fn(item: &mut ItemFn)) => expand_parent_item_fn(cx, item), |
| 103 | Stmt::Item(item: &mut Item) => { |
| 104 | cx.error(message:format_err!(item, "may only be used on expression, statement, or function" )); |
| 105 | } |
| 106 | Stmt::Macro(_) => {} |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | fn expand_parent_expr(cx: &mut Context, expr: &mut Expr, has_semi: bool) { |
| 111 | if has_semi { |
| 112 | cx.visit_last_mode = VisitLastMode::Never; |
| 113 | } |
| 114 | |
| 115 | if cx.is_dummy() { |
| 116 | cx.dummy(node:expr); |
| 117 | return; |
| 118 | } |
| 119 | |
| 120 | expand_expr(cx, expr); |
| 121 | |
| 122 | cx.build(|item: ItemEnum| build_expr(expr, item)); |
| 123 | } |
| 124 | |
| 125 | fn expand_parent_local(cx: &mut Context, local: &mut Local) { |
| 126 | #[cfg (feature = "type_analysis" )] |
| 127 | { |
| 128 | if let Pat::Type(pat) = &mut local.pat { |
| 129 | if cx.collect_impl_trait(&mut pat.ty) { |
| 130 | local.pat = (*pat.pat).clone(); |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | if cx.is_dummy() { |
| 136 | cx.dummy(local); |
| 137 | return; |
| 138 | } |
| 139 | |
| 140 | let expr = if let Some(LocalInit { expr, .. }) = &mut local.init { |
| 141 | &mut **expr |
| 142 | } else { |
| 143 | cx.error(format_err!( |
| 144 | local, |
| 145 | "the `#[auto_enum]` attribute is not supported uninitialized let statement" |
| 146 | )); |
| 147 | return; |
| 148 | }; |
| 149 | |
| 150 | expand_expr(cx, expr); |
| 151 | |
| 152 | cx.build(|item| build_expr(expr, item)); |
| 153 | } |
| 154 | |
| 155 | fn expand_parent_item_fn(cx: &mut Context, item: &mut ItemFn) { |
| 156 | let ItemFn { sig, block, .. } = item; |
| 157 | if let ReturnType::Type(_, ty) = &mut sig.output { |
| 158 | match &**ty { |
| 159 | // `return` |
| 160 | Type::ImplTrait(_) if cx.visit_last_mode != VisitLastMode::Never => { |
| 161 | let count = visitor::visit_fn(cx, &mut **block); |
| 162 | cx.visit_mode = VisitMode::Return(count.return_); |
| 163 | } |
| 164 | |
| 165 | // `?` operator |
| 166 | Type::Path(TypePath { qself: None, path }) |
| 167 | if cx.visit_last_mode != VisitLastMode::Never => |
| 168 | { |
| 169 | let ty = path.segments.last().unwrap(); |
| 170 | match &ty.arguments { |
| 171 | // `Result<T, impl Trait>` |
| 172 | PathArguments::AngleBracketed(AngleBracketedGenericArguments { |
| 173 | colon2_token: None, |
| 174 | args, |
| 175 | .. |
| 176 | }) if args.len() == 2 |
| 177 | && path_eq(path, &["std" , "core" ], &["result" , "Result" ]) => |
| 178 | { |
| 179 | if let ( |
| 180 | GenericArgument::Type(_), |
| 181 | GenericArgument::Type(Type::ImplTrait(_)), |
| 182 | ) = (&args[0], &args[1]) |
| 183 | { |
| 184 | let count = visitor::visit_fn(cx, &mut **block); |
| 185 | if count.try_ >= 2 { |
| 186 | cx.visit_mode = VisitMode::Try; |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | _ => {} |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | _ => {} |
| 195 | } |
| 196 | |
| 197 | #[cfg (feature = "type_analysis" )] |
| 198 | cx.collect_impl_trait(&mut *ty); |
| 199 | } |
| 200 | |
| 201 | if cx.is_dummy() { |
| 202 | cx.dummy(item); |
| 203 | return; |
| 204 | } |
| 205 | |
| 206 | match item.block.stmts.last_mut() { |
| 207 | Some(Stmt::Expr(expr, None)) => child_expr(cx, expr), |
| 208 | Some(_) => {} |
| 209 | None => cx.error(format_err!( |
| 210 | item.block, |
| 211 | "the `#[auto_enum]` attribute is not supported empty functions" |
| 212 | )), |
| 213 | } |
| 214 | |
| 215 | #[cfg (feature = "type_analysis" )] |
| 216 | { |
| 217 | if let VisitMode::Return(count) = cx.visit_mode { |
| 218 | if cx.args.is_empty() && cx.variant_is_empty() && count < 2 { |
| 219 | cx.dummy(item); |
| 220 | return; |
| 221 | } |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | cx.visitor(item); |
| 226 | |
| 227 | cx.build(|i| item.block.stmts.insert(0, Stmt::Item(i.into()))); |
| 228 | } |
| 229 | |