Repository: dtolnay/thiserror (Rust)

Issue:
Accept `.var` shorthand inside of parens
For example:

```rust
#[derive(Error, Debug)]
pub enum Error {
    #[error("{}", print(.0))]
    Io(io::Error),
}

fn print(error: &io::Error) -> String {
    unimplemented!()
}
```

We should expand `.var` immediately after `(`, not just `,`.


Discussion:

Patch:
diff --git a/impl/src/attr.rs b/impl/src/attr.rs
index b23c15b7..89838bf5 100644
--- a/impl/src/attr.rs
+++ b/impl/src/attr.rs
@@ -1,8 +1,11 @@
-use proc_macro2::{TokenStream, TokenTree};
+use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
 use quote::{format_ident, quote, ToTokens};
 use std::iter::once;
 use syn::parse::{Nothing, Parse, ParseStream};
-use syn::{Attribute, Error, Field, Ident, Index, LitInt, LitStr, Result, Token};
+use syn::{
+    braced, bracketed, parenthesized, token, Attribute, Error, Field, Ident, Index, LitInt, LitStr,
+    Result, Token,
+};
 
 pub struct Display {
     pub fmt: LitStr,
@@ -12,36 +15,61 @@ pub struct Display {
 impl Parse for Display {
     fn parse(input: ParseStream) -> Result<Self> {
         let fmt: LitStr = input.parse()?;
-
-        let mut args = TokenStream::new();
-        let mut last_is_comma = false;
-        while !input.is_empty() {
-            if last_is_comma && input.peek(Token![.]) {
-                if input.peek2(Ident) {
-                    input.parse::<Token![.]>()?;
-                    last_is_comma = false;
-                    continue;
-                }
-                if input.peek2(LitInt) {
-                    input.parse::<Token![.]>()?;
-                    let int: Index = input.parse()?;
-                    let ident = format_ident!("_{}", int.index, span = int.span);
-                    args.extend(once(TokenTree::Ident(ident)));
-                    last_is_comma = false;
-                    continue;
-                }
-            }
-            last_is_comma = input.peek(Token![,]);
-            let token: TokenTree = input.parse()?;
-            args.extend(once(token));
-        }
-
+        let args = parse_token_expr(input, false)?;
         let mut display = Display { fmt, args };
         display.expand_shorthand();
         Ok(display)
     }
 }
 
+fn parse_token_expr(input: ParseStream, mut last_is_comma: bool) -> Result<TokenStream> {
+    let mut tokens = TokenStream::new();
+    while !input.is_empty() {
+        if last_is_comma && input.peek(Token![.]) {
+            if input.peek2(Ident) {
+                input.parse::<Token![.]>()?;
+                last_is_comma = false;
+                continue;
+            }
+            if input.peek2(LitInt) {
+                input.parse::<Token![.]>()?;
+                let int: Index = input.parse()?;
+                let ident = format_ident!("_{}", int.index, span = int.span);
+                tokens.extend(once(TokenTree::Ident(ident)));
+                last_is_comma = false;
+                continue;
+            }
+        }
+        last_is_comma = input.peek(Token![,]);
+        let token: TokenTree = if input.peek(token::Paren) {
+            let content;
+            let delimiter = parenthesized!(content in input);
+            let nested = parse_token_expr(&content, true)?;
+            let mut group = Group::new(Delimiter::Parenthesis, nested);
+            group.set_span(delimiter.span);
+            TokenTree::Group(group)
+        } else if input.peek(token::Brace) {
+            let content;
+            let delimiter = braced!(content in input);
+            let nested = parse_token_expr(&content, true)?;
+            let mut group = Group::new(Delimiter::Brace, nested);
+            group.set_span(delimiter.span);
+            TokenTree::Group(group)
+        } else if input.peek(token::Bracket) {
+            let content;
+            let delimiter = bracketed!(content in input);
+            let nested = parse_token_expr(&content, true)?;
+            let mut group = Group::new(Delimiter::Bracket, nested);
+            group.set_span(delimiter.span);
+            TokenTree::Group(group)
+        } else {
+            input.parse()?
+        };
+        tokens.extend(once(token));
+    }
+    Ok(tokens)
+}
+
 impl ToTokens for Display {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         let fmt = &self.fmt;


Test patch:
diff --git a/tests/test_display.rs b/tests/test_display.rs
index c6b2156e..7a15e567 100644
--- a/tests/test_display.rs
+++ b/tests/test_display.rs
@@ -35,6 +35,14 @@ enum EnumError {
 #[error("1 + 1 = {}", 1 + 1)]
 struct Arithmetic;
 
+#[derive(Error, Debug)]
+#[error("!bool = {}", not(.0))]
+struct NestedShorthand(bool);
+
+fn not(bool: &bool) -> bool {
+    !*bool
+}
+
 fn assert<T: Display>(expected: &str, value: T) {
     assert_eq!(expected, value.to_string());
 }
@@ -54,4 +62,5 @@ fn test_display() {
     assert("tuple error: 0", EnumError::Tuple(0));
     assert("unit error", EnumError::Unit);
     assert("1 + 1 = 2", Arithmetic);
+    assert("!bool = false", NestedShorthand(true));
 }