Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use snafu::prelude::*;

#[derive(Debug, Snafu)]
enum InnerError {
InnerVariant,
}
struct InnerError;

#[derive(Debug, Snafu)]
enum Error {
Expand All @@ -14,4 +12,10 @@ enum Error {
},
}

#[derive(Debug, Snafu)]
struct SourceFromTransformInvalidType {
#[snafu(source(from(Cow*, ?)))]
source: InnerError,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
error: expected boolean literal or `from`
--> tests/ui/attribute-unparseable.rs:12:24
--> tests/ui/attribute-unparseable.rs:10:24
|
12 | #[snafu(source(5))]
10 | #[snafu(source(5))]
| ^

error: expected one of: `exact`, `generic` or a type followed by a comma and an expression
--> tests/ui/attribute-unparseable.rs:17:25
|
17 | #[snafu(source(from(Cow*, ?)))]
| ^^^

error: expected `,`
--> tests/ui/attribute-unparseable.rs:17:28
|
17 | #[snafu(source(from(Cow*, ?)))]
| ^
20 changes: 20 additions & 0 deletions compatibility-tests/compile-fail/tests/ui/from-generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use snafu::prelude::*;

#[derive(Debug, Snafu)]
struct DummyError;

#[derive(Debug, Snafu)]
enum EnumErrorWithGenericSourceAndContextSelector {
TheVariant {
#[snafu(source(from(generic)))]
source: DummyError,
},
}

#[derive(Debug, Snafu)]
struct StructErrorWithGenericSourceAndContextSelector {
#[snafu(source(from(generic)))]
source: DummyError,
}

fn main() {}
11 changes: 11 additions & 0 deletions compatibility-tests/compile-fail/tests/ui/from-generic.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error: Cannot use `source(from(generic))` without disabling the context selector with `context(false)` or `transparent`
--> tests/ui/from-generic.rs:9:17
|
9 | #[snafu(source(from(generic)))]
| ^^^^^^^^^^^^^^^^^^^^^

error: Cannot use `source(from(generic))` without disabling the context selector with `context(false)` or `transparent`
--> tests/ui/from-generic.rs:16:13
|
16 | #[snafu(source(from(generic)))]
| ^^^^^^^^^^^^^^^^^^^^^
25 changes: 20 additions & 5 deletions snafu-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,10 @@ impl SourceField {

enum Transformation {
None {
ty: syn::Type,
target_ty: syn::Type,
from_is_generic: bool,
},

Transform {
source_ty: syn::Type,
target_ty: syn::Type,
Expand All @@ -250,24 +252,37 @@ enum Transformation {
impl Transformation {
fn source_ty(&self) -> &syn::Type {
match self {
Transformation::None { ty } => ty,
Transformation::None { target_ty, .. } => target_ty,
Transformation::Transform { source_ty, .. } => source_ty,
}
}

fn target_ty(&self) -> &syn::Type {
match self {
Transformation::None { ty } => ty,
Transformation::None { target_ty, .. } => target_ty,
Transformation::Transform { target_ty, .. } => target_ty,
}
}

fn transformation(&self) -> proc_macro2::TokenStream {
match self {
Transformation::None { .. } => quote! { |v| v },
Transformation::None {
from_is_generic: false,
..
} => quote! { |v| v },

Transformation::None {
from_is_generic: true,
..
} => quote! { ::core::convert::Into::into },

Transformation::Transform { expr, .. } => quote! { #expr },
}
}

fn is_generic(&self) -> bool {
matches!(self, Transformation::None { from_is_generic, .. } if *from_is_generic)
}
}

fn impl_snafu_macro(ty: syn::DeriveInput) -> TokenStream {
Expand Down Expand Up @@ -831,8 +846,8 @@ impl TupleStructInfo {
crate_root,
generics,
name,
transformation,
provides,
transformation,
} = self;

let where_clauses: Vec<_> = generics
Expand Down
136 changes: 108 additions & 28 deletions snafu-derive/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ mod kw {
custom_keyword!(whatever);

custom_keyword!(from);
custom_keyword!(exact);
custom_keyword!(generic);

custom_keyword!(name);
custom_keyword!(suffix);
Expand Down Expand Up @@ -1063,15 +1065,30 @@ struct SourceFrom {

fn into_transformation(source_from: Option<SourceFrom>, target_ty: Type) -> Transformation {
match source_from {
Some(SourceFrom { value, .. }) => {
let SourceFromArg { r#type, expr, .. } = value;
Transformation::Transform {
source_ty: r#type,
Some(SourceFrom { value, .. }) => match value.value {
SourceFromValue::Exact(_) => Transformation::None {
target_ty,
expr,
from_is_generic: false,
},

SourceFromValue::Generic(_) => Transformation::None {
target_ty,
from_is_generic: true,
},

SourceFromValue::Transform(SourceFromTransform { r#type, expr, .. }) => {
Transformation::Transform {
source_ty: r#type,
target_ty,
expr,
}
}
}
None => Transformation::None { ty: target_ty },
},

None => Transformation::None {
target_ty,
from_is_generic: false,
},
}
}

Expand Down Expand Up @@ -1134,24 +1151,41 @@ impl Parse for NestedSource {
}
}

enum SourceArg {
Flag { value: LitBool },
From(SourceFromArg),
}

impl Parse for SourceArg {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(LitBool) {
Ok(SourceArg::Flag {
value: input.parse()?,
})
} else if lookahead.peek(kw::from) {
input.parse().map(SourceArg::From)
} else {
Err(lookahead.error())
}
}
}

#[derive(Clone)]
struct SourceFromArg {
from_token: kw::from,
paren_token: token::Paren,
r#type: Type,
comma_token: token::Comma,
expr: Expr,
value: SourceFromValue,
}

impl Parse for SourceFromArg {
fn parse(input: ParseStream) -> Result<Self> {
let content;

Ok(SourceFromArg {
from_token: input.parse()?,
paren_token: parenthesized!(content in input),
r#type: content.parse()?,
comma_token: content.parse()?,
expr: content.parse()?,
value: content.parse()?,
})
}
}
Expand All @@ -1160,33 +1194,79 @@ impl ToTokens for SourceFromArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.from_token.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.r#type.to_tokens(tokens);
self.comma_token.to_tokens(tokens);
self.expr.to_tokens(tokens);
self.value.to_tokens(tokens);
});
}
}

enum SourceArg {
Flag { value: LitBool },
From(SourceFromArg),
#[derive(Clone)]
enum SourceFromValue {
Exact(kw::exact),

Generic(kw::generic),

Transform(SourceFromTransform),
}

impl Parse for SourceArg {
impl Parse for SourceFromValue {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(LitBool) {
Ok(SourceArg::Flag {
value: input.parse()?,
})
} else if lookahead.peek(kw::from) {
input.parse().map(SourceArg::From)
if input.peek(kw::exact) {
input.parse().map(Self::Exact)
} else if input.peek(kw::generic) {
input.parse().map(Self::Generic)
} else {
Err(lookahead.error())
// We can't peek ahead for a type. If we fail, add our own
// error that mimics the lookahead error to tell the user
// that `exact` / `generic` are also possible here.
//
// FUTURE: Consider making transforms be keyword-prefixed (with a semver
// break?) e.g. `transform Type with Expr`
let span = input.span();
let txt = "expected one of: `exact`, `generic` or a type followed by a comma and an expression";
input.parse().map(Self::Transform).map_err(|e| {
let mut e1 = syn::Error::new(span, txt);
e1.combine(e);
e1
})
}
}
}

impl ToTokens for SourceFromValue {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
SourceFromValue::Exact(exact) => exact.to_tokens(tokens),
SourceFromValue::Generic(generic) => generic.to_tokens(tokens),
SourceFromValue::Transform(transform) => transform.to_tokens(tokens),
}
}
}

#[derive(Clone)]
struct SourceFromTransform {
r#type: Type,
comma_token: token::Comma,
expr: Expr,
}

impl Parse for SourceFromTransform {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
r#type: input.parse()?,
comma_token: input.parse()?,
expr: input.parse()?,
})
}
}

impl ToTokens for SourceFromTransform {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.r#type.to_tokens(tokens);
self.comma_token.to_tokens(tokens);
self.expr.to_tokens(tokens);
}
}

struct Transparent {
transparent_token: kw::transparent,
arg: MaybeArg<LitBool>,
Expand Down
11 changes: 11 additions & 0 deletions snafu-derive/src/parse/field_container_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ pub(super) fn parse_field_container(
_ => {} // no conflict
}

if let Some(Sidecar(span, source_field)) = &source {
if source_field.transformation.is_generic() && !selector_kind.is_without_context() {
let txt = "Cannot use `source(from(generic))` without disabling the context selector with `context(false)` or `transparent`";
errors.push_new(span, txt);
}
}

let source_field = source.map(|Sidecar(_, val)| val);
let backtrace_field = backtrace.map(|Sidecar(_, val)| val);
let is_transparent = selector_kind.is_transparent();
Expand Down Expand Up @@ -325,6 +332,10 @@ impl IntermediateSelectorKind {
}
)
}

fn is_without_context(&self) -> bool {
matches!(self, IntermediateSelectorKind::WithoutContext { .. })
}
}

enum WithoutContextSource {
Expand Down
Loading