From 67faae44b46715c482fc45fa4e3ee655da3e2a64 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Mon, 4 Nov 2024 14:29:25 -0500 Subject: [PATCH 1/2] Add regression test for issue 345 error[E0599]: `test_no_bound_on_named_fmt::Error` doesn't implement `std::fmt::Display` --> tests/test_generics.rs:173:22 | 168 | struct Error { | --------------- method `to_string` not found for this struct because it doesn't satisfy `_: Display` or `_: ToString` ... 173 | assert_eq!(error.to_string(), "..."); | ^^^^^^^^^ `test_no_bound_on_named_fmt::Error` cannot be formatted with the default formatter | = note: the following trait bounds were not satisfied: `test_no_bound_on_named_fmt::Error: std::fmt::Display` which is required by `test_no_bound_on_named_fmt::Error: ToString` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead note: the trait `std::fmt::Display` must be implemented --> $RUSTUP_HOME/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:727:1 | 727 | pub trait Display { | ^^^^^^^^^^^^^^^^^ = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `to_string`, perhaps you need to implement it: candidate #1: `ToString` --- tests/test_generics.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_generics.rs b/tests/test_generics.rs index d7790e2d..4d49edf5 100644 --- a/tests/test_generics.rs +++ b/tests/test_generics.rs @@ -159,3 +159,16 @@ pub struct StructFromGeneric { #[derive(Error, Debug)] #[error(transparent)] pub struct StructTransparentGeneric(pub E); + +// Regression test for https://github.com/dtolnay/thiserror/issues/345 +#[test] +fn test_no_bound_on_named_fmt() { + #[derive(Error, Debug)] + #[error("{thing}", thing = "...")] + struct Error { + thing: T, + } + + let error = Error { thing: DebugOnly }; + assert_eq!(error.to_string(), "..."); +} From 593317939cc2c757de9f759ab66b057201b1681a Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Mon, 4 Nov 2024 17:20:18 -0500 Subject: [PATCH 2/2] Only apply inferred bounds if interpolation refers to field, not format var --- impl/src/fmt.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/impl/src/fmt.rs b/impl/src/fmt.rs index 8b571d98..d555c247 100644 --- a/impl/src/fmt.rs +++ b/impl/src/fmt.rs @@ -69,6 +69,20 @@ impl Display<'_> { } _ => continue, }; + let formatvar = match &member { + MemberUnraw::Unnamed(index) => IdentUnraw::new(format_ident!("_{}", index)), + MemberUnraw::Named(ident) => ident.clone(), + }; + out += &formatvar.to_string(); + if !named_args.insert(formatvar.clone()) { + // Already specified in the format argument list. + continue; + } + if !has_trailing_comma { + args.extend(quote_spanned!(span=> ,)); + } + let local = formatvar.to_local(); + args.extend(quote_spanned!(span=> #formatvar = #local)); if let Some(&field) = member_index.get(&member) { let end_spec = match read.find('}') { Some(end_spec) => end_spec, @@ -83,28 +97,15 @@ impl Display<'_> { Some('b') => Trait::Binary, Some('e') => Trait::LowerExp, Some('E') => Trait::UpperExp, - Some(_) | None => Trait::Display, + Some(_) => Trait::Display, + None => { + has_bonus_display = true; + args.extend(quote_spanned!(span=> .as_display())); + Trait::Display + } }; implied_bounds.insert((field, bound)); } - let formatvar = match &member { - MemberUnraw::Unnamed(index) => IdentUnraw::new(format_ident!("_{}", index)), - MemberUnraw::Named(ident) => ident.clone(), - }; - out += &formatvar.to_string(); - if !named_args.insert(formatvar.clone()) { - // Already specified in the format argument list. - continue; - } - 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; - args.extend(quote_spanned!(span=> .as_display())); - } has_trailing_comma = false; }