Skip to content

Commit c2404b4

Browse files
authored
Merge pull request Nullus157#8 from Nemo157/try
Add try macro without return type adjustment
2 parents 8a90f93 + a5b8886 commit c2404b4

File tree

4 files changed

+239
-26
lines changed

4 files changed

+239
-26
lines changed

macros/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,11 @@ use throws::Throws;
1111
#[proc_macro_attribute]
1212
pub fn throws(args: TokenStream, input: TokenStream) -> TokenStream {
1313
let args = syn::parse_macro_input!(args as Args);
14-
Throws::new(args).fold(input)
14+
Throws::new(Some(args)).fold(input)
15+
}
16+
17+
#[proc_macro_attribute]
18+
pub fn try_fn(args: TokenStream, input: TokenStream) -> TokenStream {
19+
assert!(args.to_string() == "", "try_fn does not take arguments");
20+
Throws::new(None).fold(input)
1521
}

macros/src/throws.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ use syn::fold::Fold;
1111
use crate::Args;
1212

1313
pub struct Throws {
14-
args: Args,
14+
args: Option<Args>,
1515
outer_fn: bool,
1616
return_type: syn::Type,
1717
}
1818

1919
impl Throws {
20-
pub fn new(args: Args) -> Throws {
20+
pub fn new(args: Option<Args>) -> Throws {
2121
Throws {
2222
args,
2323
outer_fn: true,
@@ -110,9 +110,13 @@ impl Fold for Throws {
110110
if !self.outer_fn {
111111
return i;
112112
}
113-
let return_type = self.args.ret(i);
114-
let syn::ReturnType::Type(_, ty) = &return_type else {
115-
unreachable!()
113+
let return_type = match &mut self.args {
114+
Some(args) => args.ret(i),
115+
None => i,
116+
};
117+
let ty = match &return_type {
118+
syn::ReturnType::Type(_, ty) => (**ty).clone(),
119+
syn::ReturnType::Default => syn::Type::Infer(syn::parse_quote!(_)),
116120
};
117121
struct ImplTraitToInfer;
118122
impl Fold for ImplTraitToInfer {
@@ -123,7 +127,7 @@ impl Fold for Throws {
123127
}
124128
}
125129
}
126-
self.return_type = ImplTraitToInfer.fold_type(ty.as_ref().clone());
130+
self.return_type = ImplTraitToInfer.fold_type(ty);
127131
return_type
128132
}
129133

src/lib.rs

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
#![no_std]
22

3-
//! Annotations a function that "throws" a Result.
3+
//! Annotates a function that "throws" a Result.
44
//!
5-
//! Inside functions tagged with `throws`, you can use `?` and the `throw!` macro to return errors,
6-
//! but you don't need to wrap the successful return values in `Ok`.
5+
//! Inside functions tagged with either `throws` or `try_fn`, you can use `?` and the `throw!`
6+
//! macro to return errors, but you don't need to wrap the successful return values in `Ok`.
77
//!
8-
//! Using this syntax, you can write fallible functions almost as if they were nonfallible. Every
8+
//! Using this syntax, you can write fallible functions almost as if they were infallible. Every
99
//! time a function call would return a `Result`, you "re-raise" the error using `?`, and if you
1010
//! wish to raise your own error, you can return it with the `throw!` macro.
1111
//!
12+
//! The difference between `throws` and `try_fn` is in the function signature, with `throws` you
13+
//! write the signature as if it were infallible too, it will be transformed into a `Result` for
14+
//! you. With `try_fn` you write the signature as normal and only the body of the function will be
15+
//! transformed.
16+
//!
1217
//! ## Example
18+
//!
1319
//! ```
1420
//! use std::io::{self, Read};
1521
//!
16-
//! use culpa::{throw, throws};
22+
//! use culpa::{throw, throws, try_fn};
1723
//!
1824
//! #[throws(io::Error)]
1925
//! fn check() {
@@ -27,13 +33,26 @@
2733
//!
2834
//! println!("Okay!");
2935
//! }
36+
//!
37+
//! #[try_fn]
38+
//! fn check_as_try_fn() -> std::io::Result<()> {
39+
//! let mut file = std::fs::File::open("The_House_of_the_Spirits.txt")?;
40+
//! let mut text = String::new();
41+
//! file.read_to_string(&mut text)?;
42+
//!
43+
//! if !text.starts_with("Barrabas came to us by sea, the child Clara wrote") {
44+
//! throw!(io::Error::from_raw_os_error(22));
45+
//! }
46+
//!
47+
//! println!("Okay!");
48+
//! }
3049
//! ```
3150
//!
32-
//! # Default Error Type
51+
//! # `throws` Default Error Type
3352
//!
34-
//! This macro supports a "default error type" - if you do not pass a type to the macro, it will
35-
//! use the type named `Error` in this scope. So if you have defined an error type in this
36-
//! module, that will be the error thrown by this function.
53+
//! The `throws` macro supports a "default error type" - if you do not pass a type to the macro, it
54+
//! will use the type named `Error` in the current scope. So if you have defined an error type in
55+
//! the module, that will be the error thrown by this function.
3756
//!
3857
//! You can access this feature by omitting the arguments entirely or by passing `_` as the type.
3958
//!
@@ -43,7 +62,7 @@
4362
//! use culpa::throws;
4463
//!
4564
//! // Set the default error type for this module:
46-
//! type Error = std::io::Error;
65+
//! use std::io::Error;
4766
//!
4867
//! #[throws]
4968
//! fn print() {
@@ -54,21 +73,28 @@
5473
//!
5574
//! # Throwing as an Option
5675
//!
57-
//! This syntax can also support functions which return an `Option` instead of a `Result`. The
58-
//! way to access this is to pass `as Option` as the argument to `throw`.
76+
//! This syntax can also support functions which return an `Option` instead of a `Result`. To use
77+
//! this with `throws` pass `as Option` as the argument in place of the error type, to use it with
78+
//! `try_fn` just put it as the return type like normal
5979
//!
6080
//! In functions that return `Option`, you can use the `throw!()` macro without any argument to
6181
//! return `None`.
6282
//!
6383
//! ## Example
6484
//!
6585
//! ```
66-
//! use culpa::{throw, throws};
67-
//!
68-
//! #[throws(as Option)]
86+
//! #[culpa::throws(as Option)]
6987
//! fn example<T: Eq + Ord>(slice: &[T], needle: &T) -> usize {
7088
//! if !slice.contains(needle) {
71-
//! throw!();
89+
//! culpa::throw!();
90+
//! }
91+
//! slice.binary_search(needle).ok()?
92+
//! }
93+
//!
94+
//! #[culpa::try_fn]
95+
//! fn example_as_try_fn<T: Eq + Ord>(slice: &[T], needle: &T) -> Option<usize> {
96+
//! if !slice.contains(needle) {
97+
//! culpa::throw!();
7298
//! }
7399
//! slice.binary_search(needle).ok()?
74100
//! }
@@ -78,22 +104,28 @@
78104
//!
79105
//! The `?` syntax in Rust is controlled by a trait called `Try`, which is currently unstable.
80106
//! Because this feature is unstable and I don't want to maintain compatibility if its interface
81-
//! changes, this crate currently only works with two stable `Try` types: Result and Option.
107+
//! changes, this crate currently only works with two stable `Try` types: `Result` and `Option`.
82108
//! However, its designed so that it will hopefully support other `Try` types as well in the
83109
//! future.
84110
//!
85111
//! It's worth noting that `Try` also has some other stable implementations: specifically `Poll`.
86112
//! Because of the somewhat unusual implementation of `Try` for those types, this crate does not
87113
//! support `throws` syntax on functions that return `Poll` (so you can't use this syntax when
88-
//! implementing a Future by hand, for example). I hope to come up with a way to support Poll in
89-
//! the future.
114+
//! implementing a `Future` by hand, for example). I hope to come up with a way to support `Poll`
115+
//! in the future.
90116
91117
#[doc(inline)]
92118
/// Annotates a function that "throws" a Result.
93119
///
94120
/// See the main crate docs for more details.
95121
pub use culpa_macros::throws;
96122

123+
#[doc(inline)]
124+
/// Annotates a function that implicitly wraps a try block.
125+
///
126+
/// See the main crate docs for more details.
127+
pub use culpa_macros::try_fn;
128+
97129
/// Throw an error.
98130
///
99131
/// This macro is equivalent to `Err($err)?`.
@@ -215,3 +247,9 @@ pub mod __internal {
215247
/// }
216248
/// ```
217249
const _DEAD_CODE: () = ();
250+
251+
/// ```compile_fail
252+
/// #[culpa::try_(())]
253+
/// fn f() {}
254+
/// ```
255+
const _NO_TRY_ARGS: () = ();

tests/try.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use culpa::{throw, try_fn};
2+
3+
type Error = isize;
4+
5+
#[try_fn]
6+
pub fn unit_fn() -> Result<(), Error> {}
7+
8+
#[try_fn]
9+
pub fn returns_fn() -> Result<i32, Error> {
10+
return 0;
11+
}
12+
13+
#[try_fn]
14+
pub fn returns_unit_fn() -> Result<(), Error> {
15+
if true {
16+
return;
17+
}
18+
}
19+
20+
#[try_fn]
21+
pub fn tail_returns_value() -> Result<i32, Error> {
22+
0
23+
}
24+
25+
#[try_fn]
26+
pub async fn async_fn() -> Result<(), Error> {}
27+
28+
#[try_fn]
29+
pub async fn async_fn_with_ret() -> Result<i32, Error> {
30+
0
31+
}
32+
33+
#[try_fn]
34+
pub fn throws_error() -> Result<(), i32> {
35+
if true {
36+
throw!(0);
37+
}
38+
}
39+
40+
#[try_fn]
41+
pub fn throws_and_has_return_type() -> Result<&'static str, i32> {
42+
if true {
43+
return "success";
44+
} else if false {
45+
throw!(0);
46+
}
47+
"okay"
48+
}
49+
50+
#[try_fn]
51+
pub fn throws_generics<E>() -> Result<(), E> {}
52+
53+
pub struct Foo;
54+
55+
impl Foo {
56+
#[try_fn]
57+
pub fn static_method() -> Result<(), Error> {}
58+
59+
#[try_fn]
60+
pub fn bar(&self) -> Result<i32, Error> {
61+
if true {
62+
return 1;
63+
}
64+
0
65+
}
66+
}
67+
68+
#[try_fn]
69+
pub fn has_inner_fn() -> Result<(), Error> {
70+
fn inner_fn() -> i32 {
71+
0
72+
}
73+
let _: i32 = inner_fn();
74+
}
75+
76+
#[try_fn]
77+
pub fn has_inner_closure() -> Result<(), Error> {
78+
let f = || 0;
79+
let _: i32 = f();
80+
}
81+
82+
#[try_fn]
83+
pub async fn has_inner_async_block() -> Result<(), Error> {
84+
let f = async { 0 };
85+
let _: i32 = f.await;
86+
}
87+
88+
#[try_fn]
89+
pub fn throws_as_result() -> Result<i32, Error> {
90+
0
91+
}
92+
93+
#[try_fn]
94+
pub fn throws_as_result_alias() -> std::io::Result<i32> {
95+
0
96+
}
97+
98+
#[try_fn]
99+
pub fn ommitted_error() -> Result<(), Error> {}
100+
101+
pub mod foo {
102+
use culpa::{throw, try_fn};
103+
104+
pub type Error = i32;
105+
106+
#[try_fn]
107+
pub fn throws_integer() -> Result<(), i32> {
108+
throw!(0);
109+
}
110+
}
111+
112+
pub mod foo_trait_obj {
113+
use culpa::try_fn;
114+
pub trait FooTrait {}
115+
116+
struct FooStruct;
117+
118+
pub struct FooError;
119+
impl FooTrait for FooStruct {}
120+
121+
#[try_fn]
122+
pub fn foo() -> Result<Box<dyn FooTrait>, FooError> {
123+
Box::new(FooStruct)
124+
}
125+
}
126+
127+
#[try_fn]
128+
pub fn let_else(a: Option<u8>) -> Result<u8, Error> {
129+
let Some(a) = a else {
130+
return 0;
131+
};
132+
a
133+
}
134+
135+
#[try_fn]
136+
pub fn impl_trait() -> Result<impl std::fmt::Debug, Error> {}
137+
138+
#[try_fn]
139+
#[deny(unreachable_code)]
140+
pub fn unreachable() -> Result<(), i32> {
141+
todo!()
142+
}
143+
144+
trait Example {
145+
#[try_fn]
146+
fn foo() -> Result<i32, Error>;
147+
}
148+
149+
#[try_fn]
150+
fn as_option(x: bool) -> Option<i32> {
151+
if x {
152+
throw!();
153+
}
154+
0
155+
}
156+
157+
#[test]
158+
fn test_as_option_true() {
159+
assert_eq!(None, as_option(true));
160+
}
161+
162+
#[test]
163+
fn test_as_option_false() {
164+
assert_eq!(Some(0), as_option(false))
165+
}

0 commit comments

Comments
 (0)