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

Rewrite join! and try_join! as procedural macros #1783

Merged
merged 1 commit into from Aug 8, 2019
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
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -140,6 +140,7 @@ matrix:
- cargo check --manifest-path futures-util/Cargo.toml --features io
- cargo check --manifest-path futures-util/Cargo.toml --features channel
- cargo check --manifest-path futures-util/Cargo.toml --features nightly,async-await
- cargo check --manifest-path futures-util/Cargo.toml --features nightly,join-macro
- cargo check --manifest-path futures-util/Cargo.toml --features nightly,select-macro
- cargo check --manifest-path futures-util/Cargo.toml --features compat
- cargo check --manifest-path futures-util/Cargo.toml --features io-compat
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -5,6 +5,7 @@ members = [
"futures-channel",
"futures-executor",
"futures-io",
"futures-join-macro",
"futures-select-macro",
"futures-sink",
"futures-util",
Expand Down
24 changes: 24 additions & 0 deletions futures-join-macro/Cargo.toml
@@ -0,0 +1,24 @@
[package]
name = "futures-join-macro-preview"
edition = "2018"
version = "0.3.0-alpha.17"
authors = ["Taiki Endo <te316e89@gmail.com>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-lang-nursery/futures-rs"
homepage = "https://rust-lang-nursery.github.io/futures-rs"
documentation = "https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.17/futures_join_macro"
description = """
Definition of the `join!` macro and the `try_join!` macro.
"""

[lib]
name = "futures_join_macro"
proc-macro = true

[features]

[dependencies]
proc-macro2 = "0.4"
proc-macro-hack = "0.5.3"
quote = "0.6"
syn = { version = "0.15.25", features = ["full"] }
1 change: 1 addition & 0 deletions futures-join-macro/LICENSE-APACHE
1 change: 1 addition & 0 deletions futures-join-macro/LICENSE-MIT
176 changes: 176 additions & 0 deletions futures-join-macro/src/lib.rs
@@ -0,0 +1,176 @@
//! The futures-rs `join! macro implementation.

#![recursion_limit = "128"]
#![warn(rust_2018_idioms, unreachable_pub)]
// It cannot be included in the published code because this lints have false positives in the minimum required version.
#![cfg_attr(test, warn(single_use_lifetimes))]
#![warn(clippy::all)]

extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_hack::proc_macro_hack;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{parenthesized, parse_quote, Expr, Ident, Token};

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

#[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>()?);

if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}

Ok(join)
}
}

fn bind_futures(
futures_crate: &syn::Path,
fut_exprs: Vec<Expr>,
span: Span,
) -> (Vec<TokenStream2>, Vec<Ident>) {
let mut future_let_bindings = Vec::with_capacity(fut_exprs.len());
let future_names: Vec<_> = fut_exprs
.into_iter()
.enumerate()
.map(|(i, expr)| {
let name = Ident::new(&format!("_fut{}", i), span);
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);
});
name
})
.collect();

(future_let_bindings, future_names)
}

/// The `join!` macro.
#[proc_macro_hack]
pub 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 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();
}
});
let take_outputs = future_names.iter().map(|fut| {
quote! {
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<'_>| {
let mut __all_done = true;
#( #poll_futures )*
if __all_done {
#futures_crate::core_reexport::task::Poll::Ready((
#( #take_outputs )*
))
} else {
#futures_crate::core_reexport::task::Poll::Pending
}
}).await
} })
}

/// The `try_join!` macro.
#[proc_macro_hack]
pub 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 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()
{
__all_done = false;
} 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.
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()
)
);
}
}
});
let take_outputs = future_names.iter().map(|fut| {
quote! {
// `.ok().unwrap()` rather than `.unwrap()` so that we don't introduce
// an `E: Debug` bound.
unsafe { #futures_crate::core_reexport::pin::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().ok().unwrap(),
}
});

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

#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((
#( #take_outputs )*
))
)
} else {
#futures_crate::core_reexport::task::Poll::Pending
}
}).await
} })
}
2 changes: 2 additions & 0 deletions futures-util/Cargo.toml
Expand Up @@ -27,13 +27,15 @@ cfg-target-has-atomic = ["futures-core-preview/cfg-target-has-atomic"]
sink = ["futures-sink-preview"]
io = ["std", "futures-io-preview", "memchr"]
channel = ["std", "futures-channel-preview"]
join-macro = ["async-await", "futures-join-macro-preview", "proc-macro-hack", "proc-macro-nested"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For almost the same reason as select!, this is optional in futures_util (see #1701).

select-macro = ["async-await", "futures-select-macro-preview", "proc-macro-hack", "proc-macro-nested", "rand"]

[dependencies]
futures-core-preview = { path = "../futures-core", version = "=0.3.0-alpha.17", default-features = false }
futures-channel-preview = { path = "../futures-channel", version = "=0.3.0-alpha.17", default-features = false, features = ["std"], optional = true }
futures-io-preview = { path = "../futures-io", version = "=0.3.0-alpha.17", default-features = false, features = ["std"], optional = true }
futures-sink-preview = { path = "../futures-sink", version = "=0.3.0-alpha.17", default-features = false, optional = true }
futures-join-macro-preview = { path = "../futures-join-macro", version = "=0.3.0-alpha.17", default-features = false, optional = true }
futures-select-macro-preview = { path = "../futures-select-macro", version = "=0.3.0-alpha.17", default-features = false, optional = true }
proc-macro-hack = { version = "0.5", optional = true }
proc-macro-nested = { version = "0.1.2", optional = true }
Expand Down
133 changes: 0 additions & 133 deletions futures-util/src/async_await/join.rs

This file was deleted.