Skip to content

Commit

Permalink
Migrate to await syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Jul 27, 2019
1 parent a10d0ad commit 9f04a09
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 86 deletions.
4 changes: 2 additions & 2 deletions futures-async-macro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Add this to your `Cargo.toml`:

```toml
[dependencies]
futures-preview = { version = "0.3.0-alpha.15", features = ["async-stream", "nightly"] }
futures-preview = { version = "0.3.0-alpha.16", features = ["async-stream", "nightly"] }
```

### \#\[for_await\]
Expand All @@ -17,7 +17,7 @@ Processes streams using a for loop.
This is a reimplement of [futures-await]'s `#[async]` for loops for futures 0.3 and is an experimental implementation of [the idea listed as the next step of async/await](https://github.com/rust-lang/rfcs/blob/master/text/2394-async_await.md#for-await-and-processing-streams).

```rust
#![feature(async_await, generators, stmt_expr_attributes, proc_macro_hygiene)]
#![feature(async_await, stmt_expr_attributes, proc_macro_hygiene)]
use futures::for_await;
use futures::prelude::*;

Expand Down
115 changes: 68 additions & 47 deletions futures-async-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Procedural macro for the `#[async_stream]` attribute.

#![recursion_limit = "128"]
#![warn(rust_2018_idioms)]
#![warn(rust_2018_idioms, unreachable_pub)]
#![warn(clippy::all)]

extern crate proc_macro;

Expand All @@ -10,8 +11,8 @@ use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree as TokenTree2};
use quote::{quote, ToTokens};
use syn::{
fold::{self, Fold},
token, ArgCaptured, Error, Expr, ExprCall, ExprForLoop, ExprMacro, ExprYield, FnArg, FnDecl,
Ident, Item, ItemFn, Pat, PatIdent, ReturnType, TypeTuple,
token, ArgCaptured, Error, Expr, ExprCall, ExprField, ExprForLoop, ExprMacro, ExprYield, FnArg,
FnDecl, Ident, Item, ItemFn, Member, Pat, PatIdent, ReturnType, TypeTuple,
};

#[macro_use]
Expand Down Expand Up @@ -210,7 +211,7 @@ pub fn async_stream_block(input: TokenStream) -> TokenStream {
tokens.into()
}

/// The scope in which `#[for_await]`, `await!` was called.
/// The scope in which `#[for_await]`, `.await` was called.
///
/// The type of generator depends on which scope is called.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand All @@ -221,7 +222,7 @@ enum Scope {
Stream,
/// `static move ||`, `||`
///
/// It cannot call `#[for_await]`, `await!` in this scope.
/// It cannot call `#[for_await]`, `.await` in this scope.
Closure,
}

Expand All @@ -241,49 +242,66 @@ impl Expand {
));
}

let ExprForLoop { label, pat, expr, body, .. } = &expr;
// It needs to adjust the type yielded by the macro because generators used internally by
// async fn yield `()` type, but generators used internally by `async_stream` yield
// `Poll<U>` type.
let yield_ = match self.0 {
Future => TokenStream2::new(),
Stream => quote! { ::futures::core_reexport::task::Poll::Pending },
Closure => return outside_of_async_error!(expr, "#[for_await]"),
};
let ExprForLoop { label, pat, expr, body, .. } = expr;

// Basically just expand to a `poll` loop
syn::parse_quote! {{
let mut __pinned = #expr;
#label
loop {
let #pat = {
match ::futures::async_stream::poll_next_with_tls_context(unsafe {
::futures::core_reexport::pin::Pin::new_unchecked(&mut __pinned)
})
{
::futures::core_reexport::task::Poll::Ready(e) => {
match e {
match self.0 {
Future => {
// Basically just expand to a `poll` loop
syn::parse_quote! {{
let mut __pinned = #expr;
let mut __pinned = unsafe {
::futures::core_reexport::pin::Pin::new_unchecked(&mut __pinned)
};
#label
loop {
let #pat = {
match ::futures::stream::StreamExt::next(&mut __pinned).await {
::futures::core_reexport::option::Option::Some(e) => e,
::futures::core_reexport::option::Option::None => break,
}
}
::futures::core_reexport::task::Poll::Pending => {
yield #yield_;
continue
}
};

#body
}
};
}}
}
Stream => {
// Basically just expand to a `poll` loop
syn::parse_quote! {{
let mut __pinned = #expr;
#label
loop {
let #pat = {
match ::futures::async_stream::poll_next_with_tls_context(unsafe {
::futures::core_reexport::pin::Pin::new_unchecked(&mut __pinned)
})
{
::futures::core_reexport::task::Poll::Ready(e) => {
match e {
::futures::core_reexport::option::Option::Some(e) => e,
::futures::core_reexport::option::Option::None => break,
}
}
::futures::core_reexport::task::Poll::Pending => {
yield ::futures::core_reexport::task::Poll::Pending;
continue
}
}
};

#body
#body
}
}}
}
}}
Closure => return outside_of_async_error!(expr, "#[for_await]"),
}
}

/// Expands `yield expr` in `async_stream` scope.
fn expand_yield(&self, expr: ExprYield) -> ExprYield {
if self.0 != Stream {
return expr;
}
if self.0 != Stream { return expr }

let ExprYield { attrs, yield_token, expr } = expr;
let expr = expr.map_or_else(|| quote!(()), ToTokens::into_token_stream);
Expand All @@ -293,28 +311,30 @@ impl Expand {
ExprYield { attrs, yield_token, expr: Some(Box::new(expr)) }
}

/// Expands a macro.
/// Expands `async_stream_block!` macro.
fn expand_macro(&mut self, mut expr: ExprMacro) -> Expr {
if self.0 == Stream && expr.mac.path.is_ident("await") {
return self.expand_await_macros(expr);
} else if expr.mac.path.is_ident("async_stream_block") {
if expr.mac.path.is_ident("async_stream_block") {
let mut e: ExprCall = syn::parse(async_stream_block(expr.mac.tts.into())).unwrap();
e.attrs.append(&mut expr.attrs);
return Expr::Call(e);
Expr::Call(e)
} else {
Expr::Macro(expr)
}

Expr::Macro(expr)
}

/// Expands `await!(expr)` in `async_stream` scope.
/// Expands `expr.await` in `async_stream` scope.
///
/// It needs to adjust the type yielded by the macro because generators used internally by
/// async fn yield `()` type, but generators used internally by `async_stream` yield
/// `Poll<U>` type.
fn expand_await_macros(&mut self, expr: ExprMacro) -> Expr {
assert_eq!(self.0, Stream);
fn expand_await(&mut self, expr: ExprField) -> Expr {
if self.0 != Stream { return Expr::Field(expr) }

let expr = expr.mac.tts;
match &expr.member {
Member::Named(x) if x == "await" => {}
_ => return Expr::Field(expr),
}
let expr = expr.base;

// Because macro input (`#expr`) is untrusted, use `syn::parse2` + `expr_compile_error`
// instead of `syn::parse_quote!` to generate better error messages (`syn::parse_quote!`
Expand Down Expand Up @@ -349,8 +369,9 @@ impl Fold for Expand {
}

let expr = match fold::fold_expr(self, expr) {
Expr::ForLoop(expr) => self.expand_for_await(expr),
Expr::Yield(expr) => Expr::Yield(self.expand_yield(expr)),
Expr::Field(expr) => self.expand_await(expr),
Expr::ForLoop(expr) => self.expand_for_await(expr),
Expr::Macro(expr) => self.expand_macro(expr),
expr => expr,
};
Expand Down
45 changes: 11 additions & 34 deletions futures/testcrate/ui/nested.stderr
Original file line number Diff line number Diff line change
@@ -1,37 +1,14 @@
error[E0308]: mismatched types
--> $DIR/nested.rs:10:19
error[E0727]: `async` generators are not yet supported
--> $DIR/nested.rs:7:19
|
10 | yield i * i;
| ^^^^^ expected (), found integer
|
= note: expected type `()`
found type `{integer}`

error[E0698]: type inside generator must be known in this context
--> $DIR/nested.rs:10:13
|
10 | yield i * i;
| ^^^^^^^^^^^ cannot infer type for `{integer}`
|
note: the type is part of the generator because of this `yield`
--> $DIR/nested.rs:10:13
|
10 | yield i * i;
| ^^^^^^^^^^^

error[E0698]: type inside generator must be known in this context
--> $DIR/nested.rs:5:1
|
5 | #[async_stream]
| ^^^^^^^^^^^^^^^ cannot infer type for `{integer}`
|
note: the type is part of the generator because of this `yield`
--> $DIR/nested.rs:5:1
|
5 | #[async_stream]
| ^^^^^^^^^^^^^^^
7 | let _ = async {
| ___________________^
8 | | #[for_await]
9 | | for i in stream::iter(vec![1, 2]) {
10 | | yield i * i;
11 | | }
12 | | };
| |_____^

error: aborting due to 3 previous errors
error: aborting due to previous error

Some errors have detailed explanations: E0308, E0698.
For more information about an error, try `rustc --explain E0308`.
4 changes: 2 additions & 2 deletions futures/tests/async_stream/nested.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ fn _stream1() -> i32 {
let _ = async { // impl Generator<Yield = (), Return = U>
#[for_await]
for i in stream::iter(vec![1, 2]) {
await!(future::lazy(|_| i * i));
future::lazy(|_| i * i).await;
}
};
await!(future::lazy(|_| ()));
future::lazy(|_| ()).await;
}
2 changes: 1 addition & 1 deletion futures/tests/async_stream_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![feature(async_await, await_macro)]
#![feature(async_await)]
#![cfg_attr(all(feature = "async-stream", feature = "nightly"), feature(generators, stmt_expr_attributes, proc_macro_hygiene))]

#[cfg(all(feature = "async-stream", feature = "nightly"))]
Expand Down

0 comments on commit 9f04a09

Please sign in to comment.