use proc_macro2::{Span, TokenStream, TokenTree}; use quote::{quote, quote_spanned, ToTokens}; use syn::parse::{Parse, ParseStream, Parser}; use syn::{braced, Attribute, Ident, Path, Signature, Visibility};
// syn::AttributeArgs does not implement syn::Parse type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
impl RuntimeFlavor { fn from_str(s: &str) -> Result<RuntimeFlavor, String> { match s { "current_thread" => Ok(RuntimeFlavor::CurrentThread), "multi_thread" => Ok(RuntimeFlavor::Threaded), "single_thread" => Err("The single threaded runtime flavor is called `current_thread`.".to_string()), "basic_scheduler" => Err("The `basic_scheduler` runtime flavor has been renamed to `current_thread`.".to_string()), "threaded_scheduler" => Err("The `threaded_scheduler` runtime flavor has been renamed to `multi_thread`.".to_string()),
_ => Err(format!("No such runtime flavor `{}`. The runtime flavors are `current_thread` and `multi_thread`.", s)),
}
}
}
/// Config used in case of the attribute not being able to build a valid config const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
flavor: RuntimeFlavor::CurrentThread,
worker_threads: None,
start_paused: None,
crate_name: None,
unhandled_panic: None,
};
let worker_threads = parse_int(worker_threads, span, "worker_threads")?; if worker_threads == 0 { return Err(syn::Error::new(span, "`worker_threads` may not be 0."));
} self.worker_threads = Some((worker_threads, span));
Ok(())
}
fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result<usize, syn::Error> { match int {
syn::Lit::Int(lit) => match lit.base10_parse::<usize>() {
Ok(value) => Ok(value),
Err(e) => Err(syn::Error::new(
span,
format!("Failed to parse value of `{}` as integer: {}", field, e),
)),
},
_ => Err(syn::Error::new(
span,
format!("Failed to parse value of `{}` as integer.", field),
)),
}
}
fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::Error> { match int {
syn::Lit::Str(s) => Ok(s.value()),
syn::Lit::Verbatim(s) => Ok(s.to_string()),
_ => Err(syn::Error::new(
span,
format!("Failed to parse value of `{}` as string.", field),
)),
}
}
fn parse_path(lit: syn::Lit, span: Span, field: &str) -> Result<Path, syn::Error> { match lit {
syn::Lit::Str(s) => { let err = syn::Error::new(
span,
format!( "Failed to parse value of `{}` as path: \"{}\"",
field,
s.value()
),
);
s.parse::<syn::Path>().map_err(|_| err.clone())
}
_ => Err(syn::Error::new(
span,
format!("Failed to parse value of `{}` as path.", field),
)),
}
}
fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Error> { match bool {
syn::Lit::Bool(b) => Ok(b.value),
_ => Err(syn::Error::new(
span,
format!("Failed to parse value of `{}` as bool.", field),
)),
}
}
fn build_config(
input: &ItemFn,
args: AttributeArgs,
is_test: bool,
rt_multi_thread: bool,
) -> Result<FinalConfig, syn::Error> { if input.sig.asyncness.is_none() { let msg = "the `async` keyword is missing from the function declaration"; return Err(syn::Error::new_spanned(input.sig.fn_token, msg));
}
letmut config = Configuration::new(is_test, rt_multi_thread); let macro_name = config.macro_name();
for arg in args { match arg {
syn::Meta::NameValue(namevalue) => { let ident = namevalue
.path
.get_ident()
.ok_or_else(|| {
syn::Error::new_spanned(&namevalue, "Must have specified ident")
})?
.to_string()
.to_lowercase(); let lit = match &namevalue.value {
syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit,
expr => return Err(syn::Error::new_spanned(expr, "Must be a literal")),
}; match ident.as_str() { "worker_threads" => {
config.set_worker_threads(lit.clone(), syn::spanned::Spanned::span(lit))?;
} "flavor" => {
config.set_flavor(lit.clone(), syn::spanned::Spanned::span(lit))?;
} "start_paused" => {
config.set_start_paused(lit.clone(), syn::spanned::Spanned::span(lit))?;
} "core_threads" => { let msg = "Attribute `core_threads` is renamed to `worker_threads`"; return Err(syn::Error::new_spanned(namevalue, msg));
} "crate" => {
config.set_crate_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
} "unhandled_panic" => {
config
.set_unhandled_panic(lit.clone(), syn::spanned::Spanned::span(lit))?;
}
name => { let msg = format!( "Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
name,
); return Err(syn::Error::new_spanned(namevalue, msg));
}
}
}
syn::Meta::Path(path) => { let name = path
.get_ident()
.ok_or_else(|| syn::Error::new_spanned(&path, "Must have specified ident"))?
.to_string()
.to_lowercase(); let msg = match name.as_str() { "threaded_scheduler" | "multi_thread" => {
format!( "Set the runtime flavor with #[{}(flavor = \"multi_thread\")].",
macro_name
)
} "basic_scheduler" | "current_thread" | "single_threaded" => {
format!( "Set the runtime flavor with #[{}(flavor = \"current_thread\")].",
macro_name
)
} "flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => {
format!("The `{}` attribute requires an argument.", name)
}
name => {
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.", name)
}
}; return Err(syn::Error::new_spanned(path, msg));
}
other => { return Err(syn::Error::new_spanned(
other, "Unknown attribute inside the macro",
));
}
}
}
// If type mismatch occurs, the current rustc points to the last statement. let (last_stmt_start_span, last_stmt_end_span) = { letmut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter();
// `Span` on stable Rust has a limitation that only points to the first // token, not the whole tokens. We can work around this limitation by // using the first/last span of the tokens like // `syn::Error::new_spanned` does. let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); let end = last_stmt.last().map_or(start, |t| t.span());
(start, end)
};
let crate_path = config
.crate_name
.map(ToTokens::into_token_stream)
.unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream());
let generated_attrs = if is_test {
quote! { #[::core::prelude::v1::test]
}
} else {
quote! {}
};
let body_ident = quote! { body }; let last_block = quote_spanned! {last_stmt_end_span=> #[allow(clippy::expect_used, clippy::diverging_sub_expression)]
{ return#rt
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(#body_ident);
}
};
let body = input.body();
// For test functions pin the body to the stack and use `Pin<&mut dyn // Future>` to reduce the amount of `Runtime::block_on` (and related // functions) copies we generate during compilation due to the generic // parameter `F` (the future to block on). This could have an impact on // performance, but because it's only for testing it's unlikely to be very // large. // // We don't do this for the main function as it should only be used once so // there will be no benefit. let body = if is_test { let output_type = match &input.sig.output { // For functions with no return value syn doesn't print anything, // but that doesn't work as `Output` for our boxed `Future`, so // default to `()` (the same type as the function output).
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
};
quote! { let body = async#body; #crate_path::pin!(body); let body: ::core::pin::Pin<&mutdyn ::core::future::Future<Output = #output_type>> = body;
}
} else {
quote! { let body = async#body;
}
};
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream { // If any of the steps for this macro fail, we still want to expand to an item that is as close // to the expected output as possible. This helps out IDEs such that completions and other // related features keep working. let input: ItemFn = match syn::parse2(item.clone()) {
Ok(it) => it,
Err(e) => return token_stream_with_error(item, e),
};
let config = if input.sig.ident == "main" && !input.sig.inputs.is_empty() { let msg = "the main function cannot accept arguments";
Err(syn::Error::new_spanned(&input.sig.ident, msg))
} else {
AttributeArgs::parse_terminated
.parse2(args)
.and_then(|args| build_config(&input, args, false, rt_multi_thread))
};
// Check whether given attribute is a test attribute of forms: // * `#[test]` // * `#[core::prelude::*::test]` or `#[::core::prelude::*::test]` // * `#[std::prelude::*::test]` or `#[::std::prelude::*::test]` fn is_test_attribute(attr: &Attribute) -> bool { let path = match &attr.meta {
syn::Meta::Path(path) => path,
_ => returnfalse,
}; let candidates = [
["core", "prelude", "*", "test"],
["std", "prelude", "*", "test"],
]; if path.leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments[0].arguments.is_none()
&& path.segments[0].ident == "test"
{ returntrue;
} elseif path.segments.len() != candidates[0].len() { returnfalse;
}
candidates.into_iter().any(|segments| {
path.segments.iter().zip(segments).all(|(segment, path)| {
segment.arguments.is_none() && (path == "*" || segment.ident == path)
})
})
}
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream { // If any of the steps for this macro fail, we still want to expand to an item that is as close // to the expected output as possible. This helps out IDEs such that completions and other // related features keep working. let input: ItemFn = match syn::parse2(item.clone()) {
Ok(it) => it,
Err(e) => return token_stream_with_error(item, e),
}; let config = iflet Some(attr) = input.attrs().find(|attr| is_test_attribute(attr)) { let msg = "second test attribute is supplied, consider removing or changing the order of your test attributes";
Err(syn::Error::new_spanned(attr, msg))
} else {
AttributeArgs::parse_terminated
.parse2(args)
.and_then(|args| build_config(&input, args, true, rt_multi_thread))
};
impl ItemFn { /// Access all attributes of the function item. fn attrs(&self) -> impl Iterator<Item = &Attribute> { self.outer_attrs.iter().chain(self.inner_attrs.iter())
}
/// Get the body of the function item in a manner so that it can be /// conveniently used with the `quote!` macro. fn body(&self) -> Body<'_> {
Body {
brace_token: self.brace_token,
stmts: &self.stmts,
}
}
/// Convert our local function item into a token stream. fn into_tokens( self,
generated_attrs: proc_macro2::TokenStream,
body: proc_macro2::TokenStream,
last_block: proc_macro2::TokenStream,
) -> TokenStream { letmut tokens = proc_macro2::TokenStream::new(); // Outer attributes are simply streamed as-is. for attr inself.outer_attrs {
attr.to_tokens(&mut tokens);
}
// Inner attributes require extra care, since they're not supported on // blocks (which is what we're expanded into) we instead lift them // outside of the function. This matches the behavior of `syn`. formut attr inself.inner_attrs {
attr.style = syn::AttrStyle::Outer;
attr.to_tokens(&mut tokens);
}
// Add generated macros at the end, so macros processed later are aware of them.
generated_attrs.to_tokens(&mut tokens);
impl Parse for ItemFn { #[inline] fn parse(input: ParseStream<'_>) -> syn::Result<Self> { // This parse implementation has been largely lifted from `syn`, with // the exception of: // * We don't have access to the plumbing necessary to parse inner // attributes in-place. // * We do our own statements parsing to avoid recursively parsing // entire statements and only look for the parts we're interested in.
let outer_attrs = input.call(Attribute::parse_outer)?; let vis: Visibility = input.parse()?; let sig: Signature = input.parse()?;
let content; let brace_token = braced!(content in input); let inner_attrs = Attribute::parse_inner(&content)?;
// Parse a single token tree and extend our current buffer with it. // This avoids parsing the entire content of the sub-tree.
buf.extend([content.parse::<TokenTree>()?]);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.