Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
10 changes: 10 additions & 0 deletions fake-propane/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "fake-propane"
version = "0.1.0"
authors = ["Without Boats <woboats@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
futures-core = "0.3.5"
40 changes: 40 additions & 0 deletions fake-propane/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<T>(pub T);

impl<T: Generator> Iterator for GenIter<T> {
type Item = T::Yield;

fn next(&mut self) -> Option<Self::Item> {
panic!()
}
}

pub struct GenStream<T>(T);

impl<T> GenStream<T> {
pub unsafe fn new(val: T) -> GenStream<T> {
GenStream(val)
}
}

impl<G: Generator<*mut (), Yield = Poll<T>, Return = ()>, T> Stream for GenStream<G> {
type Item = T;

fn poll_next(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
panic!()
}
}
}

#[macro_export]
macro_rules! async_gen_yield {
($e:expr) => { $e }
}
60 changes: 48 additions & 12 deletions fehler-macros/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -12,24 +13,49 @@ const WRAPPER_MUST_BE_PATH: &str = "Wrapper type must be a normal path type";
pub struct Args {
error: Option<Type>,
wrapper: Option<Type>,
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) {
Expand All @@ -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<Args> {
let propane_integration = input.peek(Token![@]);

if propane_integration {
input.parse::<syn::token::At>().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,
})
}

Expand All @@ -75,7 +111,7 @@ impl Parse for Args {
false => result(),
});

Ok(Args { error, wrapper })
Ok(Args { error, wrapper, propane_integration })
}
}

Expand Down
115 changes: 92 additions & 23 deletions fehler-macros/src/throws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}

Expand Down Expand Up @@ -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;
Expand All @@ -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")
}
22 changes: 22 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
25 changes: 25 additions & 0 deletions tests/propane.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#![feature(generators, generator_trait, try_trait)]

use std::io;

#[fehler::throws(@__internal_propane_integration io::Error)]
fn simple() -> (impl Iterator<Item = i32>) {
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<Item = i32>) {
let __ret = |_| {
for x in 0..10 {
propane::async_gen_yield!(x);
}
};

unsafe { ::propane::__internal::GenStream::new(__ret) }
}