actix_macros/
lib.rs

1//! Macros for Actix system and runtime.
2//!
3//! The [`actix-rt`](https://docs.rs/actix-rt) crate must be available for macro output to compile.
4//!
5//! # Entry-point
6//! See docs for the [`#[main]`](macro@main) macro.
7//!
8//! # Tests
9//! See docs for the [`#[test]`](macro@test) macro.
10
11#![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/// Marks async entry-point function to be executed by Actix system.
23///
24/// # Examples
25/// ```
26/// #[actix_rt::main]
27/// async fn main() {
28///     println!("Hello world");
29/// }
30/// ```
31// #[allow(clippy::needless_doctest_main)]
32// #[cfg(not(test))] // Work around for rust-lang/rust#62127
33#[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        // on parse err, make IDEs happy; see fn docs
38        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/// Marks async test function to be executed in an Actix system.
113///
114/// # Examples
115/// ```
116/// #[actix_rt::test]
117/// async fn my_test() {
118///     assert!(true);
119/// }
120/// ```
121#[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        // on parse err, make IDEs happy; see fn docs
126        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
213/// Converts the error to a token stream and appends it to the original input.
214///
215/// Returning the original input in addition to the error is good for IDEs which can gracefully
216/// recover and show more precise errors within the macro body.
217///
218/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
219fn 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}