blob: 09f5a6907a9bf7873658e9c6f03c7b45574a495b [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Procedural macro crate for simplifying writing async entry points
//!
//! This crate should not be depended upon directly, but should be used through
//! the fuchsia_async crate, which re-exports all these symbols.
//!
//! Program entry points, by they `main` or `#[test]` functions, that want to
//! perform asynchronous actions typically involve writing a shim such as:
//!
//! ```
//! fn main() -> Result<(), Error> {
//! let mut executor = fasync::Executor::new().unwrap();
//! let actual_main = async {
//! // Actual main code
//! Ok(())
//! };
//! executor.run_singlethreaded(actual_main())
//! }
//! ```
//!
//! or
//!
//! ```
//! #[test]
//! fn test_foo() -> Result<(), Error> {
//! let mut executor = fasync::Executor::new().unwrap();
//! let test = async {
//! // Actual test code here
//! Ok(())
//! };
//! let mut test_fut = test();
//! pin_mut!(test_fut);
//! match executor.run_until_stalled(&mut test_fut) {
//! Poll::Pending => panic!("Test blocked"),
//! Poll::Ready(x) => x,
//! }
//! }
//! ```
//!
//! This crate defines attributes that allow for writing the above as just
//!
//! ```
//! #[fuchsia_async::run_singlethreaded]
//! async fn main() -> Result<(), failure::Error> {
//! // Actual main code
//! Ok(())
//! }
//! ```
//!
//! or
//!
//! ```
//!
//! #[fuchsia_async::run_until_stalled(test)]
//! async fn test_foo() -> Result<(), Error> {
//! // Actual test code here
//! Ok(())
//! }
//! ```
//!
//! Using the optional 'test' specifier is preferred to using `#[test]`, as spurious
//! compilation errors will be generated should `#[test]` be specified before
//! `#[fuchsia_async::run_until_stalled]`.
#![deny(warnings)]
extern crate proc_macro;
use {
proc_macro::TokenStream,
quote::{quote, quote_spanned},
syn::{
parse::{Error, Parse, ParseStream},
parse_macro_input,
},
};
mod kw {
syn::custom_keyword!(test);
}
fn common(item: TokenStream, run_executor: TokenStream, test: bool) -> TokenStream {
let item = parse_macro_input!(item as syn::ItemFn);
let syn::ItemFn {
attrs,
vis: _,
constness,
unsafety,
asyncness,
abi,
ident,
decl,
block,
} = item;
if let Err(e) = (|| {
// Disallow const, unsafe or abi linkage, generics etc
if let Some(c) = constness {
return Err(Error::new(c.span, "async entry may not be 'const'"));
}
if let Some(u) = unsafety {
return Err(Error::new(u.span, "async entry may not be 'unsafe'"));
}
if let Some(abi) = abi {
return Err(Error::new(
abi.extern_token.span,
"async entry may not have custom linkage",
));
}
if !decl.generics.params.is_empty() || decl.generics.where_clause.is_some() {
return Err(Error::new(
decl.fn_token.span,
"async entry may not have generics",
));
}
if !decl.inputs.is_empty() {
return Err(Error::new(
decl.paren_token.span,
"async entry takes no arguments",
));
}
if let Some(dot3) = decl.variadic {
return Err(Error::new(dot3.spans[0], "async entry may not be variadic"));
}
// Require the target function acknowledge it is async.
if asyncness.is_none() {
return Err(Error::new(
ident.span(),
"async entry must be declared as 'async'",
));
}
// Only allow on 'main' or 'test' functions
if ident.to_string() != "main"
&& !test
&& !attrs.iter().any(|a| {
a.parse_meta()
.map(|m| {
if let syn::Meta::Word(w) = m {
w == "test"
} else {
false
}
})
.unwrap_or(false)
}) {
return Err(Error::new(
ident.span(),
"async entry must a 'main' or '#[test]'.",
));
}
Ok(())
})() {
return e.to_compile_error().into();
}
let test = if test {
quote!{#[test]}
} else {
quote!{}
};
let run_executor = proc_macro2::TokenStream::from(run_executor);
let ret_type = decl.output;
let span = ident.span();
let output = quote_spanned!{span=>
// Preserve any original attributes.
#(#attrs)* #test
fn #ident () #ret_type {
async fn func() #ret_type {
#block
}
let mut executor = ::fuchsia_async::Executor::new().expect("Failed to create executor");
#run_executor
}
};
output.into()
}
/// Define an `async` function that should complete without stalling.
///
/// If the async function should stall then a `panic` will be raised. For example:
///
/// ```
/// #[fuchsia_async::run_until_stalled]
/// async fn this_will_fail_and_not_block() -> Result<(), failure::Error> {
/// let () = await!(future::empty());
/// Ok(())
/// }
/// ```
///
/// will cause an immediate panic instead of hanging.
///
/// This is mainly intended for testing, and takes an optional `test` argument.
///
/// ```
/// #[fuchsia_async::run_until_stalled(test)]
/// async fn test_foo() {}
/// ```
#[proc_macro_attribute]
pub fn run_until_stalled(attr: TokenStream, item: TokenStream) -> TokenStream {
let test = parse_macro_input!(attr as Option<kw::test>).is_some();
let run_executor = quote!{
let mut fut = func();
::fuchsia_async::pin_mut!(fut);
match executor.run_until_stalled(&mut fut) {
::core::task::Poll::Ready(result) => result,
_ => panic!("Stalled without completing. Did you mean to use 'run_singlethreaded'?"),
}
};
common(item, run_executor.into(), test)
}
/// Define an `async` function that should run to completion on a single thread.
///
/// Takes an optional `test` argument.
///
/// ```
/// #[fuchsia_async::run_singlethreaded(test)]
/// async fn test_foo() {}
/// ```
#[proc_macro_attribute]
pub fn run_singlethreaded(attr: TokenStream, item: TokenStream) -> TokenStream {
let test = parse_macro_input!(attr as Option<kw::test>).is_some();
let run_executor = quote!{
executor.run_singlethreaded(func())
};
common(item, run_executor.into(), test)
}
struct RunAttributes {
threads: usize,
test: bool,
}
impl Parse for RunAttributes {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
let threads = input.parse::<syn::LitInt>()?.value() as usize;
input.parse::<syn::Token![,]>()?;
let test = input.parse::<Option<kw::test>>()?.is_some();
Ok(RunAttributes { threads, test })
}
}
/// Define an `async` function that should run to completion on `N` threads.
///
/// Number of threads is configured by `#[fuchsia_async::run(N)]`, and can also
/// take an optional `test` argument.
///
/// ```
/// #[fuchsia_async::run(4, test)]
/// async fn test_foo() {}
/// ```
#[proc_macro_attribute]
pub fn run(attr: TokenStream, item: TokenStream) -> TokenStream {
let RunAttributes { threads, test } = parse_macro_input!(attr as RunAttributes);
let run_executor = quote!{
executor.run(func(), #threads)
};
common(item, run_executor.into(), test)
}