Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement #[snafu(module)] support #295

Merged
merged 1 commit into from Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions 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() {}
12 changes: 12 additions & 0 deletions 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)
@@ -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)]
Expand Down
1 change: 1 addition & 0 deletions snafu-derive/Cargo.toml
Expand Up @@ -21,3 +21,4 @@ proc-macro = true
syn = { version = "1.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
heck = "0.3.3"
shepmaster marked this conversation as resolved.
Show resolved Hide resolved
107 changes: 95 additions & 12 deletions snafu-derive/src/lib.rs
Expand Up @@ -24,6 +24,11 @@ type MultiSynResult<T> = std::result::Result<T, Vec<syn::Error>>;
/// Some arbitrary tokens we treat as a black box
type UserInput = Box<dyn quote::ToTokens>;

enum ModuleName {
Default,
Custom(syn::Ident),
}

enum SnafuInfo {
Enum(EnumInfo),
NamedStruct(NamedStructInfo),
Expand All @@ -35,8 +40,9 @@ struct EnumInfo {
name: syn::Ident,
generics: syn::Generics,
variants: Vec<FieldContainer>,
default_visibility: UserInput,
default_visibility: Option<UserInput>,
default_suffix: SuffixKind,
module: Option<ModuleName>,
}

struct FieldContainer {
Expand All @@ -47,6 +53,7 @@ struct FieldContainer {
display_format: Option<Display>,
doc_comment: Option<DocComment>,
visibility: Option<UserInput>,
module: Option<ModuleName>,
}

enum SuffixKind {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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);
Expand All @@ -587,15 +600,18 @@ 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),
Att::DocComment(..) => { /* Just a regular doc comment. */ }
}
}

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();
Expand Down Expand Up @@ -647,6 +663,7 @@ fn parse_snafu_enum(
variants,
default_visibility,
default_suffix,
module,
})
}

Expand All @@ -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);
Expand All @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -968,6 +991,7 @@ fn field_container(
display_format,
doc_comment: doc_comment.finish(),
visibility,
module,
})
}

Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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<Source>),
Visibility(proc_macro2::TokenStream, UserInput),
Whatever(proc_macro2::TokenStream),
Expand All @@ -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<SnafuInfo> for proc_macro::TokenStream {
fn from(other: SnafuInfo) -> proc_macro::TokenStream {
match other {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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,
};
Expand Down Expand Up @@ -1537,6 +1593,7 @@ impl NamedStructInfo {
display_format,
doc_comment,
visibility,
module,
},
..
} = &self;
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
}
}
Expand Down
37 changes: 36 additions & 1 deletion 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::{
Expand All @@ -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);
Expand Down Expand Up @@ -66,6 +67,7 @@ enum Attribute {
CrateRoot(CrateRoot),
Display(Display),
Implicit(Implicit),
Module(Module),
Source(Source),
Visibility(Visibility),
Whatever(Whatever),
Expand All @@ -81,6 +83,7 @@ impl From<Attribute> 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()),
Expand All @@ -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) {
Expand Down Expand Up @@ -456,6 +461,36 @@ impl ToTokens for Implicit {
}
}

struct Module {
module_token: kw::module,
arg: MaybeArg<Ident>,
}

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<Self> {
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<Punctuated<SourceArg, token::Comma>>,
Expand Down