Skip to content

Commit a5b67d1

Browse files
author
Without Boats
committed
Support other try types.
The `throws` macro now takes an optional `as TYPE` argument. If this is present, instead of transforming the return type to `Result`, it transforms it to `TYPE` parameterized by the return and error types. The error type is optional as well: if no error type is present, the wrapper type is parameterized only by the return type. This allows you to write `#[throws(as Option)]` to make a throwing function that returns an option. It also supports other try types. Syntactically, these two should both work and be equivalent: `#[throws(io::Error as Poll<Result>)]` `#[throws(as Poll<io::Result>)]` However, these do not work because of the way the return type needs to be wrapped in poll. More thought needs to go into how to support the Poll types.
1 parent 5431a06 commit a5b67d1

File tree

8 files changed

+288
-40
lines changed

8 files changed

+288
-40
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ repository = "https://github.com/withoutboats/fehler"
1212
path = "./fehler-macros"
1313
version = "1.0.0-alpha.2"
1414

15+
[features]
16+
nightly = []
17+
1518
[workspace]

fehler-macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ quote = "1.0.0"
1616
proc-macro2 = "1.0.0"
1717

1818
[dependencies.syn]
19-
features = ["default", "fold", "full"]
19+
features = ["default", "fold", "full", "parsing"]
2020
version = "1.0.0"

fehler-macros/src/args.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// The Args type parses the arguments to the `#[throws]` macro.
2+
//
3+
// It is also responsible for transforming the return type by injecting
4+
// the return type and the error type into the wrapper type.
5+
6+
use proc_macro2::Span;
7+
use syn::{GenericArgument, Path, PathArguments, ReturnType, Token, Type};
8+
use syn::parse::*;
9+
10+
const WRAPPER_MUST_BE_PATH: &str = "Wrapper type must be a normal path type";
11+
12+
pub struct Args {
13+
error: Option<Type>,
14+
wrapper: Option<Type>,
15+
}
16+
17+
impl Args {
18+
pub fn ret(&mut self, ret: ReturnType) -> ReturnType {
19+
let (arrow, ret) = match ret {
20+
ReturnType::Default => (arrow(), unit()),
21+
ReturnType::Type(arrow, ty) => (arrow, *ty),
22+
};
23+
ReturnType::Type(arrow, Box::new(self.inject_to_wrapper(ret)))
24+
25+
}
26+
27+
fn inject_to_wrapper(&mut self, ret: Type) -> Type {
28+
if let Some(Type::Path(mut wrapper)) = self.wrapper.take() {
29+
let types = if let Some(error) = self.error.take() {
30+
vec![ret, error].into_iter().map(GenericArgument::Type)
31+
} else {
32+
vec![ret].into_iter().map(GenericArgument::Type)
33+
};
34+
35+
match innermost_path_arguments(&mut wrapper.path) {
36+
args @ &mut PathArguments::None => {
37+
*args = PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
38+
colon2_token: None,
39+
lt_token: Token![<](Span::call_site()),
40+
args: types.collect(),
41+
gt_token: Token![>](Span::call_site()),
42+
});
43+
}
44+
PathArguments::AngleBracketed(args) => args.args.extend(types),
45+
_ => panic!(WRAPPER_MUST_BE_PATH)
46+
}
47+
48+
Type::Path(wrapper)
49+
} else { panic!(WRAPPER_MUST_BE_PATH) }
50+
}
51+
}
52+
53+
impl Parse for Args {
54+
fn parse(input: ParseStream) -> Result<Args> {
55+
let error = match input.peek(Token![as]) {
56+
true => None,
57+
false => {
58+
let error = input.parse()?;
59+
Some(match error {
60+
Type::Infer(_) => default_error(),
61+
_ => error,
62+
})
63+
}
64+
};
65+
66+
let wrapper = Some(match input.parse::<Token![as]>().is_ok() {
67+
true => input.parse()?,
68+
false => result(),
69+
});
70+
71+
Ok(Args { error, wrapper })
72+
}
73+
}
74+
75+
fn innermost_path_arguments(path: &mut Path) -> &mut PathArguments {
76+
let arguments = &mut path.segments.last_mut().expect(WRAPPER_MUST_BE_PATH).arguments;
77+
match arguments {
78+
PathArguments::None => arguments,
79+
PathArguments::AngleBracketed(args) => {
80+
match args.args.last_mut() {
81+
Some(GenericArgument::Type(Type::Path(inner))) => {
82+
innermost_path_arguments(&mut inner.path)
83+
}
84+
// Bizarre cases like `#[throw(_ as MyTryType<'a>)]` just not supported currently
85+
_ => panic!("Certain strange wrapper types not supported"),
86+
}
87+
}
88+
_ => panic!(WRAPPER_MUST_BE_PATH)
89+
}
90+
}
91+
92+
fn arrow() -> syn::token::RArrow {
93+
Token![->](Span::call_site())
94+
}
95+
96+
fn unit() -> Type {
97+
syn::parse_str("()").unwrap()
98+
}
99+
100+
fn result() -> Type {
101+
syn::parse_str("::core::result::Result").unwrap()
102+
}
103+
104+
fn default_error() -> Type {
105+
syn::parse_str("crate::Error").unwrap()
106+
}

fehler-macros/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
extern crate proc_macro;
22

3+
mod args;
34
mod throws;
45

56
use proc_macro::*;
67

8+
use args::Args;
9+
use throws::Throws;
10+
711
#[proc_macro_attribute]
812
pub fn throws(args: TokenStream, input: TokenStream) -> TokenStream {
9-
crate::throws::entry(args, input)
13+
let args = syn::parse_macro_input!(args as Args);
14+
Throws::new(args).fold(input)
1015
}

fehler-macros/src/throws.rs

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
1+
// This module implements the Throws folder.
2+
//
3+
// The Throws folder actually visits the item being processed and performs two
4+
// processes:
5+
// - It ok wraps return expressions and inserts terminal Ok(())s.
6+
// - It delegates return type rewriting to the Args type.
7+
18
use proc_macro::*;
29
use syn::fold::Fold;
310

4-
struct Throws {
5-
ty: syn::Type,
11+
use crate::Args;
12+
13+
pub struct Throws {
14+
args: Args,
615
outer_fn: bool,
716
}
817

9-
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
10-
let mut ty = syn::parse(args).unwrap_or_else(|_| {
11-
panic!("argument to #[throws] attribute must be a type")
12-
});
13-
14-
if let syn::Type::Infer(_) = ty {
15-
ty = syn::parse2(quote::quote!(crate::Error)).unwrap();
18+
impl Throws {
19+
pub fn new(args: Args) -> Throws {
20+
Throws { args, outer_fn: true }
1621
}
1722

18-
let mut throws = Throws { ty, outer_fn: true };
19-
20-
if let Ok(item_fn) = syn::parse(input.clone()) {
21-
let item_fn = throws.fold_item_fn(item_fn);
22-
quote::quote!(#item_fn).into()
23-
} else if let Ok(method) = syn::parse(input.clone()) {
24-
let method = throws.fold_impl_item_method(method);
25-
quote::quote!(#method).into()
26-
} else if let Ok(method) = syn::parse(input.clone()) {
27-
let method = throws.fold_trait_item_method(method);
28-
quote::quote!(#method).into()
29-
} else {
30-
panic!("#[throws] attribute can only be applied to functions and methods")
23+
pub fn fold(&mut self, input: TokenStream) -> TokenStream {
24+
if let Ok(item_fn) = syn::parse(input.clone()) {
25+
let item_fn = self.fold_item_fn(item_fn);
26+
quote::quote!(#item_fn).into()
27+
} else if let Ok(method) = syn::parse(input.clone()) {
28+
let method = self.fold_impl_item_method(method);
29+
quote::quote!(#method).into()
30+
} else if let Ok(method) = syn::parse(input.clone()) {
31+
let method = self.fold_trait_item_method(method);
32+
quote::quote!(#method).into()
33+
} else {
34+
panic!("#[throws] attribute can only be applied to functions and methods")
35+
}
3136
}
3237
}
3338

@@ -91,22 +96,13 @@ impl Fold for Throws {
9196
}
9297

9398
fn fold_return_type(&mut self, i: syn::ReturnType) -> syn::ReturnType {
94-
let error = &self.ty;
95-
match i {
96-
syn::ReturnType::Default => {
97-
syn::parse2(quote::quote!(-> ::core::result::Result<(), #error>)).unwrap()
98-
}
99-
syn::ReturnType::Type(arrow, ty) => {
100-
let result = syn::parse2(quote::quote!(::core::result::Result<#ty, #error>)).unwrap();
101-
syn::ReturnType::Type(arrow, result)
102-
}
103-
}
99+
self.args.ret(i)
104100
}
105101

106102
fn fold_expr_return(&mut self, i: syn::ExprReturn) -> syn::ExprReturn {
107103
let ok = match &i.expr {
108-
Some(expr) => syn::parse2(quote::quote!(::core::result::Result::Ok(#expr))).unwrap(),
109-
None => syn::parse2(quote::quote!(::core::result::Result::Ok(()))).unwrap(),
104+
Some(expr) => ok(expr),
105+
None => ok_unit(),
110106
};
111107
syn::ExprReturn { expr: Some(Box::new(ok)), ..i }
112108
}
@@ -121,13 +117,13 @@ fn modify_tail(is_unit_fn: bool, stmts: &mut Vec<syn::Stmt>) {
121117
let new = syn::parse2(quote::quote!(#e;)).unwrap();
122118
stmts.pop();
123119
stmts.push(new);
124-
stmts.push(syn::Stmt::Expr(syn::parse2(quote::quote!(::core::result::Result::Ok(()))).unwrap()));
120+
stmts.push(syn::Stmt::Expr(ok_unit()));
125121
}
126122
Some(syn::Stmt::Expr(e)) => {
127-
*e = syn::parse2(quote::quote!(::core::result::Result::Ok(#e))).unwrap();
123+
*e = ok(e);
128124
}
129125
_ if is_unit_fn => {
130-
stmts.push(syn::Stmt::Expr(syn::parse2(quote::quote!(::core::result::Result::Ok(()))).unwrap()));
126+
stmts.push(syn::Stmt::Expr(ok_unit()));
131127
}
132128
_ => { }
133129
}
@@ -144,3 +140,11 @@ fn is_unit_fn(i: &syn::ReturnType) -> bool {
144140
}
145141
}
146142
}
143+
144+
fn ok(expr: &syn::Expr) -> syn::Expr {
145+
syn::parse2(quote::quote!(<_ as ::fehler::__internal::_Succeed>::from_ok(#expr))).unwrap()
146+
}
147+
148+
fn ok_unit() -> syn::Expr {
149+
syn::parse2(quote::quote!(<_ as ::fehler::__internal::_Succeed>::from_ok(()))).unwrap()
150+
}

src/lib.rs

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![no_std]
2+
#![cfg_attr(feature = "nightly", feature(try_trait))]
13
#[doc(inline)]
24
/// Annotations a function that "throws" a Result.
35
///
@@ -35,5 +37,106 @@ pub use fehler_macros::throws;
3537
/// This macro is equivalent to `Err($err)?`.
3638
#[macro_export]
3739
macro_rules! throw {
38-
($err:expr) => (return ::core::result::Result::Err(::core::convert::From::from($err)))
40+
($err:expr) => (return <_ as $crate::__internal::_Throw>::from_error((::core::convert::From::from($err))));
41+
() => (return <_ as ::core::default::Default>::default());
42+
}
43+
44+
#[doc(hidden)]
45+
pub mod __internal {
46+
pub trait _Succeed {
47+
type Ok;
48+
fn from_ok(ok: Self::Ok) -> Self;
49+
}
50+
51+
pub trait _Throw {
52+
type Error;
53+
fn from_error(error: Self::Error) -> Self;
54+
}
55+
56+
#[cfg(not(feature = "nightly"))]
57+
mod stable {
58+
use core::task::Poll;
59+
60+
impl<T, E> super::_Succeed for Result<T, E> {
61+
type Ok = T;
62+
fn from_ok(ok: T) -> Self {
63+
Ok(ok)
64+
}
65+
}
66+
67+
impl<T, E> super::_Throw for Result<T, E> {
68+
type Error = E;
69+
fn from_error(error: Self::Error) -> Self {
70+
Err(error)
71+
}
72+
}
73+
74+
impl<T, E> super::_Succeed for Poll<Result<T, E>> {
75+
type Ok = Poll<T>;
76+
77+
fn from_ok(ok: Self::Ok) -> Self {
78+
match ok {
79+
Poll::Ready(ok) => Poll::Ready(Ok(ok)),
80+
Poll::Pending => Poll::Pending,
81+
}
82+
}
83+
}
84+
85+
impl<T, E> super::_Throw for Poll<Result<T, E>> {
86+
type Error = E;
87+
88+
fn from_error(error: Self::Error) -> Self {
89+
Poll::Ready(Err(error))
90+
}
91+
}
92+
93+
impl<T, E> super::_Succeed for Poll<Option<Result<T, E>>> {
94+
type Ok = Poll<Option<T>>;
95+
96+
fn from_ok(ok: Self::Ok) -> Self {
97+
match ok {
98+
Poll::Ready(Some(ok)) => Poll::Ready(Some(Ok(ok))),
99+
Poll::Ready(None) => Poll::Ready(None),
100+
Poll::Pending => Poll::Pending,
101+
}
102+
}
103+
}
104+
105+
impl<T, E> super::_Throw for Poll<Option<Result<T, E>>> {
106+
type Error = E;
107+
108+
fn from_error(error: Self::Error) -> Self {
109+
Poll::Ready(Some(Err(error)))
110+
}
111+
}
112+
113+
impl<T> super::_Succeed for Option<T> {
114+
type Ok = T;
115+
116+
fn from_ok(ok: Self::Ok) -> Self {
117+
Some(ok)
118+
}
119+
}
120+
}
121+
122+
#[cfg(feature = "nightly")]
123+
mod nightly {
124+
use core::ops::Try;
125+
126+
impl<T> super::_Succeed for T where T: Try {
127+
type Ok = <T as Try>::Ok;
128+
129+
fn from_ok(ok: Self::Ok) -> Self {
130+
<T as Try>::from_ok(ok)
131+
}
132+
}
133+
134+
impl<T> super::_Throw for T where T: Try {
135+
type Error = <T as Try>::Error;
136+
137+
fn from_error(error: Self::Error) -> Self {
138+
<T as Try>::from_error(error)
139+
}
140+
}
141+
}
39142
}

tests/option.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use fehler::*;
2+
3+
#[throws(as Option)]
4+
fn foo(x: bool) -> i32 {
5+
if x { throw!(); }
6+
0
7+
}
8+
9+
#[test]
10+
fn test_outcome_true() {
11+
assert!(foo(true).is_none())
12+
}
13+
14+
#[test]
15+
fn test_outcome_false() {
16+
assert_eq!(Some(0), foo(false))
17+
}

0 commit comments

Comments
 (0)