From 0978fa1c6435312ab5eb255b32defe4045d6866e Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 6 Jul 2021 00:56:16 -0400 Subject: [PATCH] Implement #[snafu(module)] support The new attribute `module` makes the derive macro put all the context selectors for structs and enums in a module. Closes #188 --- .../tests/ui/module-visibility.rs | 19 ++++ .../tests/ui/module-visibility.stderr | 12 ++ .../tests/ui/structs/attribute-misuse.stderr | 2 +- snafu-derive/Cargo.toml | 1 + snafu-derive/src/lib.rs | 107 ++++++++++++++++-- snafu-derive/src/parse.rs | 37 +++++- snafu-derive/src/shared.rs | 45 ++++++++ src/Snafu.md | 78 +++++++++++++ tests/module.rs | 102 +++++++++++++++++ tests/structs/main.rs | 1 + tests/structs/module.rs | 79 +++++++++++++ 11 files changed, 469 insertions(+), 14 deletions(-) create mode 100644 compatibility-tests/compile-fail/tests/ui/module-visibility.rs create mode 100644 compatibility-tests/compile-fail/tests/ui/module-visibility.stderr create mode 100644 tests/module.rs create mode 100644 tests/structs/module.rs diff --git a/compatibility-tests/compile-fail/tests/ui/module-visibility.rs b/compatibility-tests/compile-fail/tests/ui/module-visibility.rs new file mode 100644 index 00000000..415b4500 --- /dev/null +++ b/compatibility-tests/compile-fail/tests/ui/module-visibility.rs @@ -0,0 +1,19 @@ +mod inside { + use snafu::prelude::*; + + #[derive(Debug, Snafu)] + #[snafu(module)] + enum Error { + Variant, + } + + fn can_access_in_same_module() { + let _ = error::VariantSnafu; + } +} + +fn cant_access_outside_of_module() { + let _ = inside::error::VariantSnafu; +} + +fn main() {} diff --git a/compatibility-tests/compile-fail/tests/ui/module-visibility.stderr b/compatibility-tests/compile-fail/tests/ui/module-visibility.stderr new file mode 100644 index 00000000..402636e2 --- /dev/null +++ b/compatibility-tests/compile-fail/tests/ui/module-visibility.stderr @@ -0,0 +1,12 @@ +error[E0603]: module `error` is private + --> $DIR/module-visibility.rs:16:21 + | +16 | let _ = inside::error::VariantSnafu; + | ^^^^^ private module + | +note: the module `error` is defined here + --> $DIR/module-visibility.rs:4:21 + | +4 | #[derive(Debug, Snafu)] + | ^^^^^ + = note: this error originates in the derive macro `Snafu` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/compatibility-tests/compile-fail/tests/ui/structs/attribute-misuse.stderr b/compatibility-tests/compile-fail/tests/ui/structs/attribute-misuse.stderr index e8d6b8f8..09187184 100644 --- a/compatibility-tests/compile-fail/tests/ui/structs/attribute-misuse.stderr +++ b/compatibility-tests/compile-fail/tests/ui/structs/attribute-misuse.stderr @@ -1,4 +1,4 @@ -error: expected one of: `backtrace`, `context`, `crate_root`, `display`, `implicit`, `source`, `visibility`, `whatever` +error: expected one of: `backtrace`, `context`, `crate_root`, `display`, `implicit`, `module`, `source`, `visibility`, `whatever` --> $DIR/attribute-misuse.rs:5:13 | 5 | #[snafu(unknown_attribute)] diff --git a/snafu-derive/Cargo.toml b/snafu-derive/Cargo.toml index 147be9c4..9dcbe40e 100644 --- a/snafu-derive/Cargo.toml +++ b/snafu-derive/Cargo.toml @@ -21,3 +21,4 @@ proc-macro = true syn = { version = "1.0", features = ["full"] } quote = "1.0" proc-macro2 = "1.0" +heck = "0.3.3" diff --git a/snafu-derive/src/lib.rs b/snafu-derive/src/lib.rs index 39938c9a..564c79d2 100644 --- a/snafu-derive/src/lib.rs +++ b/snafu-derive/src/lib.rs @@ -24,6 +24,11 @@ type MultiSynResult = std::result::Result>; /// Some arbitrary tokens we treat as a black box type UserInput = Box; +enum ModuleName { + Default, + Custom(syn::Ident), +} + enum SnafuInfo { Enum(EnumInfo), NamedStruct(NamedStructInfo), @@ -35,8 +40,9 @@ struct EnumInfo { name: syn::Ident, generics: syn::Generics, variants: Vec, - default_visibility: UserInput, + default_visibility: Option, default_suffix: SuffixKind, + module: Option, } struct FieldContainer { @@ -47,6 +53,7 @@ struct FieldContainer { display_format: Option, doc_comment: Option, visibility: Option, + module: Option, } enum SuffixKind { @@ -529,6 +536,11 @@ const ATTR_VISIBILITY: OnlyValidOn = OnlyValidOn { valid_on: "an enum, enum variants, or a struct with named fields", }; +const ATTR_MODULE: OnlyValidOn = OnlyValidOn { + attribute: "module", + valid_on: "an enum or structs with named fields", +}; + const ATTR_CONTEXT: OnlyValidOn = OnlyValidOn { attribute: "context", valid_on: "enum variants or structs with named fields", @@ -563,6 +575,7 @@ fn parse_snafu_enum( let mut errors = SyntaxErrors::default(); + let mut modules = AtMostOne::new("module", ErrorLocation::OnEnum); let mut default_visibilities = AtMostOne::new("visibility", ErrorLocation::OnEnum); let mut default_suffixes = AtMostOne::new("context(suffix)", ErrorLocation::OnEnum); let mut crate_roots = AtMostOne::new("crate_root", ErrorLocation::OnEnum); @@ -587,6 +600,7 @@ fn parse_snafu_enum( Context::Suffix(s) => default_suffixes.add(s, tokens), Context::Flag(_) => enum_errors.add(tokens, ATTR_CONTEXT_FLAG), }, + Att::Module(tokens, v) => modules.add(v, tokens), Att::Backtrace(tokens, ..) => enum_errors.add(tokens, ATTR_BACKTRACE), Att::Implicit(tokens, ..) => enum_errors.add(tokens, ATTR_IMPLICIT), Att::Whatever(tokens) => enum_errors.add(tokens, ATTR_WHATEVER), @@ -594,8 +608,10 @@ fn parse_snafu_enum( } } - let (maybe_default_visibility, errs) = default_visibilities.finish(); - let default_visibility = maybe_default_visibility.unwrap_or_else(private_visibility); + let (module, errs) = modules.finish(); + errors.extend(errs); + + let (default_visibility, errs) = default_visibilities.finish(); errors.extend(errs); let (maybe_default_suffix, errs) = default_suffixes.finish(); @@ -647,6 +663,7 @@ fn parse_snafu_enum( variants, default_visibility, default_suffix, + module, }) } @@ -664,6 +681,7 @@ fn field_container( let mut outer_errors = errors.scoped(outer_error_location); + let mut modules = AtMostOne::new("module", outer_error_location); let mut display_formats = AtMostOne::new("display", outer_error_location); let mut visibilities = AtMostOne::new("visibility", outer_error_location); let mut contexts = AtMostOne::new("context", outer_error_location); @@ -675,6 +693,7 @@ fn field_container( use SnafuAttribute as Att; match attr { + Att::Module(tokens, n) => modules.add(n, tokens), Att::Display(tokens, d) => display_formats.add(d, tokens), Att::Visibility(tokens, v) => visibilities.add(v, tokens), Att::Context(tokens, c) => contexts.add(c, tokens), @@ -789,6 +808,7 @@ fn field_container( field_errors.add(tokens, ATTR_IMPLICIT_FALSE); } } + Att::Module(tokens, ..) => field_errors.add(tokens, ATTR_MODULE), Att::Visibility(tokens, ..) => field_errors.add(tokens, ATTR_VISIBILITY), Att::Display(tokens, ..) => field_errors.add(tokens, ATTR_DISPLAY), Att::Context(tokens, ..) => field_errors.add(tokens, ATTR_CONTEXT), @@ -873,6 +893,9 @@ fn field_container( _ => {} // no conflict } + let (module, errs) = modules.finish(); + errors.extend(errs); + let (display_format, errs) = display_formats.finish(); errors.extend(errs); @@ -968,6 +991,7 @@ fn field_container( display_format, doc_comment: doc_comment.finish(), visibility, + module, }) } @@ -1057,6 +1081,7 @@ fn parse_snafu_tuple_struct( use SnafuAttribute as Att; match attr { + Att::Module(tokens, ..) => struct_errors.add(tokens, ATTR_MODULE), Att::Display(tokens, ..) => struct_errors.add(tokens, ATTR_DISPLAY), Att::Visibility(tokens, ..) => struct_errors.add(tokens, ATTR_VISIBILITY), Att::Source(tokens, ss) => { @@ -1180,6 +1205,7 @@ enum SnafuAttribute { Display(proc_macro2::TokenStream, Display), DocComment(proc_macro2::TokenStream, String), Implicit(proc_macro2::TokenStream, bool), + Module(proc_macro2::TokenStream, ModuleName), Source(proc_macro2::TokenStream, Vec), Visibility(proc_macro2::TokenStream, UserInput), Whatever(proc_macro2::TokenStream), @@ -1193,6 +1219,12 @@ fn private_visibility() -> UserInput { Box::new(quote! {}) } +// Private context selectors wouldn't be accessible outside the +// module, so we use `pub(super)`. +fn default_context_selector_visibility_in_module() -> proc_macro2::TokenStream { + quote! { pub(super) } +} + impl From for proc_macro::TokenStream { fn from(other: SnafuInfo) -> proc_macro::TokenStream { match other { @@ -1305,8 +1337,24 @@ impl EnumInfo { let error_impl = ErrorImpl(&self); let error_compat_impl = ErrorCompatImpl(&self); + let context = match &self.module { + None => quote! { #context_selectors }, + Some(module_name) => { + use crate::shared::ContextModule; + + let context_module = ContextModule { + container_name: self.name(), + body: &context_selectors, + visibility: Some(&self.default_visibility), + module_name, + }; + + quote! { #context_module } + } + }; + quote! { - #context_selectors + #context #display_impl #error_impl #error_compat_impl @@ -1357,11 +1405,19 @@ impl<'a> quote::ToTokens for ContextSelector<'a> { .. } = self.1; - let visibility = self - .1 - .visibility - .as_ref() - .unwrap_or(&self.0.default_visibility); + let default_visibility; + let selector_visibility = match ( + &self.1.visibility, + &self.0.default_visibility, + &self.0.module, + ) { + (Some(v), _, _) | (_, Some(v), _) => Some(&**v), + (None, None, Some(_)) => { + default_visibility = default_context_selector_visibility_in_module(); + Some(&default_visibility as _) + } + (None, None, None) => None, + }; let selector_doc_string = format!( "SNAFU context selector for the `{}::{}` variant", @@ -1379,7 +1435,7 @@ impl<'a> quote::ToTokens for ContextSelector<'a> { selector_kind: &selector_kind, selector_name: variant_name, user_fields: &selector_kind.user_fields(), - visibility: Some(&visibility), + visibility: selector_visibility, where_clauses: &self.0.provided_where_clauses(), default_suffix, }; @@ -1537,6 +1593,7 @@ impl NamedStructInfo { display_format, doc_comment, visibility, + module, }, .. } = &self; @@ -1609,6 +1666,16 @@ impl NamedStructInfo { let selector_doc_string = format!("SNAFU context selector for the `{}` error", name); + let default_visibility; + let selector_visibility = match (visibility, module) { + (Some(v), _) => Some(&**v), + (None, Some(_)) => { + default_visibility = default_context_selector_visibility_in_module(); + Some(&default_visibility as _) + } + (None, None) => None, + }; + let context_selector = ContextSelector { backtrace_field: backtrace_field.as_ref(), implicit_fields: implicit_fields, @@ -1620,16 +1687,32 @@ impl NamedStructInfo { selector_kind: &selector_kind, selector_name: &field_container.name, user_fields: &user_fields, - visibility: visibility.as_ref().map(|x| &**x), + visibility: selector_visibility, where_clauses: &where_clauses, default_suffix: &SuffixKind::Default, }; + let context = match module { + None => quote! { #context_selector }, + Some(module_name) => { + use crate::shared::ContextModule; + + let context_module = ContextModule { + container_name: self.name(), + body: &context_selector, + visibility: visibility.as_ref().map(|x| &**x), + module_name, + }; + + quote! { #context_module } + } + }; + quote! { #error_impl #error_compat_impl #display_impl - #context_selector + #context } } } diff --git a/snafu-derive/src/parse.rs b/snafu-derive/src/parse.rs index 644efd69..e61c7dcd 100644 --- a/snafu-derive/src/parse.rs +++ b/snafu-derive/src/parse.rs @@ -1,6 +1,6 @@ use std::collections::BTreeSet; -use crate::SnafuAttribute; +use crate::{ModuleName, SnafuAttribute}; use proc_macro2::TokenStream; use quote::{format_ident, ToTokens}; use syn::{ @@ -18,6 +18,7 @@ mod kw { custom_keyword!(crate_root); custom_keyword!(display); custom_keyword!(implicit); + custom_keyword!(module); custom_keyword!(source); custom_keyword!(visibility); custom_keyword!(whatever); @@ -66,6 +67,7 @@ enum Attribute { CrateRoot(CrateRoot), Display(Display), Implicit(Implicit), + Module(Module), Source(Source), Visibility(Visibility), Whatever(Whatever), @@ -81,6 +83,7 @@ impl From for SnafuAttribute { CrateRoot(cr) => SnafuAttribute::CrateRoot(cr.to_token_stream(), cr.into_arbitrary()), Display(d) => SnafuAttribute::Display(d.to_token_stream(), d.into_display()), Implicit(d) => SnafuAttribute::Implicit(d.to_token_stream(), d.into_bool()), + Module(v) => SnafuAttribute::Module(v.to_token_stream(), v.into_value()), Source(s) => SnafuAttribute::Source(s.to_token_stream(), s.into_components()), Visibility(v) => SnafuAttribute::Visibility(v.to_token_stream(), v.into_arbitrary()), Whatever(o) => SnafuAttribute::Whatever(o.to_token_stream()), @@ -101,6 +104,8 @@ impl Parse for Attribute { input.parse().map(Attribute::Display) } else if lookahead.peek(kw::implicit) { input.parse().map(Attribute::Implicit) + } else if lookahead.peek(kw::module) { + input.parse().map(Attribute::Module) } else if lookahead.peek(kw::source) { input.parse().map(Attribute::Source) } else if lookahead.peek(kw::visibility) { @@ -456,6 +461,36 @@ impl ToTokens for Implicit { } } +struct Module { + module_token: kw::module, + arg: MaybeArg, +} + +impl Module { + fn into_value(self) -> ModuleName { + match self.arg.into_option() { + None => ModuleName::Default, + Some(name) => ModuleName::Custom(name), + } + } +} + +impl Parse for Module { + fn parse(input: ParseStream) -> Result { + Ok(Self { + module_token: input.parse()?, + arg: input.parse()?, + }) + } +} + +impl ToTokens for Module { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.module_token.to_tokens(tokens); + self.arg.to_tokens(tokens); + } +} + struct Source { source_token: kw::source, args: MaybeArg>, diff --git a/snafu-derive/src/shared.rs b/snafu-derive/src/shared.rs index 7fa164a2..470b7567 100644 --- a/snafu-derive/src/shared.rs +++ b/snafu-derive/src/shared.rs @@ -1,8 +1,53 @@ +pub(crate) use self::context_module::ContextModule; pub(crate) use self::context_selector::ContextSelector; pub(crate) use self::display::{Display, DisplayMatchArm}; pub(crate) use self::error::{Error, ErrorSourceMatchArm}; pub(crate) use self::error_compat::{ErrorCompat, ErrorCompatBacktraceMatchArm}; +pub mod context_module { + use crate::ModuleName; + use heck::SnakeCase; + use proc_macro2::TokenStream; + use quote::{quote, ToTokens}; + use syn::Ident; + + #[derive(Copy, Clone)] + pub(crate) struct ContextModule<'a, T> { + pub container_name: &'a Ident, + pub module_name: &'a ModuleName, + pub visibility: Option<&'a dyn ToTokens>, + pub body: &'a T, + } + + impl<'a, T> ToTokens for ContextModule<'a, T> + where + T: ToTokens, + { + fn to_tokens(&self, stream: &mut TokenStream) { + let module_name = match self.module_name { + ModuleName::Default => { + let name_str = self.container_name.to_string().to_snake_case(); + syn::Ident::new(&name_str, self.container_name.span()) + } + ModuleName::Custom(name) => name.clone(), + }; + + let visibility = self.visibility; + let body = self.body; + + let module_tokens = quote! { + #visibility mod #module_name { + use super::*; + + #body + } + }; + + stream.extend(module_tokens); + } + } +} + pub mod context_selector { use crate::{ContextSelectorKind, Field, SuffixKind}; use proc_macro2::TokenStream; diff --git a/src/Snafu.md b/src/Snafu.md index 24b73b9c..2f2e2206 100644 --- a/src/Snafu.md +++ b/src/Snafu.md @@ -8,6 +8,7 @@ unique situations. - [`crate_root`](#controlling-how-the-snafu-crate-is-resolved) - [`display`](#controlling-display) - [`implicit`](#controlling-implicitly-generated-data) +- [`module`](#placing-context-selectors-in-modules) - [`source`](#controlling-error-sources) - [`visibility`](#controlling-visibility) - [`whatever`](#controlling-stringly-typed-errors) @@ -184,6 +185,83 @@ guaranteed. Therefore, exporting them in a crate's public API could cause semver breakage for such crates, should SNAFU internals change. +## Placing context selectors in modules + +When you have multiple error enums that would generate conflicting +context selectors, you can choose to place the context selectors into +a module using `snafu(module)`: + +```rust +use snafu::prelude::*; + +#[derive(Debug, Snafu)] +#[snafu(module)] +enum ReadError { + Opening, +} + +fn example() -> Result<(), ReadError> { + read_error::OpeningSnafu.fail() +} + +#[derive(Debug, Snafu)] +enum WriteError { + Opening, // Would conflict if `snafu(module)` was not used above. +} +# // https://github.com/rust-lang/rust/issues/83583 +# fn main() {} +``` + +By default, the module name will be the `snake_case` equivalent of the +enum name. You can override the default by providing an argument to +`#[snafu(module(...))]`: + +```rust +use snafu::prelude::*; + +#[derive(Debug, Snafu)] +#[snafu(module(read))] +enum ReadError { + Opening, +} + +fn example() -> Result<(), ReadError> { + read::OpeningSnafu.fail() +} +# // https://github.com/rust-lang/rust/issues/83583 +# fn main() {} +``` + +As placing the context selectors in a module naturally namespaces +them, you may wish to combine this option with +`#[snafu(context(suffix(false)))]`: + +```rust +use snafu::prelude::*; + +#[derive(Debug, Snafu)] +#[snafu(module, context(suffix(false)))] +enum ReadError { + Opening, +} + +fn example() -> Result<(), ReadError> { + read_error::Opening.fail() +} +# // https://github.com/rust-lang/rust/issues/83583 +# fn main() {} +``` + +The generated module starts with `use super::*`, so any types or +traits used by the context selectors need to be in scope — complicated +paths may need to be simplified or made absolute. + +By default, the visibility of the generated module will be private +while the context selectors inside will be `pub(super)`. Using +[`#[snafu(visibility)]`](#controlling-visibility) to control the +visibility will change the visibility of *both* the module and the +context selectors. + ## Controlling error sources ### Selecting the source field diff --git a/tests/module.rs b/tests/module.rs new file mode 100644 index 00000000..4b4fad82 --- /dev/null +++ b/tests/module.rs @@ -0,0 +1,102 @@ +pub mod inner { + use snafu::Snafu; + + #[derive(Debug)] + pub struct Dummy0; + + #[derive(Debug)] + pub struct Dummy1; + + #[derive(Debug)] + pub struct Dummy2; + + #[derive(Debug)] + pub struct Dummy3; + + #[derive(Debug, Snafu)] + #[snafu(module, visibility(pub))] + pub enum PubError { + Variant { v: Dummy0 }, + } + + #[derive(Debug, Snafu)] + #[snafu(module(custom_pub), visibility(pub))] + pub enum PubWithCustomModError { + Variant { v: Dummy1 }, + } + + #[derive(Debug, Snafu)] + #[snafu(module(custom_pub_crate), visibility(pub(crate)))] + pub(crate) enum PubCrateWithCustomModError { + Variant { v: Dummy2 }, + } + + mod child { + use super::Dummy3; + use snafu::Snafu; + + #[derive(Debug, Snafu)] + #[snafu(module, visibility(pub(in crate::inner)))] + pub enum RestrictedError { + Variant { v: Dummy3 }, + } + } + + #[test] + fn can_set_module_visibility_restricted() { + let _ = self::child::restricted_error::VariantSnafu { v: Dummy3 }.build(); + } +} + +use self::inner::Dummy1; +use snafu::Snafu; + +#[derive(Debug, Snafu)] +#[snafu(module)] +pub enum SomeError { + Variant { v: i32 }, +} + +#[derive(Debug, Snafu)] +#[snafu(module)] +pub enum QualifiedError { + Variant { + unqualified: Dummy1, + mod_struct: inner::Dummy0, + self_struct: self::Dummy1, + crate_struct: crate::Dummy1, + boxed_trait: Box, + }, +} + +#[test] +fn can_use_qualified_names_in_module() { + let _ = qualified_error::VariantSnafu { + unqualified: Dummy1, + mod_struct: inner::Dummy0, + self_struct: self::Dummy1, + crate_struct: crate::Dummy1, + boxed_trait: Box::new(()) as Box<_>, + } + .build(); +} + +#[test] +fn can_set_module() { + let _ = some_error::VariantSnafu { v: 0i32 }.build(); +} + +#[test] +fn can_set_module_visibility_pub() { + let _ = inner::pub_error::VariantSnafu { v: inner::Dummy0 }.build(); +} + +#[test] +fn can_set_module_visibility_pub_with_custom_name() { + let _ = inner::custom_pub::VariantSnafu { v: inner::Dummy1 }.build(); +} + +#[test] +fn can_set_module_visibility_pub_crate_with_custom_name() { + let _ = inner::custom_pub_crate::VariantSnafu { v: inner::Dummy2 }.build(); +} diff --git a/tests/structs/main.rs b/tests/structs/main.rs index 519fce22..8436029f 100644 --- a/tests/structs/main.rs +++ b/tests/structs/main.rs @@ -7,6 +7,7 @@ mod context_selector_name; mod display; mod from_option; mod generics; +mod module; mod no_context; mod single_use_lifetimes; mod source_attributes; diff --git a/tests/structs/module.rs b/tests/structs/module.rs new file mode 100644 index 00000000..30a95c66 --- /dev/null +++ b/tests/structs/module.rs @@ -0,0 +1,79 @@ +pub mod inner { + use snafu::Snafu; + + #[derive(Debug)] + pub struct Dummy0; + + #[derive(Debug)] + pub struct Dummy1; + + #[derive(Debug, Snafu)] + #[snafu(module, visibility(pub))] + pub struct PubError; + + #[derive(Debug, Snafu)] + #[snafu(module(custom_pub), visibility(pub))] + pub struct PubWithCustomModError; + + #[derive(Debug, Snafu)] + #[snafu(module(custom_pub_crate), visibility(pub(crate)))] + pub(crate) struct PubCrateWithCustomModError; + + #[derive(Debug, Snafu)] + #[snafu(module, visibility(pub(in crate::module)))] + pub struct RestrictedError; +} + +use self::inner::Dummy1; +use snafu::Snafu; + +#[derive(Debug, Snafu)] +#[snafu(module)] +pub struct SomeError; + +#[derive(Debug, Snafu)] +#[snafu(module)] +pub struct QualifiedError { + unqualified: Dummy1, + mod_struct: inner::Dummy0, + self_struct: self::Dummy1, + crate_struct: crate::module::Dummy1, + boxed_trait: Box, +} + +#[test] +fn can_use_qualified_names_in_module() { + let _ = qualified_error::QualifiedSnafu { + unqualified: Dummy1, + mod_struct: inner::Dummy0, + self_struct: self::Dummy1, + crate_struct: crate::module::Dummy1, + boxed_trait: Box::new(()) as Box<_>, + } + .build(); +} + +#[test] +fn can_set_module() { + let _ = some_error::SomeSnafu.build(); +} + +#[test] +fn can_set_module_visibility_pub() { + let _ = inner::pub_error::PubSnafu.build(); +} + +#[test] +fn can_set_module_visibility_restricted() { + let _ = inner::restricted_error::RestrictedSnafu.build(); +} + +#[test] +fn can_set_module_visibility_pub_with_custom_name() { + let _ = inner::custom_pub::PubWithCustomModSnafu.build(); +} + +#[test] +fn can_set_module_visibility_pub_crate_with_custom_name() { + let _ = inner::custom_pub_crate::PubCrateWithCustomModSnafu.build(); +}