Skip to content

Commit

Permalink
Merge pull request #295 from SamWilsn/derive-with-module
Browse files Browse the repository at this point in the history
  • Loading branch information
shepmaster committed Nov 15, 2021
2 parents a1981ac + 0978fa1 commit e846c56
Show file tree
Hide file tree
Showing 11 changed files with 469 additions and 14 deletions.
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"
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

0 comments on commit e846c56

Please sign in to comment.