Repository: shepmaster/snafu (rust)

Issue:
From implementations for converting from source error types
I'm evaluating `snafu` as a replacement for `quick-error` and came across a couple of ergonomic issues that I thought would be worth mentioning. They are both related to implementing `From` such that the error variant is determined by the source error type.

## Without context

First, let's consider the case where you want a variant that acts as a wrapper for a source error type with no additional context. For example, let's say you want an error type

```rust
pub enum MyError {
    Foo(FooError),
    Bar(BarError),
}
```

This represents an error that is created from either a source error of type `FooError` or a source error of type `BarError` with no additional context information. `quick-error` handles this nicer than `snafu` in two ways:

1. It allows using a tuple variant (`Foo(FooError)`) instead of requiring a struct variant (`Foo { source: FooError }`).

2. It makes it easy to create `impl From<FooError> for MyError` and `impl From<BarError> for MyError`. (`quick-error` makes this easy by having the user write `from()` to specify that they want a `From` implementation to be created.)

   This makes it possible to write

   ```rust
   // using quick-error
   fn my_fn() -> Result<(), MyError> {
       returns_result_with_foo_error()?;
       Ok(())
   }
   ```

   instead of having to explicitly specify the variant with

   ```rust
   // using snafu
   fn my_fn() -> Result<(), MyError> {
       returns_result_with_foo_error().context(Foo)?;
       Ok(())
   }
   ```

Item 1 isn't critical, but it would be nice to have. (It looks like discussion about this is in #61, so I won't talk about it more here.)

Item 2 is pretty important so that `?` works without having to call `.context()`. I suppose you could argue that the user should always call `.context()` to explicitly specify the error variant, but when the variant can be determined directly from the source error type, IMO calling `.context()` is unnecessarily verbose.

## With context

A related issue is when the error variant does have context information but the error variant can still be determined from the source error type. For example,

```rust
pub enum MyError {
    Foo {
        message: String,
        source: FooError,
    },
    Bar {
        message: String,
        source: BarError,
    },
}
```

Similar to the case without context, it would be nice to be able to use the `?` operator without explicitly specifying the error variant. So, it would be nice to be able to write

```rust
// using quick-error
fn my_fn() -> Result<(), MyError> {
    returns_result_with_foo_error().context("message")?;
    Ok(())
}
```

instead of

```rust
// using snafu
fn my_fn() -> Result<(), MyError> {
    returns_result_with_foo_error().context(Foo { message: "message"})?;
    Ok(())
}
```

To do this, we need an easy way to create `impl<C: Into<String>> From<Context<FooError, C>> for MyError` (or just `impl<'a> From<Context<FooError, &'a str>> for MyError`).

`quick-error` makes this easy by allowing the user to write `context(message: &'a str, source: FooError) -> (message.to_string(), source)` to specify that they want a `From<Context<&'a str, FooError>>` implementation to be created [note: `quick-error` swaps the type parameters of `Context`].

Edit: By the way, I wanted to mention that your documentation is awesome!

Pull request:
Add support for contextless errors
Closes #78

Patch:
diff --git a/snafu-derive/src/lib.rs b/snafu-derive/src/lib.rs
index c76b17b..2ae8ff6 100644
--- a/snafu-derive/src/lib.rs
+++ b/snafu-derive/src/lib.rs
@@ -38,14 +38,44 @@ struct EnumInfo {
 
 struct VariantInfo {
     name: syn::Ident,
-    source_field: Option<SourceField>,
     backtrace_field: Option<Field>,
-    user_fields: Vec<Field>,
+    selector_kind: ContextSelectorKind,
     display_format: Option<UserInput>,
     doc_comment: String,
     visibility: Option<UserInput>,
 }
 
+enum ContextSelectorKind {
+    Context {
+        source_field: Option<SourceField>,
+        user_fields: Vec<Field>,
+    },
+
+    NoContext {
+        source_field: SourceField,
+    },
+}
+
+impl ContextSelectorKind {
+    fn user_fields(&self) -> &[Field] {
+        match *self {
+            ContextSelectorKind::Context {
+                ref user_fields, ..
+            } => user_fields,
+            ContextSelectorKind::NoContext { .. } => &[],
+        }
+    }
+
+    fn source_field(&self) -> Option<&SourceField> {
+        match *self {
+            ContextSelectorKind::Context {
+                ref source_field, ..
+            } => source_field.as_ref(),
+            ContextSelectorKind::NoContext { ref source_field } => Some(source_field),
+        }
+    }
+}
+
 struct StructInfo {
     name: syn::Ident,
     generics: syn::Generics,
@@ -56,6 +86,7 @@ struct StructInfo {
 struct Field {
     name: syn::Ident,
     ty: syn::Type,
+    original: syn::Field,
 }
 
 impl Field {
@@ -119,7 +150,7 @@ impl SyntaxErrors {
     }
 
     /// Adds the given list of errors.
-    fn extend(&mut self, errors: Vec<syn::Error>) {
+    fn extend(&mut self, errors: impl IntoIterator<Item = syn::Error>) {
         self.inner.extend(errors);
     }
 
@@ -138,6 +169,18 @@ impl SyntaxErrors {
             Err(self.inner)
         }
     }
+
+    /// Consume the SyntaxErrors and a Result, returning the success
+    /// value if neither have errors, otherwise combining the errors.
+    fn absorb<T>(mut self, res: MultiSynResult<T>) -> MultiSynResult<T> {
+        match res {
+            Ok(v) => self.finish().map(|()| v),
+            Err(e) => {
+                self.inner.extend(e);
+                Err(self.inner)
+            }
+        }
+    }
 }
 
 /// Helper structure to simplify parameters to SyntaxErrors.add, handling cases where an attribute
@@ -395,6 +438,17 @@ fn parse_snafu_enum(
                     },
                 );
             }
+            SnafuAttribute::Context(tokens, ..) => {
+                errors.add(
+                    tokens,
+                    OnlyValidOn {
+                        // TODO: enums for these
+                        attribute: "context",
+                        valid_on: "an error variant",
+                        not_on: "an enum",
+                    },
+                );
+            }
             SnafuAttribute::DocComment(..) => { /* Just a regular doc comment. */ }
         }
     }
@@ -408,9 +462,11 @@ fn parse_snafu_enum(
         .into_iter()
         .map(|variant| {
             let name = variant.ident;
+            let variant_span = name.span();
 
             let mut display_formats = AtMostOne::new("display", "on an error variant");
             let mut visibilities = AtMostOne::new("visibility", "on an error variant");
+            let mut contexts = AtMostOne::new("context", "on an error variant");
             let mut doc_comment = String::new();
             let mut reached_end_of_doc_comment = false;
 
@@ -418,6 +474,7 @@ fn parse_snafu_enum(
                 match attr {
                     SnafuAttribute::Display(tokens, d) => display_formats.add(d, tokens),
                     SnafuAttribute::Visibility(tokens, v) => visibilities.add(v, tokens),
+                    SnafuAttribute::Context(tokens, c) => contexts.add(c, tokens),
                     SnafuAttribute::Source(tokens, ..) => {
                         errors.add(
                             tokens,
@@ -473,6 +530,7 @@ fn parse_snafu_enum(
             let mut backtrace_fields = AtMostOne::new("backtrace", "within an error variant");
 
             for syn_field in fields {
+                let original = syn_field.clone();
                 let span = syn_field.span();
                 let name = syn_field
                     .ident
@@ -481,6 +539,7 @@ fn parse_snafu_enum(
                 let field = Field {
                     name: name.clone(),
                     ty: syn_field.ty.clone(),
+                    original,
                 };
 
                 // Check whether we have multiple source/backtrace attributes on this field.
@@ -587,6 +646,16 @@ fn parse_snafu_enum(
                                 },
                             );
                         }
+                        SnafuAttribute::Context(tokens, ..) => {
+                            errors.add(
+                                tokens,
+                                OnlyValidOn {
+                                    attribute: "context",
+                                    valid_on: "variants of an error enum",
+                                    not_on: "a field",
+                                },
+                            );
+                        }
                         SnafuAttribute::DocComment(..) => { /* Just a regular doc comment. */ }
                     }
                 }
@@ -614,7 +683,7 @@ fn parse_snafu_enum(
                 });
 
                 if let Some((maybe_transformation, location)) = source_attr {
-                    let Field { name, ty } = field;
+                    let Field { name, ty, .. } = field;
                     let transformation = maybe_transformation
                         .map(|(ty, expr)| Transformation::Transform { ty, expr })
                         .unwrap_or_else(|| Transformation::None { ty });
@@ -664,11 +733,40 @@ fn parse_snafu_enum(
             let (visibility, errs) = visibilities.finish();
             errors.extend(errs);
 
+            let (is_context, errs) = contexts.finish();
+            errors.extend(errs);
+
+            let source_field = source.map(|(val, _tts)| val);
+
+            let selector_kind = if is_context.unwrap_or(true) {
+                ContextSelectorKind::Context {
+                    source_field,
+                    user_fields,
+                }
+            } else {
+                errors.extend(
+                    user_fields.into_iter().map(|Field { original, .. }| {
+                        syn::Error::new_spanned(
+                            original,
+                            "Context selectors without context must not have context fields",
+                        )
+                    })
+                );
+
+                let source_field = source_field.ok_or_else(|| {
+                        vec![syn::Error::new(
+                            variant_span,
+                            "Context selectors without context must have a source field",
+                        )]
+                    })?;
+
+                ContextSelectorKind::NoContext { source_field }
+            };
+
             Ok(VariantInfo {
                 name,
-                source_field: source.map(|(val, _tts)| val),
                 backtrace_field: backtrace.map(|(val, _tts)| val),
-                user_fields,
+                selector_kind,
                 display_format,
                 doc_comment,
                 visibility,
@@ -676,9 +774,7 @@ fn parse_snafu_enum(
         })
         .collect();
 
-    errors.finish()?;
-
-    let variants = variants.into_result()?;
+    let variants = errors.absorb(variants.into_result())?;
 
     Ok(EnumInfo {
         name,
@@ -750,6 +846,16 @@ fn parse_snafu_struct(
                     },
                 );
             }
+            SnafuAttribute::Context(tokens, ..) => {
+                errors.add(
+                    tokens,
+                    OnlyValidOn {
+                        attribute: "context",
+                        valid_on: "variants of an error enum",
+                        not_on: "a struct",
+                    },
+                );
+            }
             SnafuAttribute::DocComment(..) => { /* Just a regular doc comment. */ }
         }
     }
@@ -961,13 +1067,14 @@ enum SnafuAttribute {
     Visibility(proc_macro2::TokenStream, UserInput),
     Source(proc_macro2::TokenStream, Vec<Source>),
     Backtrace(proc_macro2::TokenStream, bool),
+    Context(proc_macro2::TokenStream, bool),
     DocComment(proc_macro2::TokenStream, String),
 }
 
 impl syn::parse::Parse for SnafuAttribute {
     fn parse(input: syn::parse::ParseStream) -> SynResult<Self> {
         use syn::token::{Comma, Paren};
-        use syn::{Expr, Ident, Visibility};
+        use syn::{Expr, Ident, LitBool, Visibility};
 
         let input_tts = input.cursor().token_stream();
         let name: Ident = input.parse()?;
@@ -1005,10 +1112,17 @@ impl syn::parse::Parse for SnafuAttribute {
             } else {
                 Err(lookahead.error())
             }
+        } else if name == "context" {
+            if input.is_empty() {
+                Ok(SnafuAttribute::Context(input_tts, true))
+            } else {
+                let v: MyParens<LitBool> = input.parse()?;
+                Ok(SnafuAttribute::Context(input_tts, v.0.value))
+            }
         } else {
             Err(syn::Error::new(
                 name.span(),
-                "expected `display`, `visibility`, `source`, or `backtrace`",
+                "expected `display`, `visibility`, `source`, `backtrace`, or `context`",
             ))
         }
     }
@@ -1247,54 +1361,11 @@ impl<'a> quote::ToTokens for ContextSelector<'a> {
 
         let VariantInfo {
             name: ref variant_name,
-            ref source_field,
+            ref selector_kind,
             ref backtrace_field,
-            ref user_fields,
             ..
         } = *self.1;
 
-        let generic_names: Vec<_> = (0..user_fields.len())
-            .map(|i| format_ident!("__T{}", i))
-            .collect();
-
-        let visibility = self
-            .1
-            .visibility
-            .as_ref()
-            .unwrap_or(&self.0.default_visibility);
-
-        let generics_list = quote! { <#(#original_lifetimes,)* #(#generic_names,)* #(#original_generic_types_without_defaults,)*> };
-        let selector_name = quote! { #variant_name<#(#generic_names,)*> };
-
-        let names: Vec<_> = user_fields.iter().map(|f| f.name.clone()).collect();
-        let selector_doc = format!(
-            "SNAFU context selector for the `{}::{}` error variant",
-            enum_name, variant_name,
-        );
-
-        let variant_selector_struct = {
-            if user_fields.is_empty() {
-                quote! {
-                    #[derive(Debug, Copy, Clone)]
-                    #[doc = #selector_doc]
-                    #visibility struct #selector_name;
-                }
-            } else {
-                let visibilities = iter::repeat(visibility);
-
-                quote! {
-                    #[derive(Debug, Copy, Clone)]
-                    #[doc = #selector_doc]
-                    #visibility struct #selector_name {
-                        #(
-                            #[allow(missing_docs)]
-                            #visibilities #names: #generic_names
-                        ),*
-                    }
-                }
-            }
-        };
-
         let backtrace_field = match *backtrace_field {
             Some(ref field) => {
                 let name = &field.name;
@@ -1303,93 +1374,177 @@ impl<'a> quote::ToTokens for ContextSelector<'a> {
             None => quote! {},
         };
 
-        let where_clauses: Vec<_> = generic_names
-            .iter()
-            .zip(user_fields)
-            .map(|(gen_ty, f)| {
-                let Field { ref ty, .. } = *f;
-                quote! { #gen_ty: core::convert::Into<#ty> }
-            })
-            .chain(self.0.provided_where_clauses())
-            .collect();
+        match *selector_kind {
+            ContextSelectorKind::Context {
+                ref user_fields,
+                ref source_field,
+            } => {
+                let generic_names: Vec<_> = (0..user_fields.len())
+                    .map(|i| format_ident!("__T{}", i))
+                    .collect();
+
+                let visibility = self
+                    .1
+                    .visibility
+                    .as_ref()
+                    .unwrap_or(&self.0.default_visibility);
 
-        let inherent_impl = if source_field.is_none() {
-            quote! {
-                impl<#(#generic_names,)*> #selector_name
-                {
-                    #[doc = "Consume the selector and return a `Result` with the associated error"]
-                    #visibility fn fail<#(#original_generics_without_defaults,)* __T>(self) -> core::result::Result<__T, #parameterized_enum_name>
-                    where
-                        #(#where_clauses),*
+                let generics_list = quote! { <#(#original_lifetimes,)* #(#generic_names,)* #(#original_generic_types_without_defaults,)*> };
+                let selector_name = quote! { #variant_name<#(#generic_names,)*> };
+
+                let names: Vec<_> = user_fields.iter().map(|f| f.name.clone()).collect();
+                let selector_doc = format!(
+                    "SNAFU context selector for the `{}::{}` error variant",
+                    enum_name, variant_name,
+                );
+
+                let variant_selector_struct = {
+                    if user_fields.is_empty() {
+                        quote! {
+                            #[derive(Debug, Copy, Clone)]
+                            #[doc = #selector_doc]
+                            #visibility struct #selector_name;
+                        }
+                    } else {
+                        let visibilities = iter::repeat(visibility);
+
+                        quote! {
+                            #[derive(Debug, Copy, Clone)]
+                            #[doc = #selector_doc]
+                            #visibility struct #selector_name {
+                                #(
+                                    #[allow(missing_docs)]
+                                    #visibilities #names: #generic_names
+                                ),*
+                            }
+                        }
+                    }
+                };
+
+                let where_clauses: Vec<_> = generic_names
+                    .iter()
+                    .zip(user_fields)
+                    .map(|(gen_ty, f)| {
+                        let Field { ref ty, .. } = *f;
+                        quote! { #gen_ty: core::convert::Into<#ty> }
+                    })
+                    .chain(self.0.provided_where_clauses())
+                    .collect();
+
+                let inherent_impl = if source_field.is_none() {
+                    quote! {
+                    impl<#(#generic_names,)*> #selector_name
                     {
-                        let Self { #(#names),* } = self;
-                        let error = #enum_name::#variant_name {
-                            #backtrace_field
-                            #( #names: core::convert::Into::into(#names) ),*
-                        };
-                        core::result::Result::Err(error)
+                        #[doc = "Consume the selector and return a `Result` with the associated error"]
+                        #visibility fn fail<#(#original_generics_without_defaults,)* __T>(self) -> core::result::Result<__T, #parameterized_enum_name>
+                        where
+                            #(#where_clauses),*
+                        {
+                            let Self { #(#names),* } = self;
+                            let error = #enum_name::#variant_name {
+                                #backtrace_field
+                                #( #names: core::convert::Into::into(#names) ),*
+                            };
+                            core::result::Result::Err(error)
+                                }
+                            }
+                        }
+                } else {
+                    quote! {}
+                };
+
+                let enum_from_variant_selector_impl = {
+                    let user_fields = user_fields.iter().map(|f| {
+                        let Field { ref name, .. } = *f;
+                        quote! { #name: self.#name.into() }
+                    });
+
+                    let source_ty;
+                    let source_xfer_field;
+
+                    match *source_field {
+                        Some(ref source_field) => {
+                            let SourceField {
+                                name: ref source_name,
+                                transformation: ref source_transformation,
+                                ..
+                            } = *source_field;
+
+                            let source_ty2 = source_transformation.ty();
+                            let source_transformation = source_transformation.transformation();
+
+                            source_ty = quote! { #source_ty2 };
+                            source_xfer_field =
+                                quote! { #source_name: (#source_transformation)(error), };
+                        }
+                        None => {
+                            source_ty = quote! { snafu::NoneError };
+                            source_xfer_field = quote! {};
+                        }
                     }
-                }
-            }
-        } else {
-            quote! {}
-        };
 
-        let enum_from_variant_selector_impl = {
-            let user_fields = user_fields.iter().map(|f| {
-                let Field { ref name, .. } = *f;
-                quote! { #name: self.#name.into() }
-            });
+                    quote! {
+                        impl#generics_list snafu::IntoError<#parameterized_enum_name> for #selector_name
+                        where
+                            #parameterized_enum_name: snafu::Error + snafu::ErrorCompat,
+                            #(#where_clauses),*
+                        {
+                            type Source = #source_ty;
+
+                            fn into_error(self, error: Self::Source) -> #parameterized_enum_name {
+                                #enum_name::#variant_name {
+                                    #source_xfer_field
+                                    #backtrace_field
+                                    #(#user_fields),*
+                                }
+                            }
+                        }
+                    }
+                };
 
-            let source_ty;
-            let source_xfer_field;
+                stream.extend({
+                    quote! {
+                        #variant_selector_struct
+                        #inherent_impl
+                        #enum_from_variant_selector_impl
+                    }
+                })
+            }
 
-            match *source_field {
-                Some(ref source_field) => {
-                    let SourceField {
-                        name: ref source_name,
-                        transformation: ref source_transformation,
-                        ..
-                    } = *source_field;
+            ContextSelectorKind::NoContext { ref source_field } => {
+                let original_generics = self.0.provided_generics_without_defaults();
+                let generics_list = quote! { <#(#original_generics,)*> };
+                let wheres = self.0.provided_where_clauses();
 
-                    let source_ty2 = source_transformation.ty();
-                    let source_transformation = source_transformation.transformation();
+                let SourceField {
+                    name: ref source_name,
+                    transformation: ref source_transformation,
+                    ..
+                } = *source_field;
 
-                    source_ty = quote! { #source_ty2 };
-                    source_xfer_field = quote! { #source_name: (#source_transformation)(error), };
-                }
-                None => {
-                    source_ty = quote! { snafu::NoneError };
-                    source_xfer_field = quote! {};
-                }
-            }
+                let source_ty = source_transformation.ty();
+                let source_transformation = source_transformation.transformation();
 
-            quote! {
-                impl#generics_list snafu::IntoError<#parameterized_enum_name> for #selector_name
-                where
-                    #parameterized_enum_name: snafu::Error + snafu::ErrorCompat,
-                    #(#where_clauses),*
-                {
-                    type Source = #source_ty;
+                let source_ty = quote! { #source_ty };
+                let source_xfer_field = quote! { #source_name: (#source_transformation)(other), };
 
-                    fn into_error(self, error: Self::Source) -> #parameterized_enum_name {
-                        #enum_name::#variant_name {
-                            #source_xfer_field
-                            #backtrace_field
-                            #(#user_fields),*
+                stream.extend({
+                    quote! {
+                        impl#generics_list From<#source_ty> for #parameterized_enum_name
+                        where
+                            #(#wheres),*
+                        {
+                            fn from(other: #source_ty) -> Self {
+                                #enum_name::#variant_name {
+                                    #source_xfer_field
+                                    #backtrace_field
+                                }
+                            }
                         }
                     }
-                }
-            }
-        };
-
-        stream.extend({
-            quote! {
-                #variant_selector_struct
-                #inherent_impl
-                #enum_from_variant_selector_impl
+                })
             }
-        })
+        }
     }
 }
 
@@ -1405,14 +1560,16 @@ impl<'a> DisplayImpl<'a> {
             .map(|variant| {
                 let VariantInfo {
                     name: ref variant_name,
-                    ref user_fields,
-                    ref source_field,
+                    ref selector_kind,
                     ref backtrace_field,
                     ref display_format,
                     ref doc_comment,
                     ..
                 } = *variant;
 
+                let user_fields = selector_kind.user_fields();
+                let source_field = selector_kind.source_field();
+
                 let format = match (display_format, source_field) {
                     (Some(ref v), _) => quote! { #v },
                     (None, _) if !doc_comment.is_empty() => {
@@ -1429,7 +1586,7 @@ impl<'a> DisplayImpl<'a> {
                     .iter()
                     .chain(backtrace_field)
                     .map(Field::name)
-                    .chain(source_field.as_ref().map(SourceField::name));
+                    .chain(source_field.map(SourceField::name));
 
                 let field_names = quote! { #(ref #field_names),* };
 
@@ -1497,12 +1654,14 @@ impl<'a> ErrorImpl<'a> {
             .map(|variant| {
                 let VariantInfo {
                     name: ref variant_name,
-                    ref source_field,
+                    ref selector_kind,
                     ..
                 } = *variant;
 
-                match *source_field {
-                    Some(ref source_field) => {
+                let source_field = selector_kind.source_field();
+
+                match source_field {
+                    Some(source_field) => {
                         let SourceField {
                             name: ref field_name,
                             ..
@@ -1595,13 +1754,12 @@ impl<'a> ErrorCompatImpl<'a> {
         self.0.variants.iter().map(|variant| {
             let VariantInfo {
                 name: ref variant_name,
-                ref source_field,
+                ref selector_kind,
                 ref backtrace_field,
                 ..
             } = *variant;
 
-
-            match (source_field, backtrace_field) {
+            match (selector_kind.source_field(), backtrace_field) {
                 (Some(ref source_field), _) if source_field.backtrace_delegate => {
                     let SourceField {
                         name: ref field_name,
@@ -1798,6 +1956,35 @@ mod sponge {
         }
     }
 
+    impl<C, T, E> FromIterator<Result<C, E>> for AllErrors<T, E>
+    where
+        T: FromIterator<C>,
+    {
+        fn from_iter<I>(i: I) -> Self
+        where
+            I: IntoIterator<Item = Result<C, E>>,
+        {
+            let mut errors = Vec::new();
+
+            let inner = i
+                .into_iter()
+                .flat_map(|v| match v {
+                    Ok(v) => Ok(v),
+                    Err(e) => {
+                        errors.push(e);
+                        Err(())
+                    }
+                })
+                .collect();
+
+            if errors.is_empty() {
+                AllErrors(Ok(inner))
+            } else {
+                AllErrors(Err(errors))
+            }
+        }
+    }
+
     impl<C, T, E> FromIterator<Result<C, Vec<E>>> for AllErrors<T, E>
     where
         T: FromIterator<C>,
diff --git a/src/guide/attributes.md b/src/guide/attributes.md
index ae8f970..cbebc1d 100644
--- a/src/guide/attributes.md
+++ b/src/guide/attributes.md
@@ -46,6 +46,42 @@ fn main() {
 }
 ```
 
+## Controlling context
+
+Sometimes, an underlying error can only occur in exactly one context
+and there's no additional information that can be provided to the
+caller. In these cases, you can use `#[snafu(context(false))]` to
+indicate that no context selector should be created. This allows using
+the `?` operator directly on the underlying error.
+
+Please think about your end users before making liberal use of this
+feature. Adding context to an error is often what distinguishes an
+actionable error from a frustrating one.
+
+**Example**
+
+```rust
+# use snafu::Snafu;
+#
+#[derive(Debug, Snafu)]
+enum Error {
+    #[snafu(context(false))]
+    NeedsNoIntroduction { source: VeryUniqueError },
+}
+
+fn my_code() -> Result<i32, Error> {
+    let val = do_something_unique()?;
+    Ok(val + 10)
+}
+
+# #[derive(Debug, Snafu)]
+# enum VeryUniqueError {}
+fn do_something_unique() -> Result<i32, VeryUniqueError> {
+    // ...
+#    Ok(42)
+}
+```
+
 ## Controlling visibility
 
 By default, each of the context selectors and their inherent


Test patch:
diff --git a/compatibility-tests/compile-fail/tests/ui/attribute-misuse.rs b/compatibility-tests/compile-fail/tests/ui/attribute-misuse.rs
index 1b81548..8019e63 100644
--- a/compatibility-tests/compile-fail/tests/ui/attribute-misuse.rs
+++ b/compatibility-tests/compile-fail/tests/ui/attribute-misuse.rs
@@ -6,6 +6,7 @@ mod enum_misuse {
     #[snafu(source(from(XXXX, Box::new)))]
     #[snafu(source(true))]
     #[snafu(backtrace)]
+    #[snafu(context)]
     enum EnumError {
         AVariant,
     }
@@ -34,6 +35,7 @@ mod field_misuse {
             #[snafu(visibility(pub))]
             #[snafu(source(false))]
             #[snafu(source(from(XXXX, Box::new)))]
+            #[snafu(context)]
             source: String,
         },
     }
@@ -51,6 +53,7 @@ mod struct_misuse {
     #[snafu(visibility(pub))]
     #[snafu(source(true))]
     #[snafu(backtrace)]
+    #[snafu(context)]
     struct StructError(Box<UsableError>);
 }
 
diff --git a/compatibility-tests/compile-fail/tests/ui/attribute-misuse.stderr b/compatibility-tests/compile-fail/tests/ui/attribute-misuse.stderr
index 60f18b7..2626336 100644
--- a/compatibility-tests/compile-fail/tests/ui/attribute-misuse.stderr
+++ b/compatibility-tests/compile-fail/tests/ui/attribute-misuse.stderr
@@ -22,62 +22,80 @@ error: `backtrace` attribute is only valid on fields of an error variant, not on
 8 |     #[snafu(backtrace)]
   |             ^^^^^^^^^
 
+error: `context` attribute is only valid on an error variant, not on an enum
+ --> $DIR/attribute-misuse.rs:9:13
+  |
+9 |     #[snafu(context)]
+  |             ^^^^^^^
+
 error: `source` attribute is only valid on fields of an error variant, not on a variant
-  --> $DIR/attribute-misuse.rs:20:46
+  --> $DIR/attribute-misuse.rs:21:46
    |
-20 |         #[snafu(display("an error variant"), source(from(XXXX, Box::new)))]
+21 |         #[snafu(display("an error variant"), source(from(XXXX, Box::new)))]
    |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: `source` attribute is only valid on fields of an error variant, not on a variant
-  --> $DIR/attribute-misuse.rs:21:17
+  --> $DIR/attribute-misuse.rs:22:17
    |
-21 |         #[snafu(source)]
+22 |         #[snafu(source)]
    |                 ^^^^^^
 
 error: `backtrace` attribute is only valid on fields of an error variant, not on a variant
-  --> $DIR/attribute-misuse.rs:22:17
+  --> $DIR/attribute-misuse.rs:23:17
    |
-22 |         #[snafu(backtrace)]
+23 |         #[snafu(backtrace)]
    |                 ^^^^^^^^^
 
 error: `display` attribute is only valid on variants of an error enum, not on a field
-  --> $DIR/attribute-misuse.rs:33:21
+  --> $DIR/attribute-misuse.rs:34:21
    |
-33 |             #[snafu(display("display should not work here"))]
+34 |             #[snafu(display("display should not work here"))]
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: `visibility` attribute is only valid on an error enum and its variants, not on a field
-  --> $DIR/attribute-misuse.rs:34:21
+  --> $DIR/attribute-misuse.rs:35:21
    |
-34 |             #[snafu(visibility(pub))]
+35 |             #[snafu(visibility(pub))]
    |                     ^^^^^^^^^^^^^^^
 
 error: Incompatible attributes [`source(false)`, `source(from)`] specified on a field
-  --> $DIR/attribute-misuse.rs:36:21
+  --> $DIR/attribute-misuse.rs:37:21
    |
-36 |             #[snafu(source(from(XXXX, Box::new)))]
+37 |             #[snafu(source(from(XXXX, Box::new)))]
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+error: `context` attribute is only valid on variants of an error enum, not on a field
+  --> $DIR/attribute-misuse.rs:38:21
+   |
+38 |             #[snafu(context)]
+   |                     ^^^^^^^
+
 error: `display` attribute is only valid on variants of an error enum, not on a struct
-  --> $DIR/attribute-misuse.rs:49:13
+  --> $DIR/attribute-misuse.rs:51:13
    |
-49 |     #[snafu(display("display should not work here"))]
+51 |     #[snafu(display("display should not work here"))]
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: `visibility` attribute is only valid on an error enum and its variants, not on a struct
-  --> $DIR/attribute-misuse.rs:51:13
+  --> $DIR/attribute-misuse.rs:53:13
    |
-51 |     #[snafu(visibility(pub))]
+53 |     #[snafu(visibility(pub))]
    |             ^^^^^^^^^^^^^^^
 
 error: `source(bool)` attribute is only valid on fields of an error variant, not on a struct
-  --> $DIR/attribute-misuse.rs:52:13
+  --> $DIR/attribute-misuse.rs:54:13
    |
-52 |     #[snafu(source(true))]
+54 |     #[snafu(source(true))]
    |             ^^^^^^^^^^^^
 
 error: `backtrace` attribute is only valid on fields of an error variant, not on a struct
-  --> $DIR/attribute-misuse.rs:53:13
+  --> $DIR/attribute-misuse.rs:55:13
    |
-53 |     #[snafu(backtrace)]
+55 |     #[snafu(backtrace)]
    |             ^^^^^^^^^
+
+error: `context` attribute is only valid on variants of an error enum, not on a struct
+  --> $DIR/attribute-misuse.rs:56:13
+   |
+56 |     #[snafu(context)]
+   |             ^^^^^^^
diff --git a/compatibility-tests/compile-fail/tests/ui/no_context.rs b/compatibility-tests/compile-fail/tests/ui/no_context.rs
new file mode 100644
index 0000000..eb468fe
--- /dev/null
+++ b/compatibility-tests/compile-fail/tests/ui/no_context.rs
@@ -0,0 +1,16 @@
+extern crate snafu;
+
+use snafu::Snafu;
+
+type BoxError = Box<dyn std::error::Error>;
+
+#[derive(Debug, Snafu)]
+enum Error {
+    #[snafu(context(false))]
+    MissingSource { },
+
+    #[snafu(context(false))]
+    HasUserFields { source: BoxError, a: i32, b: i32 },
+}
+
+fn main() {}
diff --git a/compatibility-tests/compile-fail/tests/ui/no_context.stderr b/compatibility-tests/compile-fail/tests/ui/no_context.stderr
new file mode 100644
index 0000000..5dd31ac
--- /dev/null
+++ b/compatibility-tests/compile-fail/tests/ui/no_context.stderr
@@ -0,0 +1,17 @@
+error: Context selectors without context must not have context fields
+  --> $DIR/no_context.rs:13:39
+   |
+13 |     HasUserFields { source: BoxError, a: i32, b: i32 },
+   |                                       ^^^^^^
+
+error: Context selectors without context must not have context fields
+  --> $DIR/no_context.rs:13:47
+   |
+13 |     HasUserFields { source: BoxError, a: i32, b: i32 },
+   |                                               ^^^^^^
+
+error: Context selectors without context must have a source field
+  --> $DIR/no_context.rs:10:5
+   |
+10 |     MissingSource { },
+   |     ^^^^^^^^^^^^^
diff --git a/tests/no_context.rs b/tests/no_context.rs
new file mode 100644
index 0000000..f151f10
--- /dev/null
+++ b/tests/no_context.rs
@@ -0,0 +1,89 @@
+extern crate snafu;
+
+use snafu::Snafu;
+
+#[derive(Debug, Snafu)]
+enum AlphaError {
+    AlphaDummy,
+}
+
+#[derive(Debug, Snafu)]
+enum BetaError {
+    BetaDummy,
+}
+
+#[derive(Debug, Snafu)]
+enum Error {
+    #[snafu(context(false))]
+    Alpha { source: AlphaError },
+
+    #[snafu(context(false))]
+    Beta { source: BetaError },
+}
+
+fn alpha() -> Result<i32, AlphaError> {
+    Ok(1)
+}
+
+fn beta() -> Result<i32, BetaError> {
+    Ok(2)
+}
+
+fn example() -> Result<i32, Error> {
+    let a = alpha()?;
+    let b = beta()?;
+    Ok(a * 10 + b)
+}
+
+fn check<T: std::error::Error>() {}
+
+#[test]
+fn implements_error() {
+    check::<Error>();
+
+    assert_eq!(12, example().unwrap());
+}
+
+mod with_backtraces {
+    use super::*;
+    use snafu::{Backtrace, Snafu};
+
+    #[derive(Debug, Snafu)]
+    enum Error {
+        #[snafu(context(false))]
+        Alpha {
+            source: AlphaError,
+            backtrace: Backtrace,
+        },
+    }
+
+    #[test]
+    fn implements_error() {
+        check::<Error>();
+    }
+}
+
+mod with_bounds {
+    use super::*;
+    use snafu::Snafu;
+    use std::fmt::{Debug, Display};
+
+    #[derive(Debug, Snafu)]
+    enum GenericError<T, U = i32> {
+        Something { things: T, other: U },
+    }
+
+    #[derive(Debug, Snafu)]
+    enum Error<T: 'static>
+    where
+        T: Debug + Display + Copy,
+    {
+        #[snafu(context(false))]
+        Generic { source: GenericError<T> },
+    }
+
+    #[test]
+    fn implements_error() {
+        check::<Error<i32>>();
+    }
+}