Repository: dtolnay/thiserror
Pull request #380 (2024-11-08 18:30:51)

Print field's address instead of local's address for Pointer


Patch:
diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs
index d369012..e69a7e9 100644
--- a/impl/src/fmt.rs
+++ b/impl/src/fmt.rs
@@ -3,7 +3,7 @@ use crate::attr::{Display, Trait};
 use crate::scan_expr::scan_expr;
 use crate::unraw::{IdentUnraw, MemberUnraw};
 use proc_macro2::{Delimiter, TokenStream};
-use quote::{format_ident, quote, quote_spanned, ToTokens};
+use quote::{format_ident, quote, quote_spanned};
 use std::collections::{BTreeSet, HashMap, HashSet};
 use std::iter;
 use syn::ext::IdentExt;
@@ -85,10 +85,11 @@ impl Display<'_> {
                 }
                 _ => continue,
             };
-            let mut binding_value = ToTokens::into_token_stream(match &member {
+            let binding_value = match &member {
                 MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
                 MemberUnraw::Named(ident) => ident.to_local(),
-            });
+            };
+            let mut wrapped_binding_value = quote!(::thiserror::__private::Var(#binding_value));
             let end_spec = match read.find('}') {
                 Some(end_spec) => end_spec,
                 None => return Ok(()),
@@ -105,7 +106,9 @@ impl Display<'_> {
                 Some(_) => Trait::Display,
                 None => {
                     has_bonus_display = true;
-                    binding_value.extend(quote_spanned!(span=> .as_display()));
+                    wrapped_binding_value = quote_spanned! {span=>
+                        #binding_value.as_display()
+                    };
                     Trait::Display
                 }
             };
@@ -126,7 +129,7 @@ impl Display<'_> {
             out += &formatvar.to_string();
             let local = formatvar.to_local();
             if macro_named_args.insert(member) {
-                bindings.push((local, binding_value));
+                bindings.push((local, wrapped_binding_value));
             } else {
                 // Already added to bindings by a previous use.
             }
diff --git a/src/lib.rs b/src/lib.rs
index 1489fc6..ea5244d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -280,6 +280,7 @@ mod aserror;
 mod display;
 #[cfg(error_generic_member_access)]
 mod provide;
+mod var;
 
 pub use thiserror_impl::*;
 
@@ -294,6 +295,8 @@ pub mod __private {
     #[doc(hidden)]
     pub use crate::provide::ThiserrorProvide;
     #[doc(hidden)]
+    pub use crate::var::Var;
+    #[doc(hidden)]
     pub use core::error::Error;
     #[cfg(all(feature = "std", not(thiserror_no_backtrace_type)))]
     #[doc(hidden)]
diff --git a/src/var.rs b/src/var.rs
new file mode 100644
index 0000000..8643590
--- /dev/null
+++ b/src/var.rs
@@ -0,0 +1,61 @@
+use core::fmt::{
+    self, Binary, Debug, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex,
+};
+
+pub struct Var<'a, T: ?Sized>(pub &'a T);
+
+/// Pointer is the only one for which there is a difference in behavior between
+/// `Var<'a, T>` vs `&'a T`.
+impl<'a, T: Pointer + ?Sized> Pointer for Var<'a, T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        Pointer::fmt(self.0, formatter)
+    }
+}
+
+impl<'a, T: Binary + ?Sized> Binary for Var<'a, T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        Binary::fmt(self.0, formatter)
+    }
+}
+
+impl<'a, T: Debug + ?Sized> Debug for Var<'a, T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        Debug::fmt(self.0, formatter)
+    }
+}
+
+impl<'a, T: Display + ?Sized> Display for Var<'a, T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        Display::fmt(self.0, formatter)
+    }
+}
+
+impl<'a, T: LowerExp + ?Sized> LowerExp for Var<'a, T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        LowerExp::fmt(self.0, formatter)
+    }
+}
+
+impl<'a, T: LowerHex + ?Sized> LowerHex for Var<'a, T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        LowerHex::fmt(self.0, formatter)
+    }
+}
+
+impl<'a, T: Octal + ?Sized> Octal for Var<'a, T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        Octal::fmt(self.0, formatter)
+    }
+}
+
+impl<'a, T: UpperExp + ?Sized> UpperExp for Var<'a, T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        UpperExp::fmt(self.0, formatter)
+    }
+}
+
+impl<'a, T: UpperHex + ?Sized> UpperHex for Var<'a, T> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        UpperHex::fmt(self.0, formatter)
+    }
+}


Test patch:
diff --git a/tests/test_display.rs b/tests/test_display.rs
index 1904eae..1980d55 100644
--- a/tests/test_display.rs
+++ b/tests/test_display.rs
@@ -251,6 +251,20 @@ fn test_nested_tuple_field() {
     assert("0", Error(Inner(0)));
 }
 
+#[test]
+fn test_pointer() {
+    #[derive(Error, Debug)]
+    #[error("{field:p}")]
+    pub struct Struct {
+        field: Box<i32>,
+    }
+
+    let s = Struct {
+        field: Box::new(-1),
+    };
+    assert_eq!(s.to_string(), format!("{:p}", s.field));
+}
+
 #[test]
 fn test_macro_rules() {
     // Regression test for https://github.com/dtolnay/thiserror/issues/86