From 0cd278bdd256eb5983e9f4582d6816b33faf57e7 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Mon, 17 Aug 2020 12:02:09 +0200 Subject: [PATCH] Propane integration Adds support for a propane integration, which will make a propane generator Ok-wrapping. --- Cargo.toml | 7 +++ fake-propane/Cargo.toml | 10 ++++ fake-propane/src/lib.rs | 40 +++++++++++++ fehler-macros/src/args.rs | 60 +++++++++++++++---- fehler-macros/src/throws.rs | 115 ++++++++++++++++++++++++++++-------- src/lib.rs | 22 +++++++ tests/propane.rs | 25 ++++++++ 7 files changed, 244 insertions(+), 35 deletions(-) create mode 100644 fake-propane/Cargo.toml create mode 100644 fake-propane/src/lib.rs create mode 100644 tests/propane.rs diff --git a/Cargo.toml b/Cargo.toml index 1d2f074..1eec47a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,11 @@ keywords = ["error-handling", "exceptions"] path = "./fehler-macros" version = "1.0.0" +[dev-dependencies] +futures-core = "0.3.5" + +[dev-dependencies.propane] +path = "./fake-propane" +package = "fake-propane" + [workspace] diff --git a/fake-propane/Cargo.toml b/fake-propane/Cargo.toml new file mode 100644 index 0000000..3e7634d --- /dev/null +++ b/fake-propane/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fake-propane" +version = "0.1.0" +authors = ["Without Boats "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +futures-core = "0.3.5" diff --git a/fake-propane/src/lib.rs b/fake-propane/src/lib.rs new file mode 100644 index 0000000..8019831 --- /dev/null +++ b/fake-propane/src/lib.rs @@ -0,0 +1,40 @@ +#![feature(generator_trait)] + +pub mod __internal { + use std::ops::Generator; + use std::pin::Pin; + use std::task::*; + + use futures_core::Stream; + + pub struct GenIter(pub T); + + impl Iterator for GenIter { + type Item = T::Yield; + + fn next(&mut self) -> Option { + panic!() + } + } + + pub struct GenStream(T); + + impl GenStream { + pub unsafe fn new(val: T) -> GenStream { + GenStream(val) + } + } + + impl, Return = ()>, T> Stream for GenStream { + type Item = T; + + fn poll_next(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { + panic!() + } + } +} + +#[macro_export] +macro_rules! async_gen_yield { + ($e:expr) => { $e } +} diff --git a/fehler-macros/src/args.rs b/fehler-macros/src/args.rs index 86a7631..9a824ce 100644 --- a/fehler-macros/src/args.rs +++ b/fehler-macros/src/args.rs @@ -2,6 +2,7 @@ // // It is also responsible for transforming the return type by injecting // the return type and the error type into the wrapper type. +use std::mem; use proc_macro2::Span; use syn::{GenericArgument, Path, PathArguments, ReturnType, Token, Type}; @@ -12,24 +13,49 @@ const WRAPPER_MUST_BE_PATH: &str = "Wrapper type must be a normal path type"; pub struct Args { error: Option, wrapper: Option, + pub propane_integration: bool, } impl Args { - pub fn ret(&mut self, ret: ReturnType) -> ReturnType { - let (arrow, ret) = match ret { - ReturnType::Default => (arrow(), unit()), - ReturnType::Type(arrow, ty) => (arrow, *ty), - }; - ReturnType::Type(arrow, Box::new(self.inject_to_wrapper(ret))) + pub fn ret(&mut self, mut ret: ReturnType) -> ReturnType { + if self.propane_integration { + self.propane_ret(&mut ret); + ret + } else { + let (arrow, mut ret) = match ret { + ReturnType::Default => (arrow(), unit()), + ReturnType::Type(arrow, ty) => (arrow, *ty), + }; + self.inject_to_wrapper(&mut ret); + ReturnType::Type(arrow, Box::new(ret)) + } + } + fn propane_ret(&mut self, ret: &mut ReturnType) { + if let syn::ReturnType::Type(_, ty) = ret { + if let syn::Type::Paren(syn::TypeParen { elem, .. }) = &mut **ty { + if let syn::Type::ImplTrait(ty) = &mut **elem { + if let syn::TypeParamBound::Trait(bound) = &mut ty.bounds[0] { + let bound = bound.path.segments.last_mut().unwrap(); + if let syn::PathArguments::AngleBracketed(args) = &mut bound.arguments { + if let syn::GenericArgument::Binding(binding) = &mut args.args[0] { + let ty = &mut binding.ty; + self.inject_to_wrapper(ty); + } + } + } + } + } + } } - fn inject_to_wrapper(&mut self, ret: Type) -> Type { - if let Some(Type::Path(mut wrapper)) = self.wrapper.take() { + fn inject_to_wrapper(&mut self, ret: &mut Type) { + let ty = mem::replace(ret, Type::Never(syn::TypeNever { bang_token: Default::default() })); + let ty = if let Some(Type::Path(mut wrapper)) = self.wrapper.take() { let types = if let Some(error) = self.error.take() { - vec![ret, error].into_iter().map(GenericArgument::Type) + vec![ty, error].into_iter().map(GenericArgument::Type) } else { - vec![ret].into_iter().map(GenericArgument::Type) + vec![ty].into_iter().map(GenericArgument::Type) }; match innermost_path_arguments(&mut wrapper.path) { @@ -46,16 +72,26 @@ impl Args { } Type::Path(wrapper) - } else { panic!(WRAPPER_MUST_BE_PATH) } + } else { panic!(WRAPPER_MUST_BE_PATH) }; + *ret = ty; } } impl Parse for Args { fn parse(input: ParseStream) -> Result { + let propane_integration = input.peek(Token![@]); + + if propane_integration { + input.parse::().unwrap(); + let ident: syn::Ident = input.parse()?; + assert_eq!(ident, "__internal_propane_integration"); + }; + if input.is_empty() { return Ok(Args { error: Some(default_error()), wrapper: Some(result()), + propane_integration, }) } @@ -75,7 +111,7 @@ impl Parse for Args { false => result(), }); - Ok(Args { error, wrapper }) + Ok(Args { error, wrapper, propane_integration }) } } diff --git a/fehler-macros/src/throws.rs b/fehler-macros/src/throws.rs index 222a09b..9fb3b59 100644 --- a/fehler-macros/src/throws.rs +++ b/fehler-macros/src/throws.rs @@ -34,38 +34,49 @@ impl Throws { panic!("#[throws] attribute can only be applied to functions and methods") } } -} -impl Fold for Throws { - fn fold_item_fn(&mut self, i: syn::ItemFn) -> syn::ItemFn { - if !self.outer_fn { return i; } - - let sig = syn::Signature { - output: self.fold_return_type(i.sig.output), - ..i.sig - }; - - self.outer_fn = false; + fn fold_propane_body(&mut self, mut block: syn::Block, is_async: bool) -> syn::Block { + use std::mem; + use syn::{Stmt::Local as L, Local, Expr::Closure as C, ExprClosure}; + let body = if let L(Local { init: Some((_, expr)), .. }) = &mut block.stmts[0] { + if let C(ExprClosure { body, .. }) = &mut **expr { + &mut **body + } else { panic!("body did not have correct structure") } + } else { panic!("body did not have correct structure") }; + let mut folder = YieldThrows { is_async }; + *body = folder.fold_expr(mem::replace(body, syn::parse_str("{}").unwrap())); + block + } - let inner = self.fold_block(*i.block); - let block = Box::new(make_fn_block(&inner)); + fn fold_sig_and_body( + &mut self, + sig: syn::Signature, + body: syn::Block, + ) -> (syn::Signature, syn::Block) { + if !self.outer_fn { return (sig, body); } - syn::ItemFn { sig, block, ..i } - } + let output = self.fold_return_type(sig.output); + let sig = syn::Signature { output, ..sig }; - fn fold_impl_item_method(&mut self, i: syn::ImplItemMethod) -> syn::ImplItemMethod { - if !self.outer_fn { return i; } + self.outer_fn = false; - let sig = syn::Signature { - output: self.fold_return_type(i.sig.output), - ..i.sig + let body = match self.args.propane_integration { + true => self.fold_propane_body(body, is_async(&sig.output)), + false => make_fn_block(&self.fold_block(body)), }; - self.outer_fn = false; + (sig, body) + } +} - let inner = self.fold_block(i.block); - let block = make_fn_block(&inner); +impl Fold for Throws { + fn fold_item_fn(&mut self, i: syn::ItemFn) -> syn::ItemFn { + let (sig, body) = self.fold_sig_and_body(i.sig, *i.block); + syn::ItemFn { sig, block: Box::new(body), ..i } + } + fn fold_impl_item_method(&mut self, i: syn::ImplItemMethod) -> syn::ImplItemMethod { + let (sig, block) = self.fold_sig_and_body(i.sig, i.block); syn::ImplItemMethod { sig, block, ..i } } @@ -110,6 +121,49 @@ impl Fold for Throws { } } +struct YieldThrows { + is_async: bool, +} + +impl Fold for YieldThrows { + fn fold_expr_yield(&mut self, i: syn::ExprYield) -> syn::ExprYield { + let ok = match &i.expr { + Some(expr) => ok(expr), + None => ok_unit(), + }; + syn::ExprYield { expr: Some(Box::new(ok)), ..i } + } + + fn fold_expr_macro(&mut self, mut i: syn::ExprMacro) -> syn::ExprMacro { + let name = &i.mac.path.segments.last().unwrap().ident; + let replacement = if name == "throw" { + if self.is_async { + "async_gen_throw" + } else { + "gen_throw" + } + } else if name == "async_gen_yield" { + "async_gen_yield_fehler" + } else { + return i; + }; + i.mac.path = syn::parse_str(&format!("::fehler::{}", replacement)).unwrap(); + i + } + + fn fold_item(&mut self, i: syn::Item) -> syn::Item { + i + } + + fn fold_expr_closure(&mut self, i: syn::ExprClosure) -> syn::ExprClosure { + i + } + + fn fold_expr_async(&mut self, i: syn::ExprAsync) -> syn::ExprAsync { + i + } +} + fn make_fn_block(inner: &syn::Block) -> syn::Block { syn::parse2(quote::quote! {{ let __ret = #inner; @@ -126,3 +180,18 @@ fn ok(expr: &syn::Expr) -> syn::Expr { fn ok_unit() -> syn::Expr { syn::parse2(quote::quote!(<_ as ::fehler::__internal::_Succeed>::from_ok(()))).unwrap() } + +fn is_async(ret: &syn::ReturnType) -> bool { + if let syn::ReturnType::Type(_, ty) = ret { + if let syn::Type::Paren(syn::TypeParen { elem, .. }) = &**ty { + if let syn::Type::ImplTrait(ty) = &**elem { + if let syn::TypeParamBound::Trait(bound) = &ty.bounds[0] { + let bound = bound.path.segments.last().unwrap(); + return bound.ident == "Stream" + } + } + } + } + + panic!("return type did not have correct structure") +} diff --git a/src/lib.rs b/src/lib.rs index 9e4de17..38f4631 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,28 @@ macro_rules! throw { () => (return <_ as ::core::default::Default>::default()); } +#[doc(hidden)] +#[macro_export] +macro_rules! gen_throw { + ($err:expr) => (yield <_ as $crate::__internal::_Throw>::from_error((::core::convert::From::from($err)))); + () => (yield <_ as ::core::default::Default>::default()); +} + +#[doc(hidden)] +#[macro_export] +macro_rules! async_gen_throw { + ($err:expr) => (yield core::Poll::Ready(<_ as $crate::__internal::_Throw>::from_error((::core::convert::From::from($err))))); + () => (yield core::Poll::Ready(<_ as ::core::default::Default>::default())); +} + +#[doc(hidden)] +#[macro_export] +macro_rules! async_gen_yield_fehler { + ($e:expr) => {{ + yield core::task::Poll::Ready(<_ as ::fehler::__internal::_Succeed>::from_ok($e)) + }} +} + #[doc(hidden)] pub mod __internal { pub trait _Succeed { diff --git a/tests/propane.rs b/tests/propane.rs new file mode 100644 index 0000000..90247af --- /dev/null +++ b/tests/propane.rs @@ -0,0 +1,25 @@ +#![feature(generators, generator_trait, try_trait)] + +use std::io; + +#[fehler::throws(@__internal_propane_integration io::Error)] +fn simple() -> (impl Iterator) { + let __ret = || { + for x in 0..10 { + yield x; + } + }; + + ::propane::__internal::GenIter(__ret) +} + +#[fehler::throws(@__internal_propane_integration ())] +fn async_gen() -> (impl futures_core::Stream) { + let __ret = |_| { + for x in 0..10 { + propane::async_gen_yield!(x); + } + }; + + unsafe { ::propane::__internal::GenStream::new(__ret) } +}