use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; use syn::parse::{Parse, ParseStream}; use syn::{parse_quote, Expr, Ident, Pat, Token};
mod kw {
syn::custom_keyword!(complete);
}
struct Select { // span of `complete`, then expression after `=> ...`
complete: Option<Expr>,
default: Option<Expr>,
normal_fut_exprs: Vec<Expr>,
normal_fut_handlers: Vec<(Pat, Expr)>,
}
while !input.is_empty() { let case_kind = if input.peek(kw::complete) { // `complete` if select.complete.is_some() { return Err(input.error("multiple `complete` cases found, only one allowed"));
}
input.parse::<kw::complete>()?;
CaseKind::Complete
} elseif input.peek(Token![default]) { // `default` if select.default.is_some() { return Err(input.error("multiple `default` cases found, only one allowed"));
}
input.parse::<Ident>()?;
CaseKind::Default
} else { // `<pat> = <expr>` let pat = Pat::parse_multi_with_leading_vert(input)?;
input.parse::<Token![=]>()?; let expr = input.parse()?;
CaseKind::Normal(pat, expr)
};
// `=> <expr>`
input.parse::<Token![=>]>()?; let expr = input.parse::<Expr>()?;
// Commas after the expression are only optional if it's a `Block` // or it is the last branch in the `match`. let is_block = match expr {
Expr::Block(_) => true,
_ => false,
}; if is_block || input.is_empty() {
input.parse::<Option<Token![,]>>()?;
} else {
input.parse::<Token![,]>()?;
}
// Enum over all the cases in which the `select!` waiting has completed and the result // can be processed. // // `enum __PrivResult<_1, _2, ...> { _1(_1), _2(_2), ..., Complete }` fn declare_result_enum(
result_ident: Ident,
variants: usize,
complete: bool,
span: Span,
) -> (Vec<Ident>, syn::ItemEnum) { // "_0", "_1", "_2" let variant_names: Vec<Ident> =
(0..variants).map(|num| format_ident!("_{}", num, span = span)).collect();
let type_parameters = &variant_names; let variants = &variant_names;
let complete_variant = if complete { Some(quote!(Complete)) } else { None };
fn select_inner(input: TokenStream, random: bool) -> TokenStream { let parsed = syn::parse_macro_input!(input as Select);
// should be def_site, but that's unstable let span = Span::call_site();
let enum_ident = Ident::new("__PrivResult", span);
let (variant_names, enum_item) = declare_result_enum(
enum_ident.clone(),
parsed.normal_fut_exprs.len(),
parsed.complete.is_some(),
span,
);
// bind non-`Ident` future exprs w/ `let` letmut future_let_bindings = Vec::with_capacity(parsed.normal_fut_exprs.len()); let bound_future_names: Vec<_> = parsed
.normal_fut_exprs
.into_iter()
.zip(variant_names.iter())
.map(|(expr, variant_name)| { match expr {
syn::Expr::Path(path) => { // Don't bind futures that are already a path. // This prevents creating redundant stack space // for them. // Passing Futures by path requires those Futures to implement Unpin. // We check for this condition here in order to be able to // safely use Pin::new_unchecked(&mut #path) later on.
future_let_bindings.push(quote! {
__futures_crate::async_await::assert_fused_future(&#path);
__futures_crate::async_await::assert_unpin(&#path);
});
path
}
_ => { // Bind and pin the resulting Future on the stack. This is // necessary to support direct select! calls on !Unpin // Futures. The Future is not explicitly pinned here with // a Pin call, but assumed as pinned. The actual Pin is // created inside the poll() function below to defer the // creation of the temporary pointer, which would otherwise // increase the size of the generated Future. // Safety: This is safe since the lifetime of the Future // is totally constraint to the lifetime of the select! // expression, and the Future can't get moved inside it // (it is shadowed).
future_let_bindings.push(quote! { letmut#variant_name = #expr;
});
parse_quote! { #variant_name }
}
}
})
.collect();
// For each future, make an `&mut dyn FnMut(&mut Context<'_>) -> Option<Poll<__PrivResult<...>>` // to use for polling that individual future. These will then be put in an array. let poll_functions = bound_future_names.iter().zip(variant_names.iter()).map(
|(bound_future_name, variant_name)| { // Below we lazily create the Pin on the Future below. // This is done in order to avoid allocating memory in the generator // for the Pin variable. // Safety: This is safe because one of the following condition applies: // 1. The Future is passed by the caller by name, and we assert that // it implements Unpin. // 2. The Future is created in scope of the select! function and will // not be moved for the duration of it. It is thereby stack-pinned
quote! { letmut#variant_name = |__cx: &mut __futures_crate::task::Context<'_>| { letmut#bound_future_name = unsafe {
__futures_crate::Pin::new_unchecked(&mut#bound_future_name)
}; if __futures_crate::future::FusedFuture::is_terminated(&#bound_future_name) {
__futures_crate::None
} else {
__futures_crate::Some(__futures_crate::future::FutureExt::poll_unpin(
&mut#bound_future_name,
__cx,
).map(#enum_ident::#variant_name))
}
}; let#variant_name: &mutdyn FnMut(
&mut __futures_crate::task::Context<'_>
) -> __futures_crate::Option<__futures_crate::task::Poll<_>> = &mut#variant_name;
}
},
);
let none_polled = if parsed.complete.is_some() {
quote! {
__futures_crate::task::Poll::Ready(#enum_ident::Complete)
}
} else {
quote! {
panic!("all futures in select! were completed,\
but no `complete =>` handler was provided")
}
};
let branches = quote! { #branches #complete_branch
};
let await_select_fut = if parsed.default.is_some() { // For select! with default this returns the Poll result
quote! {
__poll_fn(&mut __futures_crate::task::Context::from_waker(
__futures_crate::task::noop_waker_ref()
))
}
} else {
quote! {
__futures_crate::future::poll_fn(__poll_fn).await
}
};
let execute_result_expr = iflet Some(default_expr) = &parsed.default { // For select! with default __select_result is a Poll, otherwise not
quote! { match __select_result {
__futures_crate::task::Poll::Ready(result) => match result { #branches
},
_ => #default_expr
}
}
} else {
quote! { match __select_result { #branches
}
}
};
let shuffle = if random {
quote! {
__futures_crate::async_await::shuffle(&mut __select_arr);
}
} else {
quote!()
};
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.