diff --git a/Cargo.lock b/Cargo.lock index 29b575f..3287ba3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ dependencies = [ [[package]] name = "actix_async_handler" -version = "0.0.1" +version = "0.0.2" dependencies = [ "actix", "actix-rt", diff --git a/Cargo.toml b/Cargo.toml index 9cb9132..66aa13a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "actix_async_handler" description = "Support for async syntax for Actix" license = "MIT" -version = "0.0.2" +version = "0.0.3" edition = "2018" repository = "https://github.com/concurrentes-fiuba/actix-async-handler" diff --git a/README.md b/README.md index 1edfd1c..0141c89 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Actix Async Handler =================== -A attribute macro to support writing `async` message handlers for Actix actors +An attribute macro to support writing `async` message handlers for Actix actors Using this macro you can convert [this example](https://docs.rs/actix/latest/actix/struct.AtomicResponse.html#examples) @@ -34,4 +34,252 @@ async fn handle(&mut self, _msg: Msg, _ctx: &mut Context) -> Self::Result self.0 -= 1; self.0 } +``` + +## Usage + +Add actix_async_handler as dev dependency. + +``` +cargo add --dev actix_async_handler +``` + +When implementing an async handler, annotate it with the `#[async_handler]` attribute like + +```rust + +#[async_handler] +impl Handler for MyActor { + type Result = u64; // or whatever your message handler returns, no enclosing ResponseActFuture or AtomicFuture needed + async fn handle(&mut self, _msg: Msg, _ctx: &mut Context) -> Self::Result { + // your handler code, for example + self.other_actor_addr.send(OtherMsg()).await // yay! we can use await + } +``` +that's it! Enjoy. + +By default, the returned future will be an `AtomicFuture`, so your actor won't handle any other incoming messages until +fully resolves any awaited calls. This is the behavior that mostly respects the Hewitt's original model, letting you +abstract the await-ness in your code and use it exactly like a sync version would do. If you rather let your actor +process messages in between awaits, you can change it to be a `ResponseActFuture` by annotating your handler with +`#[async_handler(non_atomic)]` instead. + + +## Known Limitations + +Known list of language features that won't be correctly translated, and hopefully workarounds that may exist. + +### Chained operations on await results + +The following code is not translated well (yet) + +```rust +let result = self.delegate_actor_addr.send(MyMsg).await.or_else(0) + 3 +``` + +Isolate the awaitable call to its own expression instead + +```rust +let await_result = self.delegate_actor_addr.send(MyMsg).await; +let result = await_result.or_else(0) + 3 +``` + + +### If expressions + +#### Mutating variables inside if expressions + +The following code won't work as expected + +```rust +let mut result = None; + +if some_condition { + let returned_value = self.delegate_actor.send(message).await; + result = returned_value.ok(); +} + +println!("{}", result); // Always prints None regardless of some_condition and returned_value +``` + +The `async_handler` macro translates your async code to a "pyramid of doom" in order to correctly +move the latest value of your variables. + +For example, a code like this + +```rust + +let a = call_a().await; +let b = call_b(a).await; +let c = call_c(b).await; +println!("{}, {}, {}", a, b, c) +``` + +becomes (simplified) + +```rust +wrap_future(call_a()) + .then(move |__res, __self, __ctx| { + let a = __res; + wrap_future(call_b(a)) + .then(move |__res, __self, __ctx| { + let b = __res; + wrap_future(call_c(b)) + .then(move |__res, __self, __ctx| { + let c = __res; + println!("{}, {}, {}", a, b, c) + }) + }) + }) +``` + +This way the latest lines are the innermost in the `then` chain, and as such are moving the correct values for the scope variables. + +The problem arises when you are using an if condition. Here as we have different branches, `then` is applied externally. + +For the first example, the translated code would look like (again simplified) + +```rust + let mut result = None; + + (if some_condition { + wrap_future(self.delegate_actor.send(message)) + .then(move |__res, __self, __ctx| { + let returned_value = __res; + result = returned_value.ok(); // updates the local copy of result, useless + } + } else { + wrap_future(fut::ready(())) // both if branches need to return a future. + }).then(move |__res, __self, __ctx| { + println!("{}", result); + }) +``` + +The `then` for the lines after the if is put outside the conditional chain, and as such captures the original variable +value. Hence, the value stays the original from the point of view of the print. + +To overcome this issue, you should make your condition always return what you need to be updated. + +In the code above, you should do instead + +```rust + +let mut result = None; + +result = if some_condition { + let returned_value = self.delegate_actor.send(message).await; + returned_value.ok() +} + +println!("{}", result); +``` + +If you have multiple variables you wish to update, you could pack them in a tuple + +```rust + +let mut a = 0, mut b = 0, mut c = 0; + +(a, b, c) = if some_condition { + a = call_a().await; + b = call_b(b).await; + c = call_c(c).await; + (a, b, c) +} else { + (a, b, c) // return the defaults. It is mandatory to have an else +} + +``` + +#### Need for explicitly setting a return type for if expressions + +This doesn't compile + +```rust +let result = if some_condition { + let a = call_a().await + a.ok() +} else { + None +} +``` + +As the translation code is not smart enough to figure the returned type of `a.ok()` + +instead you should hit the compiler on the type like: + +```rust +let result: Option = if some_condition { + let a = call_a().await // image return type to be Result + a.ok() +} else { + None +} +``` + +#### Early returning inside if expressions + +This code wouldn't do what you expect + +```rust + +if some_early_exit_condition { + call_a().await; + return; +} + +call_b(a).await; +... +``` + +As the `then` chain is external to the closure containing the `if`, it won't avoid the code after the await to be executed. + +Write an else block containing the rest instead + +```rust +if some_early_exit_condition { + call_a().await; +} else { + call_b(a).await; + ... // rest of the code +} +``` + +### Previous declaration of result variable + +This fails to compile with ``Cannot assign to `a` as it is not declared mutable`` + +```rust +let a; + +if condition { + a = call_a().await; +} +``` + +Given you cannot really use a for anything outside the then block, simply declare it local. If you +want to "return" the result of the await call, refer to [Mutating variables inside if expressions](#mutating-variables-inside-if-expressions) + +### match expressions + +`await`s inside `match` expressions are not currently supported. Replace them with chained `if let` expressions instead like + +```rust +match action { + Move(x, y) => call_move_async(x, y).await, + Talk(msg) => say_async(msg).await, + _ => println!("unknown action"); +} +``` + +becomes + +```rust +if let Move(x, y) = action { + call_move_async(x, y).await +} else if let Talk(msg) = action { + say_async(msg).await +} else { + println!("unknown action"); +} ``` \ No newline at end of file diff --git a/examples/if.rs b/examples/if.rs new file mode 100644 index 0000000..03185a4 --- /dev/null +++ b/examples/if.rs @@ -0,0 +1,89 @@ +use std::time::Duration; + +use actix::{Actor, Addr, Context, Handler, MailboxError, Message}; +use actix::clock::sleep; +use actix::fut::result; + +use actix_async_handler::async_handler; + +#[derive(Message, Clone, Copy)] +#[rtype(result = "u64")] +struct Ping(u64); + +struct Ponger {} + +impl Actor for Ponger { + type Context = Context; +} + +#[async_handler] +impl Handler for Ponger { + type Result = u64; + + async fn handle(&mut self, msg: Ping, _ctx: &mut Self::Context) -> Self::Result { + println!("[Ponger] sleeping for {} secs", msg.0); + sleep(Duration::from_secs(msg.0)).await; + println!("[Ponger] woke up."); + msg.0 + } +} + +struct Pinger { + ponger: Addr +} + +impl Actor for Pinger { + type Context = Context; +} + +#[async_handler] +impl Handler for Pinger { + + type Result = u64; + + async fn handle(&mut self, msg: Ping, ctx: &mut Self::Context) -> Self::Result { + + if msg.0 > 1 { + self.ponger.send(msg).await + } + + if msg.0 > 2 { + self.ponger.send(msg).await + } else if msg.0 > 3 { + // This seems useless, but it is here to test the else having an await + self.ponger.send(msg).await + } + + let result: Result = if msg.0 > 0 { + let part = self.ponger.send(msg).await; + self.ponger.send(Ping(part.unwrap())).await + } else { + Ok(42u64) + }; + + let mut final_result: Result = Err(MailboxError::Closed); + final_result = if result.unwrap() > 1 { + self.ponger.send(Ping(result.unwrap())).await + } else { + result + }; + + if let Ok(v) = final_result { + self.ponger.send(Ping(v)).await + } + + final_result.unwrap() + 1 + + } + +} + +#[actix_rt::main] +async fn main() { + + let ponger = Ponger {}.start(); + let pinger = Pinger { ponger }.start(); + + println!("Result {}", pinger.send(Ping(2)).await.unwrap()); + println!("Result {}", pinger.send(Ping(3)).await.unwrap()); +} \ No newline at end of file diff --git a/src/impl.rs b/src/impl.rs index 901cc5d..820ce24 100644 --- a/src/impl.rs +++ b/src/impl.rs @@ -1,6 +1,6 @@ use proc_macro2::{Span, TokenStream, TokenTree}; use quote::{quote, ToTokens}; -use syn::{Block, Error, Expr, ExprAssign, Ident, ImplItem, ImplItemFn, ImplItemType, ItemImpl, Local, LocalInit, Macro, Pat, Result, Stmt}; +use syn::{Block, Error, Expr, ExprAssign, ExprAwait, ExprBlock, ExprIf, Ident, ImplItem, ImplItemFn, ImplItemType, ItemImpl, Local, LocalInit, Macro, Pat, Result, Stmt}; use syn::FnArg::Typed; use syn::fold::Fold; use syn::spanned::Spanned; @@ -20,10 +20,9 @@ pub fn async_handler_impl(attribute: TokenStream, input: TokenStream) -> TokenSt } pub fn async_handler_inner(is_atomic: bool, input: TokenStream) -> Result { - let input_item = syn::parse2::(input.clone()); + let mut item_fn = syn::parse2::(input.clone())?; - let is_handler = input_item.clone().ok() - .and_then(|item| item.trait_) + let is_handler = item_fn.trait_.as_ref() .and_then(|trait_| trait_.1.segments.first().map(|i| "Handler" == i.ident.to_string())) .unwrap_or(false); @@ -31,8 +30,6 @@ pub fn async_handler_inner(is_atomic: bool, input: TokenStream) -> Result { @@ -82,20 +79,7 @@ fn process_handler_fn(is_atomic: bool, body: &mut ImplItemFn) -> Result<()> { let awaits = split_awaits(&self_renamed); - let future_chain = awaits.iter().rfold(None, |acc, await_block| - match acc { - Some(inner) => Some(quote! { - .then(move |__res, __self, __ctx| { - #await_block #inner - }) - }), - None => Some(quote! { - .map(move |__res, __self, __ctx| { - #await_block - }) - }) - } - ).unwrap_or(quote!()); + let future_chain = build_future_chain(awaits, true, false); let result_type = result_type_ident(is_atomic, body.span()); @@ -109,56 +93,225 @@ fn process_handler_fn(is_atomic: bool, body: &mut ImplItemFn) -> Result<()> { Ok(()) } +fn build_future_chain(awaits: Vec, enclose_first: bool, return_unit: bool) -> TokenStream { + awaits.iter().rfold(None, |acc, await_block| + match acc { + Some((count, inner)) + if count == (awaits.len()-1) && !enclose_first => Some((count + 1, quote! { + #await_block #inner + })), + Some((count, inner)) => Some((count + 1, quote! { + .then(move |__res, __self, __ctx| { + #await_block #inner + }) + })), + None if return_unit => Some((1, quote! { + .map(move |__res, __self, __ctx| { + #await_block; + () + }) + })), + None if !await_block.is_empty() => Some((1, quote! { + .map(move |__res, __self, __ctx| { + #await_block + }) + })), + None => Some((1, quote!())) + } + ).unwrap_or((0, quote!())).1 +} + fn split_awaits(block: &Block) -> Vec { - let mut parts = Vec::new(); - let mut current_part = TokenStream::new(); + let mut parts = vec!(TokenStream::new()); for stmt in &block.stmts { - match stmt { + if !match stmt { Stmt::Expr(Expr::Await(expr), _) => { - let base = &*expr.base; - quote!( - actix::fut::wrap_future::<_, Self>(#base) - ).to_tokens(&mut current_part); - parts.push(current_part); - current_part = TokenStream::new(); + expr_await(&mut parts, expr); + true } - Stmt::Expr(Expr::Assign(ExprAssign { left, right: expr, .. }), ..) - if matches!(**expr, Expr::Await(_)) => { - if let Expr::Await(inner) = &**expr { - let base = &*inner.base; + Stmt::Expr(Expr::Assign(ExprAssign { left, right: expr, .. }), ..) => + match &**expr { + Expr::Await(inner) => { + expr_await(&mut parts, inner); quote!( - actix::fut::wrap_future::<_, Self>(#base) - ).to_tokens(&mut current_part); - parts.push(current_part); - current_part = TokenStream::new(); - quote! ( #left = __res; - ).to_tokens(&mut current_part); + ).to_tokens(parts.last_mut().unwrap()); + true } - } - Stmt::Local(Local { pat, init: Some(LocalInit { expr, .. }), .. } ) - if matches!(**expr, Expr::Await(_)) => { - if let Expr::Await(inner) = &**expr { - let base = &*inner.base; - quote!( - actix::fut::wrap_future::<_, Self>(#base) - ).to_tokens(&mut current_part); - parts.push(current_part); - current_part = TokenStream::new(); + Expr::If(expr ) => { + if expr_if(&mut parts, expr, false) { + quote!( + #left = __res; + ).to_tokens(parts.last_mut().unwrap()); + true + } else { + false + } + } + _ => false + } + Stmt::Local(Local { pat, init: Some(LocalInit { expr, .. }), .. } ) => + match &**expr { + Expr::Await(inner) => { + expr_await(&mut parts, inner); quote! ( let #pat = __res; - ).to_tokens(&mut current_part); + ).to_tokens(parts.last_mut().unwrap()); + true } + Expr::If(expr ) => { + if expr_if(&mut parts, expr, false) { + quote!( + let #pat = __res; + ).to_tokens(parts.last_mut().unwrap()); + true + } else { + false + } + } + _ => false + } + Stmt::Expr(Expr::If(expr ), ..) => { + expr_if(&mut parts, expr, true) } - stmt => { - stmt.to_tokens(&mut current_part); - } + _ => false + } { + stmt.to_tokens(parts.last_mut().unwrap()); } } - parts.push(current_part); parts } +fn expr_if(parts: &mut Vec, expr: &ExprIf, return_unit: bool) -> bool { + let result = expr_if_inner(expr, return_unit); + if result.is_empty() { + false + } else { + result.to_tokens(parts.last_mut().unwrap()); + parts.push(TokenStream::new()); + true + } +} +fn expr_if_inner(expr: &ExprIf, return_unit: bool) -> TokenStream { + let ExprIf { cond, then_branch, else_branch, .. } = expr; + let then_parts = split_awaits(then_branch); + + let mut token_stream = TokenStream::new(); + + if then_parts.len() > 1 { + let then_chain = build_future_chain(then_parts, false, return_unit); + quote!( + if #cond { + Box::pin(#then_chain) as std::pin::Pin>> + } + ).to_tokens(&mut token_stream); + if else_branch.is_none() { + quote!( + else { + Box::pin(actix::fut::ready(())) + } + ).to_tokens(&mut token_stream); + } else { + let else_expr = else_branch.as_ref().unwrap().1.as_ref(); + let awaited = match else_expr { + Expr::Block(ExprBlock { block, .. }) => { + let else_parts = split_awaits(block); + if else_parts.len() > 1 { + let else_chain = build_future_chain(else_parts, false, return_unit); + quote!( + else { + Box::pin(#else_chain) + } + ).to_tokens(&mut token_stream); + true + } else { + false + } + }, + Expr::If(if_expr) => { + let else_parts = expr_if_inner(if_expr, return_unit); + if !else_parts.is_empty() { + // chained else if(s) have awaits + quote!( + else #else_parts + ).to_tokens(&mut token_stream); + true + } else { + false + } + } + _ => false + }; + if !awaited { + if return_unit { + quote!( + else { + Box::pin(actix::fut::ready({ #else_expr; })) + } + ) + } else { + quote!( + else { + Box::pin(actix::fut::ready(#else_expr)) + } + ) + }.to_tokens(&mut token_stream); + } + } + } else if else_branch.is_some() { + match else_branch.as_ref().unwrap().1.as_ref() { + Expr::Block(ExprBlock { block, .. }) => { + let else_parts = split_awaits(block); + if else_parts.len() > 1 { + let else_chain = build_future_chain(else_parts, false, return_unit); + non_awaited_if_expr_for_else(return_unit, cond, then_branch, &mut token_stream); + quote!( + else { + Box::pin(#else_chain) as std::pin::Pin>> + } + ).to_tokens(&mut token_stream); + } + } + Expr::If(if_expr) => { + let else_parts = expr_if_inner(if_expr, return_unit); + if !else_parts.is_empty() { + non_awaited_if_expr_for_else(return_unit, cond, then_branch, &mut token_stream); + // chained else if(s) have awaits + quote!( + else #else_parts + ).to_tokens(&mut token_stream); + } + } + _ => () + } + } + token_stream +} + +fn non_awaited_if_expr_for_else(return_unit: bool, cond: &Box, then_branch: &Block, mut token_stream: &mut TokenStream) { + if return_unit { + quote!( + if #cond { + Box::pin(actix::fut::ready({ #then_branch; })) + } + ).to_tokens(&mut token_stream); + } else { + quote!( + if #cond { + Box::pin(actix::fut::ready(#then_branch)) + } + ).to_tokens(&mut token_stream); + } +} + +fn expr_await(parts: &mut Vec, expr: &ExprAwait) { + let base = &*expr.base; + quote!( + actix::fut::wrap_future::<_, Self>(#base) + ).to_tokens(parts.last_mut().unwrap()); + parts.push(TokenStream::new()); +} + struct RenameParams(String); impl Fold for RenameParams { diff --git a/src/integration_test.rs b/src/integration_test.rs index c954a7e..f9faf98 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -1,6 +1,7 @@ use crate::r#impl::async_handler_inner; use quote::quote; use rust_format::Formatter; +use syn::ExprIf; #[test] fn test_splits_awaits_integration() { @@ -192,4 +193,418 @@ fn test_await_return_value_assignment() { let actual = rust_format::RustFmt::default().format_tokens(result.clone().expect("")).expect(""); assert_eq!(expected, actual) +} + +#[test] +fn test_if_single_branch() { + let result = async_handler_inner(true, quote! { + impl Handler for ResultAssignment { + type Result = u64; + async fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + + if msg.0 > 0 { + self.other_actor.send(0).await; + } + + self.other_actor.send(msg).await + } + } + }); + + let expected = + r#"impl Handler for ResultAssignment { + type Result = actix::AtomicResponse; + fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + use actix::ActorFutureExt; + actix::AtomicResponse::new(Box::pin( + actix::fut::wrap_future::<_, Self>(actix::fut::ready(())).then( + move |__res, __self, __ctx| { + if msg.0 > 0 { + Box::pin( + actix::fut::wrap_future::<_, Self>(__self.other_actor.send(0)).map( + move |__res, __self, __ctx| { + () + }, + ), + ) + as std::pin::Pin< + Box>, + > + } else { + Box::pin(actix::fut::ready(())) + } + .then(move |__res, __self, __ctx| { + actix::fut::wrap_future::<_, Self>(__self.other_actor.send(msg)) + }) + }, + ), + )) + } +} +"#; + + let actual = rust_format::RustFmt::default().format_tokens(result.clone().expect("")).expect(""); + assert_eq!(expected, actual) +} + +#[test] +fn test_if_branch_awaits() { + let result = async_handler_inner(true, quote! { + impl Handler for ResultAssignment { + type Result = u64; + async fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + if msg.0 > 0 { + let part = self.other_actor.send(0).await; + self.other_actor.send(part).await + } else { + call_boring_non_awaitable_stuff(); + 42 + } + + self.other_actor.send(0).await + } + } + }); + + let expected = + r#"impl Handler for ResultAssignment { + type Result = actix::AtomicResponse; + fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + use actix::ActorFutureExt; + actix::AtomicResponse::new(Box::pin( + actix::fut::wrap_future::<_, Self>(actix::fut::ready(())).then( + move |__res, __self, __ctx| { + if msg.0 > 0 { + Box::pin( + actix::fut::wrap_future::<_, Self>(__self.other_actor.send(0)).then( + move |__res, __self, __ctx| { + let part = __res; + actix::fut::wrap_future::<_, Self>( + __self.other_actor.send(part), + ) + .map( + move |__res, __self, __ctx| { + () + }, + ) + }, + ), + ) + as std::pin::Pin< + Box>, + > + } else { + Box::pin(actix::fut::ready({ + { + call_boring_non_awaitable_stuff(); + 42 + }; + })) + } + .then(move |__res, __self, __ctx| { + actix::fut::wrap_future::<_, Self>(__self.other_actor.send(0)) + }) + }, + ), + )) + } +} +"#; + + let actual = rust_format::RustFmt::default().format_tokens(result.clone().expect("")).expect(""); + assert_eq!(expected, actual) +} + +#[test] +fn test_if_branch_awaits_return_value() { + let result = async_handler_inner(true, quote! { + impl Handler for ResultAssignment { + type Result = u64; + async fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + let result = if msg.0 > 0 { + let part = self.other_actor.send(0).await; + self.other_actor.send(part).await + } else { + call_boring_non_awaitable_stuff(); + 42 + }; + + self.other_actor.send(result).await + } + } + }); + + let expected = + r#"impl Handler for ResultAssignment { + type Result = actix::AtomicResponse; + fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + use actix::ActorFutureExt; + actix::AtomicResponse::new(Box::pin( + actix::fut::wrap_future::<_, Self>(actix::fut::ready(())).then( + move |__res, __self, __ctx| { + if msg.0 > 0 { + Box::pin( + actix::fut::wrap_future::<_, Self>(__self.other_actor.send(0)).then( + move |__res, __self, __ctx| { + let part = __res; + actix::fut::wrap_future::<_, Self>( + __self.other_actor.send(part), + ) + }, + ), + ) + as std::pin::Pin< + Box>, + > + } else { + Box::pin(actix::fut::ready({ + call_boring_non_awaitable_stuff(); + 42 + })) + } + .then(move |__res, __self, __ctx| { + let result = __res; + actix::fut::wrap_future::<_, Self>(__self.other_actor.send(result)) + }) + }, + ), + )) + } +} +"#; + + let actual = rust_format::RustFmt::default().format_tokens(result.clone().expect("")).expect(""); + assert_eq!(expected, actual); +} + +#[test] +fn test_if_both_branches_await() { + let result = async_handler_inner(true, quote! { + impl Handler for ResultAssignment { + type Result = u64; + async fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + if msg.0 > 0 { + self.other_actor.send(0).await; + } else { + self.negative_actor.send(42).await; + }; + } + } + }); + + let expected = + r#"impl Handler for ResultAssignment { + type Result = actix::AtomicResponse; + fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + use actix::ActorFutureExt; + actix::AtomicResponse::new(Box::pin( + actix::fut::wrap_future::<_, Self>(actix::fut::ready(())).then( + move |__res, __self, __ctx| { + if msg.0 > 0 { + Box::pin( + actix::fut::wrap_future::<_, Self>(__self.other_actor.send(0)).map( + move |__res, __self, __ctx| { + () + }, + ), + ) + as std::pin::Pin< + Box>, + > + } else { + Box::pin( + actix::fut::wrap_future::<_, Self>(__self.negative_actor.send(42)).map( + move |__res, __self, __ctx| { + () + }, + ), + ) + } + }, + ), + )) + } +} +"#; + + let actual = rust_format::RustFmt::default().format_tokens(result.clone().expect("")).expect(""); + assert_eq!(expected, actual); +} + +#[test] +fn test_if_else_awaits() { + let result = async_handler_inner(true, quote! { + impl Handler for ResultAssignment { + type Result = u64; + async fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + let result = if msg.0 > 0 { + call_boring_non_awaitable_stuff() + } else { + self.negative_actor.send(42).await + }; + } + } + }); + + let expected = + r#"impl Handler for ResultAssignment { + type Result = actix::AtomicResponse; + fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + use actix::ActorFutureExt; + actix::AtomicResponse::new(Box::pin( + actix::fut::wrap_future::<_, Self>(actix::fut::ready(())).then( + move |__res, __self, __ctx| { + if msg.0 > 0 { + Box::pin(actix::fut::ready({ call_boring_non_awaitable_stuff() })) + } else { + Box::pin(actix::fut::wrap_future::<_, Self>( + __self.negative_actor.send(42), + )) + as std::pin::Pin< + Box>, + > + } + .map(move |__res, __self, __ctx| { + let result = __res; + }) + }, + ), + )) + } +} +"#; + + let actual = rust_format::RustFmt::default().format_tokens(result.clone().expect("")).expect(""); + assert_eq!(expected, actual); +} + +#[test] +fn test_if_else_chain_awaits() { + let result = async_handler_inner(true, quote! { + impl Handler for ResultAssignment { + type Result = u64; + async fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + let result = if msg.0 > 0 { + call_boring_non_awaitable_stuff() + } else if other_cond { + other_boring_stuff() + } else if nice_cond { + self.fun_actor.send(12).await + } else { + self.negative_actor.send(42).await + }; + } + } + }); + + let expected = + r#"impl Handler for ResultAssignment { + type Result = actix::AtomicResponse; + fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + use actix::ActorFutureExt; + actix::AtomicResponse::new(Box::pin( + actix::fut::wrap_future::<_, Self>(actix::fut::ready(())).then( + move |__res, __self, __ctx| { + if msg.0 > 0 { + Box::pin(actix::fut::ready({ call_boring_non_awaitable_stuff() })) + } else if other_cond { + Box::pin(actix::fut::ready({ other_boring_stuff() })) + } else if nice_cond { + Box::pin(actix::fut::wrap_future::<_, Self>( + __self.fun_actor.send(12), + )) + as std::pin::Pin< + Box>, + > + } else { + Box::pin(actix::fut::wrap_future::<_, Self>( + __self.negative_actor.send(42), + )) + } + .map(move |__res, __self, __ctx| { + let result = __res; + }) + }, + ), + )) + } +} +"#; + + let actual = rust_format::RustFmt::default().format_tokens(result.clone().expect("")).expect(""); + assert_eq!(expected, actual); +} + +#[test] +fn test_if_assigns() { + let result = async_handler_inner(true, quote! { + impl Handler for ResultAssignment { + type Result = u64; + async fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + let result; + result = if msg.0 > 2 { + self.other_actor.send(part).await + } else { + 15 + }; + + self.other_actor.send(result).await + } + } + }); + + let expected = + r#"impl Handler for ResultAssignment { + type Result = actix::AtomicResponse; + fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + use actix::ActorFutureExt; + actix::AtomicResponse::new(Box::pin( + actix::fut::wrap_future::<_, Self>(actix::fut::ready(())).then( + move |__res, __self, __ctx| { + let result; + if msg.0 > 2 { + Box::pin(actix::fut::wrap_future::<_, Self>( + __self.other_actor.send(part), + )) + as std::pin::Pin< + Box>, + > + } else { + Box::pin(actix::fut::ready({ 15 })) + } + .then(move |__res, __self, __ctx| { + result = __res; + actix::fut::wrap_future::<_, Self>(__self.other_actor.send(result)) + }) + }, + ), + )) + } +} +"#; + + let actual = rust_format::RustFmt::default().format_tokens(result.clone().expect("")).expect(""); + assert_eq!(expected, actual); +} + +#[test] +fn test_mut() { + let result = async_handler_inner(true, quote! { + impl Handler for ResultAssignment { + type Result = u64; + async fn handle(&mut self, msg: Conditional, ctx: &mut Self::Context) -> Self::Result { + let mut result = 0; + if msg.0 > 2 { + self.other_actor.send(part).await; + result += 1; + }; + + println("{}", result) + } + } + }); + + + let actual = rust_format::RustFmt::default().format_tokens(result.clone().expect("")).expect(""); + println!("{}", actual); } \ No newline at end of file