Repository: dtolnay/thiserror
Pull request #351 (2024-11-04 21:39:50)

Add test of r#Backtrace that is not std::backtrace::Backtrace
Without `r#Backtrace`:

```console
error[E0308]: mismatched types
   --> tests/test_backtrace.rs:42:14
    |
42  |     #[derive(Error, Debug)]
    |              ^^^^^
    |              |
    |              expected `std::backtrace::Backtrace`, found `not_backtrace::Backtrace`
    |              arguments to this method are incorrect
    |
    = note: `not_backtrace::Backtrace` and `std::backtrace::Backtrace` have similar names, but are actually distinct types
note: `not_backtrace::Backtrace` is defined in the current crate
   --> tests/test_backtrace.rs:26:9
    |
26  |         pub struct Backtrace;
    |         ^^^^^^^^^^^^^^^^^^^^
note: `std::backtrace::Backtrace` is defined in crate `std`
   --> $RUSTUP_HOME/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/backtrace.rs:108:1
    |
108 | pub struct Backtrace {
    | ^^^^^^^^^^^^^^^^^^^^
note: method defined here
   --> $RUSTUP_HOME/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/error.rs:607:12
    |
607 |     pub fn provide_ref<T: ?Sized + 'static>(&mut self, value: &'a T) -> &mut Self {
    |            ^^^^^^^^^^^
    = note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info)
```


Patch:
diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs
index aa69986..3d1394c 100644
--- a/impl/src/fmt.rs
+++ b/impl/src/fmt.rs
@@ -1,7 +1,6 @@
 use crate::ast::Field;
 use crate::attr::{Display, Trait};
 use crate::scan_expr::scan_expr;
-use crate::unraw::IdentUnraw;
 use proc_macro2::{TokenStream, TokenTree};
 use quote::{format_ident, quote, quote_spanned};
 use std::collections::{BTreeSet as Set, HashMap as Map};
@@ -58,13 +57,14 @@ impl Display<'_> {
                         Err(_) => return,
                     };
                     if !member_index.contains_key(&member) {
-                        out += int;
+                        out += &int;
                         continue;
                     }
                     member
                 }
                 'a'..='z' | 'A'..='Z' | '_' => {
-                    let ident = Ident::new(take_ident(&mut read), span);
+                    let mut ident = take_ident(&mut read);
+                    ident.set_span(span);
                     Member::Named(ident)
                 }
                 _ => continue,
@@ -87,10 +87,14 @@ impl Display<'_> {
                 };
                 implied_bounds.insert((field, bound));
             }
-            let formatvar = IdentUnraw::new(match &member {
+            let local = match &member {
                 Member::Unnamed(index) => format_ident!("_{}", index),
                 Member::Named(ident) => ident.clone(),
-            });
+            };
+            let mut formatvar = local.clone();
+            if formatvar.to_string().starts_with("r#") {
+                formatvar = format_ident!("r_{}", formatvar);
+            }
             out += &formatvar.to_string();
             if !named_args.insert(formatvar.clone()) {
                 // Already specified in the format argument list.
@@ -99,7 +103,6 @@ impl Display<'_> {
             if !has_trailing_comma {
                 args.extend(quote_spanned!(span=> ,));
             }
-            let local = formatvar.to_local();
             args.extend(quote_spanned!(span=> #formatvar = #local));
             if read.starts_with('}') && member_index.contains_key(&member) {
                 has_bonus_display = true;
@@ -117,7 +120,7 @@ impl Display<'_> {
 }
 
 struct FmtArguments {
-    named: Set<IdentUnraw>,
+    named: Set<Ident>,
     unnamed: bool,
 }
 
@@ -155,7 +158,7 @@ fn try_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
             break;
         }
         if input.peek(Ident::peek_any) && input.peek2(Token![=]) && !input.peek2(Token![==]) {
-            let ident: IdentUnraw = input.parse()?;
+            let ident = input.call(Ident::parse_any)?;
             input.parse::<Token![=]>()?;
             args.named.insert(ident);
         } else {
@@ -187,7 +190,7 @@ fn fallback_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
             && !input.peek3(Token![==])
         {
             input.parse::<Token![,]>()?;
-            let ident: IdentUnraw = input.parse()?;
+            let ident = input.call(Ident::parse_any)?;
             input.parse::<Token![=]>()?;
             args.named.insert(ident);
         }
@@ -214,28 +217,35 @@ fn is_syn_full() -> bool {
     }
 }
 
-fn take_int<'a>(read: &mut &'a str) -> &'a str {
-    let mut int_len = 0;
-    for ch in read.chars() {
+fn take_int(read: &mut &str) -> String {
+    let mut int = String::new();
+    for (i, ch) in read.char_indices() {
         match ch {
-            '0'..='9' => int_len += 1,
-            _ => break,
+            '0'..='9' => int.push(ch),
+            _ => {
+                *read = &read[i..];
+                break;
+            }
         }
     }
-    let (int, rest) = read.split_at(int_len);
-    *read = rest;
     int
 }
 
-fn take_ident<'a>(read: &mut &'a str) -> &'a str {
-    let mut ident_len = 0;
-    for ch in read.chars() {
+fn take_ident(read: &mut &str) -> Ident {
+    let mut ident = String::new();
+    let raw = read.starts_with("r#");
+    if raw {
+        ident.push_str("r#");
+        *read = &read[2..];
+    }
+    for (i, ch) in read.char_indices() {
         match ch {
-            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident_len += 1,
-            _ => break,
+            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
+            _ => {
+                *read = &read[i..];
+                break;
+            }
         }
     }
-    let (ident, rest) = read.split_at(ident_len);
-    *read = rest;
-    ident
+    Ident::parse_any.parse_str(&ident).unwrap()
 }
diff --git a/impl/src/lib.rs b/impl/src/lib.rs
index a86242d..7d7c6e3 100644
--- a/impl/src/lib.rs
+++ b/impl/src/lib.rs
@@ -26,7 +26,6 @@ mod generics;
 mod prop;
 mod scan_expr;
 mod span;
-mod unraw;
 mod valid;
 
 use proc_macro::TokenStream;
diff --git a/impl/src/unraw.rs b/impl/src/unraw.rs
deleted file mode 100644
index 1fcaf6c..0000000
--- a/impl/src/unraw.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-use proc_macro2::{Ident, Span, TokenStream};
-use quote::ToTokens;
-use std::cmp::Ordering;
-use std::fmt::{self, Display};
-use syn::ext::IdentExt as _;
-use syn::parse::{Parse, ParseStream, Result};
-
-#[derive(Clone)]
-#[repr(transparent)]
-pub(crate) struct IdentUnraw(Ident);
-
-impl IdentUnraw {
-    pub fn new(ident: Ident) -> Self {
-        IdentUnraw(ident)
-    }
-
-    pub fn to_local(&self) -> Ident {
-        let unraw = self.0.unraw();
-        let repr = unraw.to_string();
-        if syn::parse_str::<Ident>(&repr).is_err() {
-            if let "_" | "super" | "self" | "Self" | "crate" = repr.as_str() {
-                // Some identifiers are never allowed to appear as raw, like r#self and r#_.
-            } else {
-                return Ident::new_raw(&repr, Span::call_site());
-            }
-        }
-        unraw
-    }
-}
-
-impl Display for IdentUnraw {
-    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-        Display::fmt(&self.0.unraw(), formatter)
-    }
-}
-
-impl Eq for IdentUnraw {}
-
-impl PartialEq for IdentUnraw {
-    fn eq(&self, other: &Self) -> bool {
-        PartialEq::eq(&self.0.unraw(), &other.0.unraw())
-    }
-}
-
-impl Ord for IdentUnraw {
-    fn cmp(&self, other: &Self) -> Ordering {
-        Ord::cmp(&self.0.unraw(), &other.0.unraw())
-    }
-}
-
-impl PartialOrd for IdentUnraw {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(Self::cmp(self, other))
-    }
-}
-
-impl Parse for IdentUnraw {
-    fn parse(input: ParseStream) -> Result<Self> {
-        input.call(Ident::parse_any).map(IdentUnraw::new)
-    }
-}
-
-impl ToTokens for IdentUnraw {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        self.0.unraw().to_tokens(tokens);
-    }
-}


Test patch:
diff --git a/tests/test_backtrace.rs b/tests/test_backtrace.rs
index 8f11da3..2a4cffa 100644
--- a/tests/test_backtrace.rs
+++ b/tests/test_backtrace.rs
@@ -21,6 +21,11 @@ pub mod structs {
     use std::sync::Arc;
     use thiserror::Error;
 
+    mod not_backtrace {
+        #[derive(Debug)]
+        pub struct Backtrace;
+    }
+
     #[derive(Error, Debug)]
     #[error("...")]
     pub struct PlainBacktrace {
@@ -34,6 +39,12 @@ pub mod structs {
         backtrace: Backtrace,
     }
 
+    #[derive(Error, Debug)]
+    #[error("...")]
+    pub struct NotBacktrace {
+        backtrace: crate::structs::not_backtrace::r#Backtrace,
+    }
+
     #[derive(Error, Debug)]
     #[error("...")]
     pub struct OptBacktrace {
diff --git a/tests/test_display.rs b/tests/test_display.rs
index 27bb72c..89bdc4a 100644
--- a/tests/test_display.rs
+++ b/tests/test_display.rs
@@ -279,7 +279,7 @@ fn test_macro_rules() {
 #[test]
 fn test_raw() {
     #[derive(Error, Debug)]
-    #[error("braced raw error: {fn}")]
+    #[error("braced raw error: {r#fn}")]
     struct Error {
         r#fn: &'static str,
     }
@@ -291,7 +291,7 @@ fn test_raw() {
 fn test_raw_enum() {
     #[derive(Error, Debug)]
     enum Error {
-        #[error("braced raw error: {fn}")]
+        #[error("braced raw error: {r#fn}")]
         Braced { r#fn: &'static str },
     }
 
@@ -299,21 +299,23 @@ fn test_raw_enum() {
 }
 
 #[test]
-fn test_keyword() {
+fn test_raw_conflict() {
     #[derive(Error, Debug)]
-    #[error("error: {type}", type = 1)]
-    struct Error;
+    enum Error {
+        #[error("braced raw error: {r#func}, {func}", func = "U")]
+        Braced { r#func: &'static str },
+    }
 
-    assert("error: 1", Error);
+    assert("braced raw error: T, U", Error::Braced { r#func: "T" });
 }
 
 #[test]
-fn test_self() {
+fn test_keyword() {
     #[derive(Error, Debug)]
-    #[error("error: {self:?}")]
+    #[error("error: {type}", type = 1)]
     struct Error;
 
-    assert("error: Error", Error);
+    assert("error: 1", Error);
 }
 
 #[test]
diff --git a/tests/ui/display-underscore.rs b/tests/ui/display-underscore.rs
deleted file mode 100644
index 335614b..0000000
--- a/tests/ui/display-underscore.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-#[error("{_}")]
-pub struct Error;
-
-fn main() {}
diff --git a/tests/ui/display-underscore.stderr b/tests/ui/display-underscore.stderr
deleted file mode 100644
index 36882b9..0000000
--- a/tests/ui/display-underscore.stderr
+++ /dev/null
@@ -1,7 +0,0 @@
-error: invalid format string: invalid argument name `_`
- --> tests/ui/display-underscore.rs:4:11
-  |
-4 | #[error("{_}")]
-  |           ^ invalid argument name in format string
-  |
-  = note: argument name cannot be a single underscore
diff --git a/tests/ui/raw-identifier.rs b/tests/ui/raw-identifier.rs
deleted file mode 100644
index e7e66d0..0000000
--- a/tests/ui/raw-identifier.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-#[error("error: {r#fn}")]
-pub struct Error {
-    r#fn: &'static str,
-}
-
-fn main() {
-    let r#fn = "...";
-    let _ = format!("error: {r#fn}");
-}
diff --git a/tests/ui/raw-identifier.stderr b/tests/ui/raw-identifier.stderr
deleted file mode 100644
index a3ce94d..0000000
--- a/tests/ui/raw-identifier.stderr
+++ /dev/null
@@ -1,21 +0,0 @@
-error: invalid format string: raw identifiers are not supported
- --> tests/ui/raw-identifier.rs:4:18
-  |
-4 | #[error("error: {r#fn}")]
-  |                  --^^
-  |                  |
-  |                  raw identifier used here in format string
-  |                  help: remove the `r#`
-  |
-  = note: identifiers in format strings can be keywords and don't need to be prefixed with `r#`
-
-error: invalid format string: raw identifiers are not supported
-  --> tests/ui/raw-identifier.rs:11:30
-   |
-11 |     let _ = format!("error: {r#fn}");
-   |                              --^^
-   |                              |
-   |                              raw identifier used here in format string
-   |                              help: remove the `r#`
-   |
-   = note: identifiers in format strings can be keywords and don't need to be prefixed with `r#`