diff --git a/README.md b/README.md index de34da4..8296928 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [build status](https://github.com/mcmah309/error_set/actions?query=branch%3Amaster) -A concise way to define errors and ergonomically coerce a subset into a superset with with just `.into()` or `?`, and coerce between intersecting sets with `coerce!`. +A concise way to define errors and ergonomically coerce a subset into a superset with with just `.into()` or `?`. `error_set` was inspired by zig's [error set](https://ziglang.org/documentation/master/#Error-Set-Type) and works functionally the same. @@ -500,82 +500,4 @@ fn main() { } } ``` - - -## The `coerce!` Macro -The `coerce!` macro handles coercing between intersecting sets (sets where some of the error types are in common). This allows only being explicit where relevant, such as the disjointedness. -e.g. given: -```rust -error_set! { - SetX = { - X - } || Common; - SetY = { - Y - } || Common; - Common = { - A, - B, - C, - D, - E, - F, - G, - H, - }; -} -``` -rather than writting: -```rust -fn setx_result_to_sety_result() -> Result<(), SetY> { - let _ok = match setx_result() { - Ok(ok) => ok, - Err(SetX::X) => {} // handle disjointedness - Err(SetX::A) => { - return Err(SetY::A); - } - Err(SetX::B) => { - return Err(SetY::B); - } - Err(SetX::C) => { - return Err(SetY::C); - } - Err(SetX::D) => { - return Err(SetY::D); - } - Err(SetX::E) => { - return Err(SetY::E); - } - Err(SetX::F) => { - return Err(SetY::F); - } - Err(SetX::G) => { - return Err(SetY::G); - } - Err(SetX::H) => { - return Err(SetY::H); - } - }; - Ok(()) -} -``` -one can write this, which compiles to the `match` statement above: -```rust -fn setx_result_to_sety_result() -> Result<(),SetY> { - let _ok = coerce!(setx_result() => { - Ok(ok) => ok, - Err(SetX::X) => {}, // handle disjointedness - { Err(SetX) => return Err(SetY) } // terminal coercion - }); - Ok(()) -} -``` -The `coerce!` macro is a flat fast (no tt muncher 🦫) declarative macro created by the `error_set!` macro for the set. -`coerce!` behaves like a regular `match` statement, except it allows a terminal coercion statement between sets. e.g. -```rust -{ Err(SetX) => return Err(SetY) } -{ Err(SetX) => Err(SetY) } -{ SetX => return SetY } -{ SetX => SetY } -``` -With `coerce!`, one can concisely handle specific variants of errors as they bubble up the call stack and propagate the rest. \ No newline at end of file + \ No newline at end of file diff --git a/impl/src/expand.rs b/impl/src/expand.rs index 001e970..df8e4db 100644 --- a/impl/src/expand.rs +++ b/impl/src/expand.rs @@ -1,5 +1,7 @@ use std::{cell::RefCell, rc::Rc}; +#[cfg(feature = "coerce_macro")] +use coerce_macro::add_coerce_macro; use proc_macro2::TokenStream; use quote::TokenStreamExt; use syn::{Attribute, Ident}; @@ -253,58 +255,198 @@ fn impl_froms(error_enum_node: &ErrorEnumGraphNode, token_stream: &mut TokenStre }) } } +//************************************************************************// +#[derive(Clone)] +struct ErrorEnumGraphNode { + pub(crate) error_enum: ErrorEnum, + /// nodes where all error variants of the error enum are in this error enum's error variants. + pub(crate) subsets: Vec>>, +} + +impl PartialEq for ErrorEnumGraphNode { + fn eq(&self, other: &Self) -> bool { + self.error_enum == other.error_enum + } +} + +impl ErrorEnumGraphNode { + pub(crate) fn new(node: ErrorEnum) -> ErrorEnumGraphNode { + ErrorEnumGraphNode { + error_enum: node, + subsets: Vec::new(), + } + } +} + +#[derive(Clone)] +pub(crate) struct ErrorEnum { + pub(crate) attributes: Vec, + pub(crate) error_name: Ident, + pub(crate) error_variants: Vec, +} + +impl std::hash::Hash for ErrorEnum { + fn hash(&self, state: &mut H) { + self.error_name.hash(state); + } +} + +impl Eq for ErrorEnum {} + +impl PartialEq for ErrorEnum { + fn eq(&self, other: &Self) -> bool { + self.error_name == other.error_name + } +} //************************************************************************// #[cfg(feature = "coerce_macro")] -fn add_coerce_macro(error_enums: &Vec, token_stream: &mut TokenStream) { - let enum_intersections: Vec = construct_set_intersections(&error_enums); - let mut macro_pattern_token_stream = TokenStream::new(); - for enum_interscetion in enum_intersections { - let EnumIntersection { - enum1: enum1_name, - enum2: enum2_name, - intersection, - } = enum_interscetion; - let mut match_arms_return_err = TokenStream::new(); - let mut match_arms_err = TokenStream::new(); - let mut match_arms_return = TokenStream::new(); - let mut match_arms = TokenStream::new(); - for variant in intersection { - match variant { - AstErrorEnumVariant::SourceErrorVariant(source_variant) => { - let variant = source_variant.name; - match_arms_return_err.append_all(quote::quote! { +mod coerce_macro { + //! ## The `coerce!` Macro + //! + //! The `coerce!` macro handles coercing between intersecting sets (sets where some of the error types are in common). This allows only being explicit where relevant, such as the disjointedness. + //! + //! e.g. given: + //! + //! ```rust + //! error_set! { + //! SetX = { + //! X + //! } || Common; + //! SetY = { + //! Y + //! } || Common; + //! Common = { + //! A, + //! B, + //! C, + //! D, + //! E, + //! F, + //! G, + //! H, + //! }; + //! } + //! ``` + //! + //! rather than writing: + //! + //! ```rust + //! fn setx_result_to_sety_result() -> Result<(), SetY> { + //! let _ok = match setx_result() { + //! Ok(ok) => ok, + //! Err(SetX::X) => {} // handle disjointedness + //! Err(SetX::A) => { + //! return Err(SetY::A); + //! } + //! Err(SetX::B) => { + //! return Err(SetY::B); + //! } + //! Err(SetX::C) => { + //! return Err(SetY::C); + //! } + //! Err(SetX::D) => { + //! return Err(SetY::D); + //! } + //! Err(SetX::E) => { + //! return Err(SetY::E); + //! } + //! Err(SetX::F) => { + //! return Err(SetY::F); + //! } + //! Err(SetX::G) => { + //! return Err(SetY::G); + //! } + //! Err(SetX::H) => { + //! return Err(SetY::H); + //! } + //! }; + //! Ok(()) + //! } + //! ``` + //! + //! one can write this, which compiles to the `match` statement above: + //! + //! ```rust + //! fn setx_result_to_sety_result() -> Result<(), SetY> { + //! let _ok = coerce!(setx_result() => { + //! Ok(ok) => ok, + //! Err(SetX::X) => {}, // handle disjointedness + //! { Err(SetX) => return Err(SetY) } // terminal coercion + //! }); + //! Ok(()) + //! } + //! ``` + //! + //! The `coerce!` macro is a flat fast (no tt muncher 🦫) declarative macro created by the `error_set!` macro for the set. + //! `coerce!` behaves like a regular `match` statement, except it allows a terminal coercion statement between sets. e.g. + //! + //! ```rust + //! { Err(SetX) => return Err(SetY) } + //! { Err(SetX) => Err(SetY) } + //! { SetX => return SetY } + //! { SetX => SetY } + //! ``` + //! + //! With `coerce!`, one can concisely handle specific variants of errors as they bubble up the call stack and propagate the rest. + + use proc_macro2::TokenStream; + use quote::TokenStreamExt; + use syn::Ident; + + use crate::ast::AstErrorEnumVariant; + + use super::ErrorEnum; + + pub(crate) fn add_coerce_macro(error_enums: &Vec, token_stream: &mut TokenStream) { + let enum_intersections: Vec = construct_set_intersections(&error_enums); + let mut macro_pattern_token_stream = TokenStream::new(); + for enum_interscetion in enum_intersections { + let EnumIntersection { + enum1: enum1_name, + enum2: enum2_name, + intersection, + } = enum_interscetion; + let mut match_arms_return_err = TokenStream::new(); + let mut match_arms_err = TokenStream::new(); + let mut match_arms_return = TokenStream::new(); + let mut match_arms = TokenStream::new(); + for variant in intersection { + match variant { + AstErrorEnumVariant::SourceErrorVariant(source_variant) => { + let variant = source_variant.name; + match_arms_return_err.append_all(quote::quote! { Err(#enum1_name::#variant(source)) => { return Err(#enum2_name::#variant(source)); }, }); - match_arms_err.append_all(quote::quote! { + match_arms_err.append_all(quote::quote! { Err(#enum1_name::#variant(source)) => { Err(#enum2_name::#variant(source)) }, }); - match_arms_return.append_all(quote::quote! { + match_arms_return.append_all(quote::quote! { #enum1_name::#variant(source) => { return #enum2_name::#variant(source); }, }); - match_arms.append_all(quote::quote! { - #enum1_name::#variant(source) => { #enum2_name::#variant(source) }, - }); - }, - AstErrorEnumVariant::Variant(variant) => { - let variant = variant.name; - match_arms_return_err.append_all(quote::quote! { - Err(#enum1_name::#variant) => { return Err(#enum2_name::#variant); }, - }); - match_arms_err.append_all(quote::quote! { - Err(#enum1_name::#variant) => { Err(#enum2_name::#variant) }, - }); - match_arms_return.append_all(quote::quote! { - #enum1_name::#variant => { return #enum2_name::#variant; }, - }); - match_arms.append_all(quote::quote! { - #enum1_name::#variant => { #enum2_name::#variant }, - }); - }, + match_arms.append_all(quote::quote! { + #enum1_name::#variant(source) => { #enum2_name::#variant(source) }, + }); + } + AstErrorEnumVariant::Variant(variant) => { + let variant = variant.name; + match_arms_return_err.append_all(quote::quote! { + Err(#enum1_name::#variant) => { return Err(#enum2_name::#variant); }, + }); + match_arms_err.append_all(quote::quote! { + Err(#enum1_name::#variant) => { Err(#enum2_name::#variant) }, + }); + match_arms_return.append_all(quote::quote! { + #enum1_name::#variant => { return #enum2_name::#variant; }, + }); + match_arms.append_all(quote::quote! { + #enum1_name::#variant => { #enum2_name::#variant }, + }); + } + } } - } - macro_pattern_token_stream.append_all(quote::quote! { + macro_pattern_token_stream.append_all(quote::quote! { ($expr:expr => { $($patterns:pat => $results:expr$(,)?)+, {Err(#enum1_name) => return Err(#enum2_name)} }) => { match $expr { $($patterns => $results,)+ @@ -330,19 +472,19 @@ fn add_coerce_macro(error_enums: &Vec, token_stream: &mut TokenStream } }; }); - } - // when no default coercion - macro_pattern_token_stream.append_all(quote::quote! { - ($expr:expr => { $($patterns:pat => $results:expr$(,)?)+ }) => { - match $expr { - $($patterns => $results,)+ - } - }; - }); - // - macro_pattern_token_stream.append_all(quote::quote! { - ($($other:tt)*) => { - compile_error!(r#" + } + // when no default coercion + macro_pattern_token_stream.append_all(quote::quote! { + ($expr:expr => { $($patterns:pat => $results:expr$(,)?)+ }) => { + match $expr { + $($patterns => $results,)+ + } + }; + }); + // + macro_pattern_token_stream.append_all(quote::quote! { + ($($other:tt)*) => { + compile_error!(r#" No patterns matched. Possible reasons: 1. There are no intersections between the sets. @@ -360,98 +502,61 @@ coerce!($VAR => { }); ``` "#) - }; - }); - token_stream.append_all(quote::quote! { - #[allow(unused_macros)] - macro_rules! coerce { - #macro_pattern_token_stream - } + }; + }); + token_stream.append_all(quote::quote! { + #[allow(unused_macros)] + macro_rules! coerce { + #macro_pattern_token_stream + } - pub(crate) use coerce; - }); -} + pub(crate) use coerce; + }); + } -#[cfg(feature = "coerce_macro")] -fn construct_set_intersections(error_enums: &Vec) -> Vec { - let mut enum_intersections: Vec = Vec::new(); - let length = error_enums.len(); - for index1 in 0..length { - for index2 in 0..length { - let enum1 = &error_enums[index1]; - let enum2 = &error_enums[index2]; - let mut intersections = Vec::new(); - for variant in &enum1.error_variants { - if enum2.error_variants.contains(&variant) { - intersections.push(variant.clone()); + fn construct_set_intersections(error_enums: &Vec) -> Vec { + let mut enum_intersections: Vec = Vec::new(); + let length = error_enums.len(); + for index1 in 0..length { + for index2 in 0..length { + let enum1 = &error_enums[index1]; + let enum2 = &error_enums[index2]; + let mut intersections = Vec::new(); + for variant in &enum1.error_variants { + if enum2.error_variants.contains(&variant) { + intersections.push(variant.clone()); + } + } + if !intersections.is_empty() { + let enum_intersection = EnumIntersection::new( + enum1.error_name.clone(), + enum2.error_name.clone(), + intersections, + ); + enum_intersections.push(enum_intersection); } } - if !intersections.is_empty() { - let enum_intersection = EnumIntersection::new(enum1.error_name.clone(), enum2.error_name.clone(), intersections); - enum_intersections.push(enum_intersection); - } - } - } - enum_intersections -} - -#[cfg(feature = "coerce_macro")] -struct EnumIntersection { - pub(crate) enum1: Ident, - pub(crate) enum2: Ident, - pub(crate) intersection: Vec, -} - -#[cfg(feature = "coerce_macro")] -impl EnumIntersection { - pub(crate) fn new(enum1: Ident, enum2: Ident, intersection: Vec) -> EnumIntersection { - EnumIntersection { - enum1, - enum2, - intersection, } + enum_intersections } -} -//************************************************************************// -#[derive(Clone)] -struct ErrorEnumGraphNode { - pub(crate) error_enum: ErrorEnum, - /// nodes where all error variants of the error enum are in this error enum's error variants. - pub(crate) subsets: Vec>>, -} -impl PartialEq for ErrorEnumGraphNode { - fn eq(&self, other: &Self) -> bool { - self.error_enum == other.error_enum + struct EnumIntersection { + pub(crate) enum1: Ident, + pub(crate) enum2: Ident, + pub(crate) intersection: Vec, } -} -impl ErrorEnumGraphNode { - pub(crate) fn new(node: ErrorEnum) -> ErrorEnumGraphNode { - ErrorEnumGraphNode { - error_enum: node, - subsets: Vec::new(), + impl EnumIntersection { + pub(crate) fn new( + enum1: Ident, + enum2: Ident, + intersection: Vec, + ) -> EnumIntersection { + EnumIntersection { + enum1, + enum2, + intersection, + } } } } - -#[derive(Clone)] -pub(crate) struct ErrorEnum { - pub(crate) attributes: Vec, - pub(crate) error_name: Ident, - pub(crate) error_variants: Vec, -} - -impl std::hash::Hash for ErrorEnum { - fn hash(&self, state: &mut H) { - self.error_name.hash(state); - } -} - -impl Eq for ErrorEnum {} - -impl PartialEq for ErrorEnum { - fn eq(&self, other: &Self) -> bool { - self.error_name == other.error_name - } -} diff --git a/src/lib.rs b/src/lib.rs index ba92c94..7edd04c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + pub use error_set_impl::*; pub trait Coerce {