Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support reexport for procedural macros #2124

Merged
merged 2 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ members = [
"futures-util",
"futures-test",

"futures/tests/macro-tests",
"futures/tests/macro-reexport",

"examples/functional",
"examples/imperative",
]
66 changes: 21 additions & 45 deletions futures-macro/src/join.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,17 @@ use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::{parenthesized, parse_quote, Expr, Ident, Token};

mod kw {
syn::custom_keyword!(futures_crate_path);
}
use syn::{Expr, Ident, Token};

#[derive(Default)]
struct Join {
futures_crate_path: Option<syn::Path>,
fut_exprs: Vec<Expr>,
}

impl Parse for Join {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let mut join = Join::default();

// When `futures_crate_path(::path::to::futures::lib)` is provided,
// it sets the path through which futures library functions will be
// accessed.
if input.peek(kw::futures_crate_path) {
input.parse::<kw::futures_crate_path>()?;
let content;
parenthesized!(content in input);
join.futures_crate_path = Some(content.parse()?);
}

while !input.is_empty() {
join.fut_exprs.push(input.parse::<Expr>()?);

Expand All @@ -43,7 +28,6 @@ impl Parse for Join {
}

fn bind_futures(
futures_crate: &syn::Path,
fut_exprs: Vec<Expr>,
span: Span,
) -> (Vec<TokenStream2>, Vec<Ident>) {
Expand All @@ -56,7 +40,7 @@ fn bind_futures(
future_let_bindings.push(quote! {
// Move future into a local so that it is pinned in one place and
// is no longer accessible by the end user.
let mut #name = #futures_crate::future::maybe_done(#expr);
let mut #name = __futures_crate::future::maybe_done(#expr);
});
name
})
Expand All @@ -69,39 +53,35 @@ fn bind_futures(
pub(crate) fn join(input: TokenStream) -> TokenStream {
let parsed = syn::parse_macro_input!(input as Join);

let futures_crate = parsed
.futures_crate_path
.unwrap_or_else(|| parse_quote!(::futures_util));

// should be def_site, but that's unstable
let span = Span::call_site();

let (future_let_bindings, future_names) = bind_futures(&futures_crate, parsed.fut_exprs, span);
let (future_let_bindings, future_names) = bind_futures(parsed.fut_exprs, span);

let poll_futures = future_names.iter().map(|fut| {
quote! {
__all_done &= #futures_crate::core_reexport::future::Future::poll(
unsafe { #futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }, __cx).is_ready();
__all_done &= __futures_crate::future::Future::poll(
unsafe { __futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }, __cx).is_ready();
}
});
let take_outputs = future_names.iter().map(|fut| {
quote! {
unsafe { #futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }.take_output().unwrap(),
unsafe { __futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }.take_output().unwrap(),
}
});

TokenStream::from(quote! { {
#( #future_let_bindings )*

#futures_crate::future::poll_fn(move |__cx: &mut #futures_crate::task::Context<'_>| {
__futures_crate::future::poll_fn(move |__cx: &mut __futures_crate::task::Context<'_>| {
let mut __all_done = true;
#( #poll_futures )*
if __all_done {
#futures_crate::core_reexport::task::Poll::Ready((
__futures_crate::task::Poll::Ready((
#( #take_outputs )*
))
} else {
#futures_crate::core_reexport::task::Poll::Pending
__futures_crate::task::Poll::Pending
}
}).await
} })
Expand All @@ -111,29 +91,25 @@ pub(crate) fn join(input: TokenStream) -> TokenStream {
pub(crate) fn try_join(input: TokenStream) -> TokenStream {
let parsed = syn::parse_macro_input!(input as Join);

let futures_crate = parsed
.futures_crate_path
.unwrap_or_else(|| parse_quote!(::futures_util));

// should be def_site, but that's unstable
let span = Span::call_site();

let (future_let_bindings, future_names) = bind_futures(&futures_crate, parsed.fut_exprs, span);
let (future_let_bindings, future_names) = bind_futures(parsed.fut_exprs, span);

let poll_futures = future_names.iter().map(|fut| {
quote! {
if #futures_crate::core_reexport::future::Future::poll(
unsafe { #futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }, __cx).is_pending()
if __futures_crate::future::Future::poll(
unsafe { __futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }, __cx).is_pending()
{
__all_done = false;
} else if unsafe { #futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }.output_mut().unwrap().is_err() {
} else if unsafe { __futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }.output_mut().unwrap().is_err() {
// `.err().unwrap()` rather than `.unwrap_err()` so that we don't introduce
// a `T: Debug` bound.
// Also, for an error type of ! any code after `err().unwrap()` is unreachable.
#[allow(unreachable_code)]
return #futures_crate::core_reexport::task::Poll::Ready(
#futures_crate::core_reexport::result::Result::Err(
unsafe { #futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().err().unwrap()
return __futures_crate::task::Poll::Ready(
__futures_crate::core_reexport::result::Result::Err(
unsafe { __futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().err().unwrap()
)
);
}
Expand All @@ -145,25 +121,25 @@ pub(crate) fn try_join(input: TokenStream) -> TokenStream {
// an `E: Debug` bound.
// Also, for an ok type of ! any code after `ok().unwrap()` is unreachable.
#[allow(unreachable_code)]
unsafe { #futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().ok().unwrap(),
unsafe { __futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().ok().unwrap(),
}
});

TokenStream::from(quote! { {
#( #future_let_bindings )*

#[allow(clippy::diverging_sub_expression)]
#futures_crate::future::poll_fn(move |__cx: &mut #futures_crate::task::Context<'_>| {
__futures_crate::future::poll_fn(move |__cx: &mut __futures_crate::task::Context<'_>| {
let mut __all_done = true;
#( #poll_futures )*
if __all_done {
#futures_crate::core_reexport::task::Poll::Ready(
#futures_crate::core_reexport::result::Result::Ok((
__futures_crate::task::Poll::Ready(
__futures_crate::core_reexport::result::Result::Ok((
#( #take_outputs )*
))
)
} else {
#futures_crate::core_reexport::task::Poll::Pending
__futures_crate::task::Poll::Pending
}
}).await
} })
Expand Down
8 changes: 4 additions & 4 deletions futures-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,24 @@ mod select;

/// The `join!` macro.
#[proc_macro_hack]
pub fn join(input: TokenStream) -> TokenStream {
pub fn join_internal(input: TokenStream) -> TokenStream {
crate::join::join(input)
}

/// The `try_join!` macro.
#[proc_macro_hack]
pub fn try_join(input: TokenStream) -> TokenStream {
pub fn try_join_internal(input: TokenStream) -> TokenStream {
crate::join::try_join(input)
}

/// The `select!` macro.
#[proc_macro_hack]
pub fn select(input: TokenStream) -> TokenStream {
pub fn select_internal(input: TokenStream) -> TokenStream {
crate::select::select(input)
}

/// The `select_biased!` macro.
#[proc_macro_hack]
pub fn select_biased(input: TokenStream) -> TokenStream {
pub fn select_biased_internal(input: TokenStream) -> TokenStream {
crate::select::select_biased(input)
}
55 changes: 20 additions & 35 deletions futures-macro/src/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{parenthesized, parse_quote, Expr, Ident, Pat, Token};
use syn::{parse_quote, Expr, Ident, Pat, Token};
use syn::parse::{Parse, ParseStream};

mod kw {
syn::custom_keyword!(complete);
syn::custom_keyword!(futures_crate_path);
}

struct Select {
futures_crate_path: Option<syn::Path>,
// span of `complete`, then expression after `=> ...`
complete: Option<Expr>,
default: Option<Expr>,
Expand All @@ -30,23 +28,12 @@ enum CaseKind {
impl Parse for Select {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let mut select = Select {
futures_crate_path: None,
complete: None,
default: None,
normal_fut_exprs: vec![],
normal_fut_handlers: vec![],
};

// When `futures_crate_path(::path::to::futures::lib)` is provided,
// it sets the path through which futures library functions will be
// accessed.
if input.peek(kw::futures_crate_path) {
input.parse::<kw::futures_crate_path>()?;
let content;
parenthesized!(content in input);
select.futures_crate_path = Some(content.parse()?);
}

while !input.is_empty() {
let case_kind = if input.peek(kw::complete) {
// `complete`
Expand Down Expand Up @@ -147,8 +134,6 @@ pub(crate) fn select_biased(input: TokenStream) -> TokenStream {
fn select_inner(input: TokenStream, random: bool) -> TokenStream {
let parsed = syn::parse_macro_input!(input as Select);

let futures_crate: syn::Path = parsed.futures_crate_path.unwrap_or_else(|| parse_quote!(::futures_util));

// should be def_site, but that's unstable
let span = Span::call_site();

Expand All @@ -175,8 +160,8 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream {
// 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);
__futures_crate::async_await::assert_fused_future(&#path);
__futures_crate::async_await::assert_unpin(&#path);
});
path
},
Expand Down Expand Up @@ -214,28 +199,28 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream {
// 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! {
let mut #variant_name = |__cx: &mut #futures_crate::task::Context<'_>| {
let mut #variant_name = |__cx: &mut __futures_crate::task::Context<'_>| {
let mut #bound_future_name = unsafe {
::core::pin::Pin::new_unchecked(&mut #bound_future_name)
};
if #futures_crate::future::FusedFuture::is_terminated(&#bound_future_name) {
if __futures_crate::future::FusedFuture::is_terminated(&#bound_future_name) {
None
} else {
Some(#futures_crate::future::FutureExt::poll_unpin(
Some(__futures_crate::future::FutureExt::poll_unpin(
&mut #bound_future_name,
__cx,
).map(#enum_ident::#variant_name))
}
};
let #variant_name: &mut dyn FnMut(
&mut #futures_crate::task::Context<'_>
) -> Option<#futures_crate::task::Poll<_>> = &mut #variant_name;
&mut __futures_crate::task::Context<'_>
) -> 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)
__futures_crate::task::Poll::Ready(#enum_ident::Complete)
}
} else {
quote! {
Expand Down Expand Up @@ -267,21 +252,21 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream {
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()
__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
__futures_crate::future::poll_fn(__poll_fn).await
}
};

let execute_result_expr = if let 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 {
__futures_crate::task::Poll::Ready(result) => match result {
#branches
},
_ => #default_expr
Expand All @@ -297,7 +282,7 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream {

let shuffle = if random {
quote! {
#futures_crate::async_await::shuffle(&mut __select_arr);
__futures_crate::async_await::shuffle(&mut __select_arr);
}
} else {
quote!()
Expand All @@ -309,7 +294,7 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream {
let __select_result = {
#( #future_let_bindings )*

let mut __poll_fn = |__cx: &mut #futures_crate::task::Context<'_>| {
let mut __poll_fn = |__cx: &mut __futures_crate::task::Context<'_>| {
let mut __any_polled = false;

#( #poll_functions )*
Expand All @@ -318,12 +303,12 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream {
#shuffle
for poller in &mut __select_arr {
let poller: &mut &mut dyn FnMut(
&mut #futures_crate::task::Context<'_>
) -> Option<#futures_crate::task::Poll<_>> = poller;
&mut __futures_crate::task::Context<'_>
) -> Option<__futures_crate::task::Poll<_>> = poller;
match poller(__cx) {
Some(x @ #futures_crate::task::Poll::Ready(_)) =>
Some(x @ __futures_crate::task::Poll::Ready(_)) =>
return x,
Some(#futures_crate::task::Poll::Pending) => {
Some(__futures_crate::task::Poll::Pending) => {
__any_polled = true;
}
None => {}
Expand All @@ -333,7 +318,7 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream {
if !__any_polled {
#none_polled
} else {
#futures_crate::task::Poll::Pending
__futures_crate::task::Poll::Pending
}
};

Expand Down