1#![deny(rust_2018_idioms, nonstandard_style)]
12#![warn(future_incompatible)]
13#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
14#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
15
16use proc_macro::TokenStream;
17use quote::quote;
18use syn::parse::Parser as _;
19
20type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
21
22#[proc_macro_attribute]
34pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
35 let mut input = match syn::parse::<syn::ItemFn>(item.clone()) {
36 Ok(input) => input,
37 Err(err) => return input_and_compile_error(item, err),
39 };
40
41 let parser = AttributeArgs::parse_terminated;
42 let args = match parser.parse(args.clone()) {
43 Ok(args) => args,
44 Err(err) => return input_and_compile_error(args, err),
45 };
46
47 let attrs = &input.attrs;
48 let vis = &input.vis;
49 let sig = &mut input.sig;
50 let body = &input.block;
51
52 if sig.asyncness.is_none() {
53 return syn::Error::new_spanned(
54 sig.fn_token,
55 "the async keyword is missing from the function declaration",
56 )
57 .to_compile_error()
58 .into();
59 }
60
61 let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
62
63 for arg in &args {
64 match arg {
65 syn::Meta::NameValue(syn::MetaNameValue {
66 path,
67 value:
68 syn::Expr::Lit(syn::ExprLit {
69 lit: syn::Lit::Str(lit),
70 ..
71 }),
72 ..
73 }) => match path
74 .get_ident()
75 .map(|i| i.to_string().to_lowercase())
76 .as_deref()
77 {
78 Some("system") => match lit.parse() {
79 Ok(path) => system = path,
80 Err(_) => {
81 return syn::Error::new_spanned(lit, "Expected path")
82 .to_compile_error()
83 .into();
84 }
85 },
86 _ => {
87 return syn::Error::new_spanned(arg, "Unknown attribute specified")
88 .to_compile_error()
89 .into();
90 }
91 },
92
93 _ => {
94 return syn::Error::new_spanned(arg, "Unknown attribute specified")
95 .to_compile_error()
96 .into();
97 }
98 }
99 }
100
101 sig.asyncness = None;
102
103 (quote! {
104 #(#attrs)*
105 #vis #sig {
106 <#system>::new().block_on(async move { #body })
107 }
108 })
109 .into()
110}
111
112#[proc_macro_attribute]
122pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
123 let mut input = match syn::parse::<syn::ItemFn>(item.clone()) {
124 Ok(input) => input,
125 Err(err) => return input_and_compile_error(item, err),
127 };
128
129 let parser = AttributeArgs::parse_terminated;
130 let args = match parser.parse(args.clone()) {
131 Ok(args) => args,
132 Err(err) => return input_and_compile_error(args, err),
133 };
134
135 let attrs = &input.attrs;
136 let vis = &input.vis;
137 let sig = &mut input.sig;
138 let body = &input.block;
139 let mut has_test_attr = false;
140
141 for attr in attrs {
142 if attr.path().is_ident("test") {
143 has_test_attr = true;
144 }
145 }
146
147 if sig.asyncness.is_none() {
148 return syn::Error::new_spanned(
149 input.sig.fn_token,
150 "the async keyword is missing from the function declaration",
151 )
152 .to_compile_error()
153 .into();
154 }
155
156 sig.asyncness = None;
157
158 let missing_test_attr = if has_test_attr {
159 quote! {}
160 } else {
161 quote! { #[::core::prelude::v1::test] }
162 };
163
164 let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
165
166 for arg in &args {
167 match arg {
168 syn::Meta::NameValue(syn::MetaNameValue {
169 path,
170 value:
171 syn::Expr::Lit(syn::ExprLit {
172 lit: syn::Lit::Str(lit),
173 ..
174 }),
175 ..
176 }) => match path
177 .get_ident()
178 .map(|i| i.to_string().to_lowercase())
179 .as_deref()
180 {
181 Some("system") => match lit.parse() {
182 Ok(path) => system = path,
183 Err(_) => {
184 return syn::Error::new_spanned(lit, "Expected path")
185 .to_compile_error()
186 .into();
187 }
188 },
189 _ => {
190 return syn::Error::new_spanned(arg, "Unknown attribute specified")
191 .to_compile_error()
192 .into();
193 }
194 },
195 _ => {
196 return syn::Error::new_spanned(arg, "Unknown attribute specified")
197 .to_compile_error()
198 .into();
199 }
200 }
201 }
202
203 (quote! {
204 #missing_test_attr
205 #(#attrs)*
206 #vis #sig {
207 <#system>::new().block_on(async { #body })
208 }
209 })
210 .into()
211}
212
213fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
220 let compile_err = TokenStream::from(err.to_compile_error());
221 item.extend(compile_err);
222 item
223}