From 063249ac4c199763b612833208cd4dfa9b40dade Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Thu, 21 Sep 2023 09:50:33 +0200 Subject: [PATCH 1/3] Add try macro without return type adjustment --- macros/src/lib.rs | 8 ++- macros/src/throws.rs | 16 +++-- src/lib.rs | 12 ++++ tests/try.rs | 165 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 tests/try.rs diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 0f43fea..9e9f7e9 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -11,5 +11,11 @@ use throws::Throws; #[proc_macro_attribute] pub fn throws(args: TokenStream, input: TokenStream) -> TokenStream { let args = syn::parse_macro_input!(args as Args); - Throws::new(args).fold(input) + Throws::new(Some(args)).fold(input) +} + +#[proc_macro_attribute] +pub fn try_(args: TokenStream, input: TokenStream) -> TokenStream { + assert!(args.to_string() == "", "try does not take arguments"); + Throws::new(None).fold(input) } diff --git a/macros/src/throws.rs b/macros/src/throws.rs index 27acd2d..1c7f9cc 100644 --- a/macros/src/throws.rs +++ b/macros/src/throws.rs @@ -11,13 +11,13 @@ use syn::fold::Fold; use crate::Args; pub struct Throws { - args: Args, + args: Option, outer_fn: bool, return_type: syn::Type, } impl Throws { - pub fn new(args: Args) -> Throws { + pub fn new(args: Option) -> Throws { Throws { args, outer_fn: true, @@ -110,9 +110,13 @@ impl Fold for Throws { if !self.outer_fn { return i; } - let return_type = self.args.ret(i); - let syn::ReturnType::Type(_, ty) = &return_type else { - unreachable!() + let return_type = match &mut self.args { + Some(args) => args.ret(i), + None => i, + }; + let ty = match &return_type { + syn::ReturnType::Type(_, ty) => (**ty).clone(), + syn::ReturnType::Default => syn::Type::Infer(syn::parse_quote!(_)), }; struct ImplTraitToInfer; impl Fold for ImplTraitToInfer { @@ -123,7 +127,7 @@ impl Fold for Throws { } } } - self.return_type = ImplTraitToInfer.fold_type(ty.as_ref().clone()); + self.return_type = ImplTraitToInfer.fold_type(ty); return_type } diff --git a/src/lib.rs b/src/lib.rs index b246601..6a3839c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,12 @@ /// See the main crate docs for more details. pub use culpa_macros::throws; +#[doc(inline)] +/// Annotates a function that implicitly wraps a try block. +/// +/// See the main crate docs for more details. +pub use culpa_macros::try_; + /// Throw an error. /// /// This macro is equivalent to `Err($err)?`. @@ -215,3 +221,9 @@ pub mod __internal { /// } /// ``` const _DEAD_CODE: () = (); + +/// ```compile_fail +/// #[culpa::try_(())] +/// fn f() {} +/// ``` +const _NO_TRY_ARGS: () = (); diff --git a/tests/try.rs b/tests/try.rs new file mode 100644 index 0000000..8eeb758 --- /dev/null +++ b/tests/try.rs @@ -0,0 +1,165 @@ +use culpa::{throw, try_}; + +type Error = isize; + +#[try_] +pub fn unit_fn() -> Result<(), Error> {} + +#[try_] +pub fn returns_fn() -> Result { + return 0; +} + +#[try_] +pub fn returns_unit_fn() -> Result<(), Error> { + if true { + return; + } +} + +#[try_] +pub fn tail_returns_value() -> Result { + 0 +} + +#[try_] +pub async fn async_fn() -> Result<(), Error> {} + +#[try_] +pub async fn async_fn_with_ret() -> Result { + 0 +} + +#[try_] +pub fn throws_error() -> Result<(), i32> { + if true { + throw!(0); + } +} + +#[try_] +pub fn throws_and_has_return_type() -> Result<&'static str, i32> { + if true { + return "success"; + } else if false { + throw!(0); + } + "okay" +} + +#[try_] +pub fn throws_generics() -> Result<(), E> {} + +pub struct Foo; + +impl Foo { + #[try_] + pub fn static_method() -> Result<(), Error> {} + + #[try_] + pub fn bar(&self) -> Result { + if true { + return 1; + } + 0 + } +} + +#[try_] +pub fn has_inner_fn() -> Result<(), Error> { + fn inner_fn() -> i32 { + 0 + } + let _: i32 = inner_fn(); +} + +#[try_] +pub fn has_inner_closure() -> Result<(), Error> { + let f = || 0; + let _: i32 = f(); +} + +#[try_] +pub async fn has_inner_async_block() -> Result<(), Error> { + let f = async { 0 }; + let _: i32 = f.await; +} + +#[try_] +pub fn throws_as_result() -> Result { + 0 +} + +#[try_] +pub fn throws_as_result_alias() -> std::io::Result { + 0 +} + +#[try_] +pub fn ommitted_error() -> Result<(), Error> {} + +pub mod foo { + use culpa::{throw, try_}; + + pub type Error = i32; + + #[try_] + pub fn throws_integer() -> Result<(), i32> { + throw!(0); + } +} + +pub mod foo_trait_obj { + use culpa::try_; + pub trait FooTrait {} + + struct FooStruct; + + pub struct FooError; + impl FooTrait for FooStruct {} + + #[try_] + pub fn foo() -> Result, FooError> { + Box::new(FooStruct) + } +} + +#[try_] +pub fn let_else(a: Option) -> Result { + let Some(a) = a else { + return 0; + }; + a +} + +#[try_] +pub fn impl_trait() -> Result {} + +#[try_] +#[deny(unreachable_code)] +pub fn unreachable() -> Result<(), i32> { + todo!() +} + +trait Example { + #[try_] + fn foo() -> Result; +} + +#[try_] +fn as_option(x: bool) -> Option { + if x { + throw!(); + } + 0 +} + +#[test] +fn test_as_option_true() { + assert_eq!(None, as_option(true)); +} + +#[test] +fn test_as_option_false() { + assert_eq!(Some(0), as_option(false)) +} From 74947e75852490b692f101602ff5ced239755a5a Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Wed, 10 Jan 2024 22:24:27 +0100 Subject: [PATCH 2/3] Rename `try_` to `try_fn` --- macros/src/lib.rs | 4 ++-- src/lib.rs | 2 +- tests/try.rs | 54 +++++++++++++++++++++++------------------------ 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 9e9f7e9..dfd8868 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,7 +15,7 @@ pub fn throws(args: TokenStream, input: TokenStream) -> TokenStream { } #[proc_macro_attribute] -pub fn try_(args: TokenStream, input: TokenStream) -> TokenStream { - assert!(args.to_string() == "", "try does not take arguments"); +pub fn try_fn(args: TokenStream, input: TokenStream) -> TokenStream { + assert!(args.to_string() == "", "try_fn does not take arguments"); Throws::new(None).fold(input) } diff --git a/src/lib.rs b/src/lib.rs index 6a3839c..7a9419a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ pub use culpa_macros::throws; /// Annotates a function that implicitly wraps a try block. /// /// See the main crate docs for more details. -pub use culpa_macros::try_; +pub use culpa_macros::try_fn; /// Throw an error. /// diff --git a/tests/try.rs b/tests/try.rs index 8eeb758..7a526b2 100644 --- a/tests/try.rs +++ b/tests/try.rs @@ -1,43 +1,43 @@ -use culpa::{throw, try_}; +use culpa::{throw, try_fn}; type Error = isize; -#[try_] +#[try_fn] pub fn unit_fn() -> Result<(), Error> {} -#[try_] +#[try_fn] pub fn returns_fn() -> Result { return 0; } -#[try_] +#[try_fn] pub fn returns_unit_fn() -> Result<(), Error> { if true { return; } } -#[try_] +#[try_fn] pub fn tail_returns_value() -> Result { 0 } -#[try_] +#[try_fn] pub async fn async_fn() -> Result<(), Error> {} -#[try_] +#[try_fn] pub async fn async_fn_with_ret() -> Result { 0 } -#[try_] +#[try_fn] pub fn throws_error() -> Result<(), i32> { if true { throw!(0); } } -#[try_] +#[try_fn] pub fn throws_and_has_return_type() -> Result<&'static str, i32> { if true { return "success"; @@ -47,16 +47,16 @@ pub fn throws_and_has_return_type() -> Result<&'static str, i32> { "okay" } -#[try_] +#[try_fn] pub fn throws_generics() -> Result<(), E> {} pub struct Foo; impl Foo { - #[try_] + #[try_fn] pub fn static_method() -> Result<(), Error> {} - #[try_] + #[try_fn] pub fn bar(&self) -> Result { if true { return 1; @@ -65,7 +65,7 @@ impl Foo { } } -#[try_] +#[try_fn] pub fn has_inner_fn() -> Result<(), Error> { fn inner_fn() -> i32 { 0 @@ -73,44 +73,44 @@ pub fn has_inner_fn() -> Result<(), Error> { let _: i32 = inner_fn(); } -#[try_] +#[try_fn] pub fn has_inner_closure() -> Result<(), Error> { let f = || 0; let _: i32 = f(); } -#[try_] +#[try_fn] pub async fn has_inner_async_block() -> Result<(), Error> { let f = async { 0 }; let _: i32 = f.await; } -#[try_] +#[try_fn] pub fn throws_as_result() -> Result { 0 } -#[try_] +#[try_fn] pub fn throws_as_result_alias() -> std::io::Result { 0 } -#[try_] +#[try_fn] pub fn ommitted_error() -> Result<(), Error> {} pub mod foo { - use culpa::{throw, try_}; + use culpa::{throw, try_fn}; pub type Error = i32; - #[try_] + #[try_fn] pub fn throws_integer() -> Result<(), i32> { throw!(0); } } pub mod foo_trait_obj { - use culpa::try_; + use culpa::try_fn; pub trait FooTrait {} struct FooStruct; @@ -118,13 +118,13 @@ pub mod foo_trait_obj { pub struct FooError; impl FooTrait for FooStruct {} - #[try_] + #[try_fn] pub fn foo() -> Result, FooError> { Box::new(FooStruct) } } -#[try_] +#[try_fn] pub fn let_else(a: Option) -> Result { let Some(a) = a else { return 0; @@ -132,21 +132,21 @@ pub fn let_else(a: Option) -> Result { a } -#[try_] +#[try_fn] pub fn impl_trait() -> Result {} -#[try_] +#[try_fn] #[deny(unreachable_code)] pub fn unreachable() -> Result<(), i32> { todo!() } trait Example { - #[try_] + #[try_fn] fn foo() -> Result; } -#[try_] +#[try_fn] fn as_option(x: bool) -> Option { if x { throw!(); From a5b8886bb129f040ae636064cdb8c609ef97a8bf Mon Sep 17 00:00:00 2001 From: Wim Looman Date: Wed, 10 Jan 2024 22:33:02 +0100 Subject: [PATCH 3/3] Add `try_fn` to the crate docs + minor doc cleanups --- src/lib.rs | 64 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7a9419a..f7d6c36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,25 @@ #![no_std] -//! Annotations a function that "throws" a Result. +//! Annotates a function that "throws" a Result. //! -//! Inside functions tagged with `throws`, you can use `?` and the `throw!` macro to return errors, -//! but you don't need to wrap the successful return values in `Ok`. +//! Inside functions tagged with either `throws` or `try_fn`, you can use `?` and the `throw!` +//! macro to return errors, but you don't need to wrap the successful return values in `Ok`. //! -//! Using this syntax, you can write fallible functions almost as if they were nonfallible. Every +//! Using this syntax, you can write fallible functions almost as if they were infallible. Every //! time a function call would return a `Result`, you "re-raise" the error using `?`, and if you //! wish to raise your own error, you can return it with the `throw!` macro. //! +//! The difference between `throws` and `try_fn` is in the function signature, with `throws` you +//! write the signature as if it were infallible too, it will be transformed into a `Result` for +//! you. With `try_fn` you write the signature as normal and only the body of the function will be +//! transformed. +//! //! ## Example +//! //! ``` //! use std::io::{self, Read}; //! -//! use culpa::{throw, throws}; +//! use culpa::{throw, throws, try_fn}; //! //! #[throws(io::Error)] //! fn check() { @@ -27,13 +33,26 @@ //! //! println!("Okay!"); //! } +//! +//! #[try_fn] +//! fn check_as_try_fn() -> std::io::Result<()> { +//! let mut file = std::fs::File::open("The_House_of_the_Spirits.txt")?; +//! let mut text = String::new(); +//! file.read_to_string(&mut text)?; +//! +//! if !text.starts_with("Barrabas came to us by sea, the child Clara wrote") { +//! throw!(io::Error::from_raw_os_error(22)); +//! } +//! +//! println!("Okay!"); +//! } //! ``` //! -//! # Default Error Type +//! # `throws` Default Error Type //! -//! This macro supports a "default error type" - if you do not pass a type to the macro, it will -//! use the type named `Error` in this scope. So if you have defined an error type in this -//! module, that will be the error thrown by this function. +//! The `throws` macro supports a "default error type" - if you do not pass a type to the macro, it +//! will use the type named `Error` in the current scope. So if you have defined an error type in +//! the module, that will be the error thrown by this function. //! //! You can access this feature by omitting the arguments entirely or by passing `_` as the type. //! @@ -43,7 +62,7 @@ //! use culpa::throws; //! //! // Set the default error type for this module: -//! type Error = std::io::Error; +//! use std::io::Error; //! //! #[throws] //! fn print() { @@ -54,8 +73,9 @@ //! //! # Throwing as an Option //! -//! This syntax can also support functions which return an `Option` instead of a `Result`. The -//! way to access this is to pass `as Option` as the argument to `throw`. +//! This syntax can also support functions which return an `Option` instead of a `Result`. To use +//! this with `throws` pass `as Option` as the argument in place of the error type, to use it with +//! `try_fn` just put it as the return type like normal //! //! In functions that return `Option`, you can use the `throw!()` macro without any argument to //! return `None`. @@ -63,12 +83,18 @@ //! ## Example //! //! ``` -//! use culpa::{throw, throws}; -//! -//! #[throws(as Option)] +//! #[culpa::throws(as Option)] //! fn example(slice: &[T], needle: &T) -> usize { //! if !slice.contains(needle) { -//! throw!(); +//! culpa::throw!(); +//! } +//! slice.binary_search(needle).ok()? +//! } +//! +//! #[culpa::try_fn] +//! fn example_as_try_fn(slice: &[T], needle: &T) -> Option { +//! if !slice.contains(needle) { +//! culpa::throw!(); //! } //! slice.binary_search(needle).ok()? //! } @@ -78,15 +104,15 @@ //! //! The `?` syntax in Rust is controlled by a trait called `Try`, which is currently unstable. //! Because this feature is unstable and I don't want to maintain compatibility if its interface -//! changes, this crate currently only works with two stable `Try` types: Result and Option. +//! changes, this crate currently only works with two stable `Try` types: `Result` and `Option`. //! However, its designed so that it will hopefully support other `Try` types as well in the //! future. //! //! It's worth noting that `Try` also has some other stable implementations: specifically `Poll`. //! Because of the somewhat unusual implementation of `Try` for those types, this crate does not //! support `throws` syntax on functions that return `Poll` (so you can't use this syntax when -//! implementing a Future by hand, for example). I hope to come up with a way to support Poll in -//! the future. +//! implementing a `Future` by hand, for example). I hope to come up with a way to support `Poll` +//! in the future. #[doc(inline)] /// Annotates a function that "throws" a Result.