From 8339c28c03f0f43893553412aab90cf687840233 Mon Sep 17 00:00:00 2001 From: Vaiz <4908982+Vaiz@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:56:22 +0100 Subject: [PATCH 1/6] fix(ohno_macros): keep the caller's syntax context when rewriting a unit struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `#[ohno::error]` turns a unit struct into a tuple struct so it has room for the `OhnoCore` field. The parentheses and the semicolon it synthesises for that came from `Paren::default()` and `::default()`, both of which carry `Span::call_site()`. The rewritten item therefore ended up in the macro's syntax context rather than the caller's. `rustc` reports `dead_code` on a struct at its identifier span re-tagged with the item's syntax context, and cancels any lint whose primary span sits in an external macro expansion. So an error type declared as a unit struct was never reported as dead, and a caller's `#[expect(dead_code)]` on it could never be fulfilled — it surfaced instead as `unfulfilled_lint_expectations`, which fails a `-D warnings` gate with no hint that ohno was involved. Taking the spans from the declaration's own identifier keeps the item in the caller's context. Only the unit shape was affected; the named and tuple shapes push a field onto delimiters the author wrote, and `#[derive(ohno::Error)]` never rebuilds the item at all. Generated tokens are unchanged, so the expansion snapshots are untouched. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/ohno_macros_impl/src/error_attr/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/ohno_macros_impl/src/error_attr/mod.rs b/crates/ohno_macros_impl/src/error_attr/mod.rs index b2a48fb71..5059aeac7 100644 --- a/crates/ohno_macros_impl/src/error_attr/mod.rs +++ b/crates/ohno_macros_impl/src/error_attr/mod.rs @@ -101,13 +101,18 @@ fn inject_core(item: &mut ItemStruct) { } Fields::Unnamed(fields) => fields.unnamed.push(unnamed_core(&marker)), Fields::Unit => { + // The synthesized delimiters take the declaration's own span, so the rewritten struct + // keeps the caller's syntax context. Call-site tokens would place the item in this + // macro's context, where `rustc` discards `dead_code` on it as external-macro code and + // a caller's `#[expect(dead_code)]` can never be fulfilled. + let span = item.ident.span(); let mut unnamed = FieldsUnnamed { - paren_token: syn::token::Paren::default(), + paren_token: syn::token::Paren(span), unnamed: syn::punctuated::Punctuated::new(), }; unnamed.unnamed.push(unnamed_core(&marker)); item.fields = Fields::Unnamed(unnamed); - item.semi_token = Some(::default()); + item.semi_token = Some(syn::Token![;](span)); } } } From ff6aafae08cac2d91f3c69e1c96a9e824d3fae27 Mon Sep 17 00:00:00 2001 From: Vaiz <4908982+Vaiz@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:35:09 +0100 Subject: [PATCH 2/6] fix(ohno_macros): drop `#[automatically_derived]` from the constructors impl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `#[automatically_derived]` is accepted only on a trait `impl`. The derive put it on the inherent `impl` holding `new` and `caused_by`, where `rustc` rejects it with "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release". Nobody sees that warning today, because lints raised inside an external macro expansion are discarded — including after the unit-struct span fix, since these tokens come from the derive rather than from the caller's declaration. So it is latent: it costs nothing now and breaks every `#[ohno::error]` and `#[derive(ohno::Error)]` user at once on the release that promotes it. Removing it changes no behaviour. The attribute marks an impl as machine-written so `dead_code` skips field reads within it, which is why `Debug` deliberately goes without one; the constructors carry their own `#[allow(dead_code)]` and never relied on it. The five trait impls the derive emits keep theirs, as those are the placements the attribute is for. Snapshots re-recorded: 35 deleted lines, every one of them the attribute above an inherent `impl T {`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/ohno_macros/docs/design.md | 6 +++++- .../src/derive_error/generate/constructors.rs | 4 +++- ...c_api__a_from_attribute_generates_its_conversions.snap | 6 ------ ...ic_api__a_numeric_argument_roots_at_a_tuple_field.snap | 3 --- ...blic_api__a_positional_argument_is_scoped_to_self.snap | 8 -------- ...__a_template_lowers_to_a_literal_or_a_format_call.snap | 6 ------ ...public_api__an_argument_may_call_a_method_of_self.snap | 3 --- ...ublic_api__every_struct_shape_generates_its_items.snap | 5 ----- .../public_api__generics_thread_through_every_impl.snap | 3 --- ...lic_api__the_suppressing_flags_remove_their_items.snap | 1 - 10 files changed, 8 insertions(+), 37 deletions(-) diff --git a/crates/ohno_macros/docs/design.md b/crates/ohno_macros/docs/design.md index aee88658f..ad8566cd0 100644 --- a/crates/ohno_macros/docs/design.md +++ b/crates/ohno_macros/docs/design.md @@ -278,7 +278,7 @@ error type becomes infallible. including the core, so it iterates the full field list and branches once on style, into `debug_struct` for a named struct or `debug_tuple` for a tuple one. -It is the one generated item that is **not** `#[automatically_derived]`. +It is one of the two generated items that are **not** `#[automatically_derived]`. Dead-code analysis ignores field reads inside a derived `Debug`, so marking this one would make every field that only `Debug` reads look unused in the user's own crate. @@ -290,6 +290,10 @@ non-core fields, so the core is skipped and declaration order is kept, and both take each parameter as `impl Into<_>` of the field's type. `pub(crate)` is fixed by R1.4, settled by ADO 7675155. +Their `impl` block is the other item that is **not** `#[automatically_derived]`, +because that attribute is accepted only on a trait `impl` and this one is +inherent. `rustc` warns on it today and states it will become a hard error. + **`From`** is emitted once per conversion, zipping the non-core fields with that conversion's initializers and building the core from the source error. The source binding is named `error`, which is the name a field expression refers to. diff --git a/crates/ohno_macros_impl/src/derive_error/generate/constructors.rs b/crates/ohno_macros_impl/src/derive_error/generate/constructors.rs index c6ac36cac..06416a9a1 100644 --- a/crates/ohno_macros_impl/src/derive_error/generate/constructors.rs +++ b/crates/ohno_macros_impl/src/derive_error/generate/constructors.rs @@ -7,6 +7,9 @@ //! convenience for the crate that owns the error, not part of its public API, so adding a field is //! not a breaking change for callers. An error type that needs a public constructor declares one by //! hand, under `#[no_constructors]`. +//! +//! Their `impl` block carries no `#[automatically_derived]`. That attribute is accepted only on a +//! trait `impl`, and `rustc` warns that using it on an inherent one will become a hard error. use proc_macro2::TokenStream; use quote::quote; @@ -37,7 +40,6 @@ pub(crate) fn generate(model: &Model) -> TokenStream { let caused_by_body = construct(&model.shape, &initializers(model, "e!(#core::from(error)))); quote! { - #[automatically_derived] impl #impl_generics #ident #ty_generics #where_clause { /// Creates the error with no source. #[allow(dead_code, reason = "generated for every error type, used at the author's discretion")] diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__a_from_attribute_generates_its_conversions.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__a_from_attribute_generates_its_conversions.snap index bc9ac58b2..cb69e135a 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__a_from_attribute_generates_its_conversions.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__a_from_attribute_generates_its_conversions.snap @@ -47,7 +47,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -141,7 +140,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -245,7 +243,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -339,7 +336,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -427,7 +423,6 @@ impl ::core::fmt::Debug for T { f.debug_tuple("T").field(&self.0).field(&self.1).finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -514,7 +509,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__a_numeric_argument_roots_at_a_tuple_field.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__a_numeric_argument_roots_at_a_tuple_field.snap index b752ba5b0..82ed04646 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__a_numeric_argument_roots_at_a_tuple_field.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__a_numeric_argument_roots_at_a_tuple_field.snap @@ -54,7 +54,6 @@ impl ::core::fmt::Debug for T { f.debug_tuple("T").field(&self.0).field(&self.1).finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -139,7 +138,6 @@ impl ::core::fmt::Debug for T { f.debug_tuple("T").field(&self.0).field(&self.1).finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -228,7 +226,6 @@ impl ::core::fmt::Debug for T { f.debug_tuple("T").field(&self.0).field(&self.1).finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__a_positional_argument_is_scoped_to_self.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__a_positional_argument_is_scoped_to_self.snap index 2be31194f..7e98b004f 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__a_positional_argument_is_scoped_to_self.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__a_positional_argument_is_scoped_to_self.snap @@ -62,7 +62,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -171,7 +170,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -280,7 +278,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -389,7 +386,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -498,7 +494,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -607,7 +602,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -712,7 +706,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -821,7 +814,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__a_template_lowers_to_a_literal_or_a_format_call.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__a_template_lowers_to_a_literal_or_a_format_call.snap index 8e4575891..105b91d0e 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__a_template_lowers_to_a_literal_or_a_format_call.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__a_template_lowers_to_a_literal_or_a_format_call.snap @@ -62,7 +62,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -163,7 +162,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -272,7 +270,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -377,7 +374,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -474,7 +470,6 @@ impl ::core::fmt::Debug for T { f.debug_tuple("T").field(&self.0).field(&self.1).finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -569,7 +564,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__an_argument_may_call_a_method_of_self.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__an_argument_may_call_a_method_of_self.snap index 750b6777b..9c0fa9b71 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__an_argument_may_call_a_method_of_self.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__an_argument_may_call_a_method_of_self.snap @@ -66,7 +66,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -175,7 +174,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -280,7 +278,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__every_struct_shape_generates_its_items.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__every_struct_shape_generates_its_items.snap index 2ff86f329..ee4fd4bf4 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__every_struct_shape_generates_its_items.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__every_struct_shape_generates_its_items.snap @@ -46,7 +46,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -123,7 +122,6 @@ impl ::core::fmt::Debug for T { f.debug_tuple("T").field(&self.0).field(&self.1).finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -196,7 +194,6 @@ impl ::core::fmt::Debug for T { f.debug_struct("T").field("inner", &self.inner).finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -278,7 +275,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -365,7 +361,6 @@ impl ::core::fmt::Debug for T { f.debug_struct("T").field("path", &self.path).field("mine", &self.mine).finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__generics_thread_through_every_impl.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__generics_thread_through_every_impl.snap index d0a4bb260..5c3111319 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__generics_thread_through_every_impl.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__generics_thread_through_every_impl.snap @@ -48,7 +48,6 @@ impl ::core::fmt::Debug for T { .finish() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( @@ -137,7 +136,6 @@ impl<'a> ::core::fmt::Debug for T<'a> { .finish() } } -#[automatically_derived] impl<'a> T<'a> { /// Creates the error with no source. #[allow( @@ -235,7 +233,6 @@ where f.debug_struct("T").field("a", &self.a).field("inner", &self.inner).finish() } } -#[automatically_derived] impl T where A: Clone, diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__the_suppressing_flags_remove_their_items.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__the_suppressing_flags_remove_their_items.snap index 4ed4011e6..44c675c46 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__the_suppressing_flags_remove_their_items.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__the_suppressing_flags_remove_their_items.snap @@ -38,7 +38,6 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } -#[automatically_derived] impl T { /// Creates the error with no source. #[allow( From 179f226d45e17a772789cf27a61ace19eacd01da Mon Sep 17 00:00:00 2001 From: Vaiz <4908982+Vaiz@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:39:30 +0100 Subject: [PATCH 3/6] docs(ohno_macros): state the `#[automatically_derived]` rule where it applies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit left the design describing the attribute twice, once per exception, with each paragraph defined in terms of the other — "one of the two generated items", "the other item". That reads as a retrofit and puts a rule that governs every generated item inside the entries for two of them. State it once in the section preamble, beside the generics rule it resembles: the attribute marks an `impl` as machine-written so dead-code analysis skips the field reads inside it, and rustc accepts it only on a trait `impl`. Both exceptions then follow from the rule instead of being asserted next to it. `Debug` keeps its own paragraph, because omitting the attribute there is a deliberate trade rather than a consequence of where it is allowed, and a reader who does not know that would eventually "fix" it. The constructors need no paragraph of their own now that the preamble covers them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/ohno_macros/docs/design.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/ohno_macros/docs/design.md b/crates/ohno_macros/docs/design.md index ad8566cd0..1bbc55fa1 100644 --- a/crates/ohno_macros/docs/design.md +++ b/crates/ohno_macros/docs/design.md @@ -254,6 +254,14 @@ Two values recur below: the core field's member, and the type's name as a string literal — the default message the runtime falls back to when nothing else renders. +`#[automatically_derived]` follows one rule as well, with an exception on each +side of it. The attribute marks an `impl` as machine-written, which makes +dead-code analysis skip the field reads inside it, and `rustc` accepts it only on +a trait `impl`. Every generated trait `impl` therefore carries it except `Debug`, +which wants those field reads counted; and the inherent `impl` holding the +constructors carries it on neither ground, being no trait `impl` at all — `rustc` +warns there today and states it will become a hard error. + **`Display`** delegates to the core, passing the lowered message as an override when there is one. `OhnoCore::format_error` appends `caused by:`, the enrichment lines and the backtrace, so the generated code decides only the message. The @@ -278,7 +286,7 @@ error type becomes infallible. including the core, so it iterates the full field list and branches once on style, into `debug_struct` for a named struct or `debug_tuple` for a tuple one. -It is one of the two generated items that are **not** `#[automatically_derived]`. +It is the one generated trait `impl` without `#[automatically_derived]`. Dead-code analysis ignores field reads inside a derived `Debug`, so marking this one would make every field that only `Debug` reads look unused in the user's own crate. @@ -290,10 +298,6 @@ non-core fields, so the core is skipped and declaration order is kept, and both take each parameter as `impl Into<_>` of the field's type. `pub(crate)` is fixed by R1.4, settled by ADO 7675155. -Their `impl` block is the other item that is **not** `#[automatically_derived]`, -because that attribute is accepted only on a trait `impl` and this one is -inherent. `rustc` warns on it today and states it will become a hard error. - **`From`** is emitted once per conversion, zipping the non-core fields with that conversion's initializers and building the core from the source error. The source binding is named `error`, which is the name a field expression refers to. From 0a6528c70bbd63af3190b42f54bb5b44e3ab9f2f Mon Sep 17 00:00:00 2001 From: Vaiz <4908982+Vaiz@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:48:17 +0100 Subject: [PATCH 4/6] docs(ohno_macros): describe the state, not the change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comments and design text added here carried prose that only makes sense to a reader who knows what the code used to do, and that expires on its own: a compiler roadmap note ("rustc warns there today and states it will become a hard error"), and a module doc whose whole subject was the absence of an attribute rather than anything the module does. Neither survives its own change. Once the attribute is a hard error, "warns today" is wrong; and a note explaining why an invalid attribute is missing tells a reader nothing they could have acted on, since the compiler rejects it without help. The rule that matters — the attribute is accepted only on a trait `impl` — is already stated where the generated items are described, so the absence needs no commentary of its own. The span comment stays, reworded as a constraint rather than a contrast: it guards a real trap, since `Paren::default()` is the obvious spelling and silently puts the item in the wrong syntax context. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/ohno_macros/docs/design.md | 9 ++++----- .../src/derive_error/generate/constructors.rs | 3 --- crates/ohno_macros_impl/src/error_attr/mod.rs | 8 ++++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/ohno_macros/docs/design.md b/crates/ohno_macros/docs/design.md index 1bbc55fa1..36eee15a7 100644 --- a/crates/ohno_macros/docs/design.md +++ b/crates/ohno_macros/docs/design.md @@ -256,11 +256,10 @@ renders. `#[automatically_derived]` follows one rule as well, with an exception on each side of it. The attribute marks an `impl` as machine-written, which makes -dead-code analysis skip the field reads inside it, and `rustc` accepts it only on -a trait `impl`. Every generated trait `impl` therefore carries it except `Debug`, -which wants those field reads counted; and the inherent `impl` holding the -constructors carries it on neither ground, being no trait `impl` at all — `rustc` -warns there today and states it will become a hard error. +dead-code analysis skip the field reads inside it, and it is accepted only on a +trait `impl`. Every generated trait `impl` therefore carries it except `Debug`, +which wants those field reads counted, and the inherent `impl` holding the +constructors cannot carry it at all. **`Display`** delegates to the core, passing the lowered message as an override when there is one. `OhnoCore::format_error` appends `caused by:`, the enrichment diff --git a/crates/ohno_macros_impl/src/derive_error/generate/constructors.rs b/crates/ohno_macros_impl/src/derive_error/generate/constructors.rs index 06416a9a1..1062805b7 100644 --- a/crates/ohno_macros_impl/src/derive_error/generate/constructors.rs +++ b/crates/ohno_macros_impl/src/derive_error/generate/constructors.rs @@ -7,9 +7,6 @@ //! convenience for the crate that owns the error, not part of its public API, so adding a field is //! not a breaking change for callers. An error type that needs a public constructor declares one by //! hand, under `#[no_constructors]`. -//! -//! Their `impl` block carries no `#[automatically_derived]`. That attribute is accepted only on a -//! trait `impl`, and `rustc` warns that using it on an inherent one will become a hard error. use proc_macro2::TokenStream; use quote::quote; diff --git a/crates/ohno_macros_impl/src/error_attr/mod.rs b/crates/ohno_macros_impl/src/error_attr/mod.rs index 5059aeac7..49f8cc275 100644 --- a/crates/ohno_macros_impl/src/error_attr/mod.rs +++ b/crates/ohno_macros_impl/src/error_attr/mod.rs @@ -101,10 +101,10 @@ fn inject_core(item: &mut ItemStruct) { } Fields::Unnamed(fields) => fields.unnamed.push(unnamed_core(&marker)), Fields::Unit => { - // The synthesized delimiters take the declaration's own span, so the rewritten struct - // keeps the caller's syntax context. Call-site tokens would place the item in this - // macro's context, where `rustc` discards `dead_code` on it as external-macro code and - // a caller's `#[expect(dead_code)]` can never be fulfilled. + // Both tokens take the declaration's own span, so the rewritten struct stays in the + // caller's syntax context. Under call-site spans it would sit in this macro's context, + // where `dead_code` on it is discarded as external-macro code and a caller's + // `#[expect(dead_code)]` can never be fulfilled. let span = item.ident.span(); let mut unnamed = FieldsUnnamed { paren_token: syn::token::Paren(span), From 7dddf2c23af7d5a9e4c0b0828a55b1172869857a Mon Sep 17 00:00:00 2001 From: Vaiz <4908982+Vaiz@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:54:48 +0100 Subject: [PATCH 5/6] docs(ohno_macros): state the annotation rule in one line The design spent a paragraph on `#[automatically_derived]`, half of it on the constructors' inherent `impl`. That half is not a design decision: the attribute is only accepted on a trait `impl`, so an inherent one could never carry it, and a reader can act on none of it. What is worth stating is that generated trait `impl`s are annotated, and that `Debug` is the exception. One line covers the first; the `Debug` entry already covered the second and now reads as a single sentence, so the rule and its reason are not split across two. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/ohno_macros/docs/design.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/ohno_macros/docs/design.md b/crates/ohno_macros/docs/design.md index 36eee15a7..89eb4eed3 100644 --- a/crates/ohno_macros/docs/design.md +++ b/crates/ohno_macros/docs/design.md @@ -250,17 +250,12 @@ trait `impl` carries all three, as does the single inherent `impl` holding the constructors. That covers lifetimes, type parameters and where clauses in one rule, which is what R1.3's "all of them carry the input's generics" asks for. +Every generated trait `impl` but `Debug` carries `#[automatically_derived]`. + Two values recur below: the core field's member, and the type's name as a string literal — the default message the runtime falls back to when nothing else renders. -`#[automatically_derived]` follows one rule as well, with an exception on each -side of it. The attribute marks an `impl` as machine-written, which makes -dead-code analysis skip the field reads inside it, and it is accepted only on a -trait `impl`. Every generated trait `impl` therefore carries it except `Debug`, -which wants those field reads counted, and the inherent `impl` holding the -constructors cannot carry it at all. - **`Display`** delegates to the core, passing the lowered message as an override when there is one. `OhnoCore::format_error` appends `caused by:`, the enrichment lines and the backtrace, so the generated code decides only the message. The @@ -285,8 +280,8 @@ error type becomes infallible. including the core, so it iterates the full field list and branches once on style, into `debug_struct` for a named struct or `debug_tuple` for a tuple one. -It is the one generated trait `impl` without `#[automatically_derived]`. -Dead-code analysis ignores field reads inside a derived `Debug`, so marking this +It is the one generated trait `impl` without `#[automatically_derived]`, because +dead-code analysis ignores field reads inside a derived `Debug`, and marking this one would make every field that only `Debug` reads look unused in the user's own crate. From 55e0008648db97d5d08f70d8d6f10840aed78ef6 Mon Sep 17 00:00:00 2001 From: Vaiz <4908982+Vaiz@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:14:58 +0100 Subject: [PATCH 6/6] fix(ohno_macros): mark the generated `Debug` as `#[automatically_derived]` `Debug` carries `#[rustc_trivial_field_reads]`, so an `#[automatically_derived]` `Debug` impl has its field reads discarded by dead-code analysis. The generated impl went without the attribute to avoid that, on the grounds that it would make a field only `Debug` reads look unused. It does, and that is the right answer. A field nothing but `Debug` reads is unused, `#[derive(Debug)]` reports it, and the remedy is the author's: `#[allow(dead_code)]`, an accessor, or a place in the `#[display]` message. Withholding the attribute suppressed that report for every error type in every consumer crate, so a field left behind by a refactor was never flagged. The suppression was also broader than the reasoning assumed. `Display`, `Error`, `Enrichable` and `ErrorExt` are not `#[rustc_trivial_field_reads]`, so their reads still count: the core stays live through all four, and a field named by the `#[display]` template stays live through `Display`. Measured on a crate carrying all four cases, only the `Debug`-only field is reported. Nine fields in this crate's own examples and tests were relying on the suppression. Each is handled on its merits rather than silenced: - `default_constructors` and `derive_with_fields` gain the `#[display]` message they should always have had, which reads the fields and makes the examples print something other than the type name. - `constructor_integration` asserts the values the constructor stored, which the sibling test already did. - the rest carry `#[expect(dead_code)]` with a reason, being fields that exist to demonstrate a struct shape or to be compared through `Debug`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/ohno/examples/default_constructors.rs | 1 + crates/ohno/examples/derive_with_fields.rs | 3 ++- crates/ohno/examples/from_attribute.rs | 2 ++ crates/ohno/examples/multiple_core_fields.rs | 1 + crates/ohno/tests/constructor_integration.rs | 2 ++ crates/ohno/tests/debug_implementation_comparison.rs | 9 ++++++++- crates/ohno/tests/derive_auto_debug.rs | 5 ++++- crates/ohno_macros/docs/design.md | 12 +++++++----- .../src/derive_error/generate/traits.rs | 4 +--- ...__a_from_attribute_generates_its_conversions.snap | 6 ++++++ ...i__a_numeric_argument_roots_at_a_tuple_field.snap | 3 +++ ...api__a_positional_argument_is_scoped_to_self.snap | 8 ++++++++ ...emplate_lowers_to_a_literal_or_a_format_call.snap | 6 ++++++ ...c_api__an_argument_may_call_a_method_of_self.snap | 3 +++ ..._api__every_struct_shape_generates_its_items.snap | 5 +++++ ...blic_api__generics_thread_through_every_impl.snap | 3 +++ ...pi__the_suppressing_flags_remove_their_items.snap | 1 + 17 files changed, 63 insertions(+), 11 deletions(-) diff --git a/crates/ohno/examples/default_constructors.rs b/crates/ohno/examples/default_constructors.rs index 9d900eab6..010508251 100644 --- a/crates/ohno/examples/default_constructors.rs +++ b/crates/ohno/examples/default_constructors.rs @@ -6,6 +6,7 @@ use ohno::{Error, OhnoCore}; #[derive(Error)] +#[display("cannot open {path}")] struct MyError { path: String, inner: OhnoCore, diff --git a/crates/ohno/examples/derive_with_fields.rs b/crates/ohno/examples/derive_with_fields.rs index 9612c2e7f..e9c71b5ce 100644 --- a/crates/ohno/examples/derive_with_fields.rs +++ b/crates/ohno/examples/derive_with_fields.rs @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! Prints `InvalidQuery` +//! Prints an `InvalidQuery` through both `Display` and `Debug`. use ohno::{Error, OhnoCore}; #[derive(Error)] +#[display("invalid query: {operation} on {table}")] struct InvalidQuery { operation: String, table: String, diff --git a/crates/ohno/examples/from_attribute.rs b/crates/ohno/examples/from_attribute.rs index fd3324071..5b7a32fee 100644 --- a/crates/ohno/examples/from_attribute.rs +++ b/crates/ohno/examples/from_attribute.rs @@ -6,7 +6,9 @@ #[ohno::error] #[from(std::io::Error, std::fmt::Error)] struct MyError { + #[expect(dead_code, reason = "the example shows that #[from] fills the remaining fields")] optional_field: Option, + #[expect(dead_code, reason = "the example shows that #[from] fills the remaining fields")] count: u32, } diff --git a/crates/ohno/examples/multiple_core_fields.rs b/crates/ohno/examples/multiple_core_fields.rs index b96508e1c..60eb99013 100644 --- a/crates/ohno/examples/multiple_core_fields.rs +++ b/crates/ohno/examples/multiple_core_fields.rs @@ -7,6 +7,7 @@ use ohno::{Error, OhnoCore}; #[derive(Error)] struct MyError { + #[expect(dead_code, reason = "carried as data; the example is about disambiguating the core")] metadata: OhnoCore, #[error] // Mark the primary error field main_error: OhnoCore, diff --git a/crates/ohno/tests/constructor_integration.rs b/crates/ohno/tests/constructor_integration.rs index df4256408..61ba89e8d 100644 --- a/crates/ohno/tests/constructor_integration.rs +++ b/crates/ohno/tests/constructor_integration.rs @@ -28,6 +28,8 @@ fn test_complex_error_constructors() { } let db_err = DatabaseError::new("users", "SELECT"); + assert_eq!(db_err.table, "users"); + assert_eq!(db_err.operation, "SELECT"); assert_error_message!(db_err, "DatabaseError"); let db_err_with_error = DatabaseError::caused_by("users", "SELECT", "Table not found"); diff --git a/crates/ohno/tests/debug_implementation_comparison.rs b/crates/ohno/tests/debug_implementation_comparison.rs index 61985520c..a91a30500 100644 --- a/crates/ohno/tests/debug_implementation_comparison.rs +++ b/crates/ohno/tests/debug_implementation_comparison.rs @@ -19,7 +19,9 @@ fn test_named_struct_debug_structure() { pub(crate) struct TestNamedStruct { #[error] pub inner: OhnoCore, + #[expect(dead_code, reason = "read only by the generated Debug")] pub code: i32, + #[expect(dead_code, reason = "read only by the generated Debug")] pub message: String, } @@ -46,7 +48,11 @@ fn test_tuple_struct_debug_structure() { pub(crate) struct RefTupleStruct(pub OhnoCore, pub String, pub i32); #[derive(Error)] - pub(crate) struct TestTupleStruct(#[error] pub OhnoCore, pub String, pub i32); + pub(crate) struct TestTupleStruct( + #[error] pub OhnoCore, + #[expect(dead_code, reason = "read only by the generated Debug")] pub String, + #[expect(dead_code, reason = "read only by the generated Debug")] pub i32, + ); let ref_struct = RefTupleStruct(OhnoCore::from("error_content"), "additional_info".to_string(), 42); @@ -103,6 +109,7 @@ fn test_struct_with_enum_field_debug_structure() { struct TestEnumFieldStruct { #[error] error: OhnoCore, + #[expect(dead_code, reason = "read only by the generated Debug")] status: TestStatus, } diff --git a/crates/ohno/tests/derive_auto_debug.rs b/crates/ohno/tests/derive_auto_debug.rs index 9509d4e07..52e7d02d0 100644 --- a/crates/ohno/tests/derive_auto_debug.rs +++ b/crates/ohno/tests/derive_auto_debug.rs @@ -23,7 +23,10 @@ struct MultiFieldError { // Test tuple struct with automatic Debug #[derive(Error)] -struct TupleError(String, #[error] OhnoCore); +struct TupleError( + #[expect(dead_code, reason = "read only by the generated Debug")] String, + #[error] OhnoCore, +); // Test unit struct conversion with automatic Debug #[derive(Error)] diff --git a/crates/ohno_macros/docs/design.md b/crates/ohno_macros/docs/design.md index 89eb4eed3..96a8eb848 100644 --- a/crates/ohno_macros/docs/design.md +++ b/crates/ohno_macros/docs/design.md @@ -250,7 +250,7 @@ trait `impl` carries all three, as does the single inherent `impl` holding the constructors. That covers lifetimes, type parameters and where clauses in one rule, which is what R1.3's "all of them carry the input's generics" asks for. -Every generated trait `impl` but `Debug` carries `#[automatically_derived]`. +Every generated trait `impl` carries `#[automatically_derived]`. Two values recur below: the core field's member, and the type's name as a string literal — the default message the runtime falls back to when nothing else @@ -280,10 +280,12 @@ error type becomes infallible. including the core, so it iterates the full field list and branches once on style, into `debug_struct` for a named struct or `debug_tuple` for a tuple one. -It is the one generated trait `impl` without `#[automatically_derived]`, because -dead-code analysis ignores field reads inside a derived `Debug`, and marking this -one would make every field that only `Debug` reads look unused in the user's own -crate. +`Debug` carries `#[rustc_trivial_field_reads]`, so marking this impl +`#[automatically_derived]` makes dead-code analysis discard its field reads. A +field that nothing but `Debug` reads is then reported, which is the answer +`#[derive(Debug)]` gives too. The core and any field the `#[display]` template +names stay live, because the other generated impls read them and their traits +carry no such attribute. **Constructors** are emitted unless the model suppresses them. `new` takes one parameter per non-core field and defaults the core; `caused_by` takes the same diff --git a/crates/ohno_macros_impl/src/derive_error/generate/traits.rs b/crates/ohno_macros_impl/src/derive_error/generate/traits.rs index 1cae3437d..49d1fe671 100644 --- a/crates/ohno_macros_impl/src/derive_error/generate/traits.rs +++ b/crates/ohno_macros_impl/src/derive_error/generate/traits.rs @@ -149,9 +149,7 @@ pub(crate) fn debug(model: &Model) -> TokenStream { }; quote! { - // Not `#[automatically_derived]`: dead-code analysis skips field reads in a derived - // `Debug`, so marking this one would make every field that only `Debug` reads look unused - // in the user's own crate. + #[automatically_derived] impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { #body diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__a_from_attribute_generates_its_conversions.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__a_from_attribute_generates_its_conversions.snap index cb69e135a..41e40e594 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__a_from_attribute_generates_its_conversions.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__a_from_attribute_generates_its_conversions.snap @@ -39,6 +39,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -132,6 +133,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -235,6 +237,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -328,6 +331,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -418,6 +422,7 @@ impl ::ohno::ErrorExt for T { self.1.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_tuple("T").field(&self.0).field(&self.1).finish() @@ -500,6 +505,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__a_numeric_argument_roots_at_a_tuple_field.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__a_numeric_argument_roots_at_a_tuple_field.snap index 82ed04646..76249982b 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__a_numeric_argument_roots_at_a_tuple_field.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__a_numeric_argument_roots_at_a_tuple_field.snap @@ -49,6 +49,7 @@ impl ::ohno::ErrorExt for T { self.1.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_tuple("T").field(&self.0).field(&self.1).finish() @@ -133,6 +134,7 @@ impl ::ohno::ErrorExt for T { self.1.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_tuple("T").field(&self.0).field(&self.1).finish() @@ -221,6 +223,7 @@ impl ::ohno::ErrorExt for T { self.1.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_tuple("T").field(&self.0).field(&self.1).finish() diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__a_positional_argument_is_scoped_to_self.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__a_positional_argument_is_scoped_to_self.snap index 7e98b004f..84c4bd2ae 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__a_positional_argument_is_scoped_to_self.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__a_positional_argument_is_scoped_to_self.snap @@ -53,6 +53,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -161,6 +162,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -269,6 +271,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -377,6 +380,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -485,6 +489,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -593,6 +598,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -697,6 +703,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -805,6 +812,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__a_template_lowers_to_a_literal_or_a_format_call.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__a_template_lowers_to_a_literal_or_a_format_call.snap index 105b91d0e..35d91f94e 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__a_template_lowers_to_a_literal_or_a_format_call.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__a_template_lowers_to_a_literal_or_a_format_call.snap @@ -53,6 +53,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -153,6 +154,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -261,6 +263,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -365,6 +368,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -465,6 +469,7 @@ impl ::ohno::ErrorExt for T { self.1.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_tuple("T").field(&self.0).field(&self.1).finish() @@ -556,6 +561,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__an_argument_may_call_a_method_of_self.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__an_argument_may_call_a_method_of_self.snap index 9c0fa9b71..6d635550c 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__an_argument_may_call_a_method_of_self.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__an_argument_may_call_a_method_of_self.snap @@ -57,6 +57,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -165,6 +166,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -269,6 +271,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__every_struct_shape_generates_its_items.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__every_struct_shape_generates_its_items.snap index ee4fd4bf4..44285b98b 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__every_struct_shape_generates_its_items.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__every_struct_shape_generates_its_items.snap @@ -38,6 +38,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -117,6 +118,7 @@ impl ::ohno::ErrorExt for T { self.1.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_tuple("T").field(&self.0).field(&self.1).finish() @@ -189,6 +191,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T").field("inner", &self.inner).finish() @@ -266,6 +269,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -356,6 +360,7 @@ impl ::ohno::ErrorExt for T { self.mine.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T").field("path", &self.path).field("mine", &self.mine).finish() diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__generics_thread_through_every_impl.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__generics_thread_through_every_impl.snap index 5c3111319..23d803502 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__generics_thread_through_every_impl.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__generics_thread_through_every_impl.snap @@ -39,6 +39,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -128,6 +129,7 @@ impl<'a> ::ohno::ErrorExt for T<'a> { self.inner.backtrace() } } +#[automatically_derived] impl<'a> ::core::fmt::Debug for T<'a> { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T") @@ -225,6 +227,7 @@ where self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T where A: Clone, diff --git a/crates/ohno_macros_impl/tests/snapshots/public_api__the_suppressing_flags_remove_their_items.snap b/crates/ohno_macros_impl/tests/snapshots/public_api__the_suppressing_flags_remove_their_items.snap index 44c675c46..3e4c52ebe 100644 --- a/crates/ohno_macros_impl/tests/snapshots/public_api__the_suppressing_flags_remove_their_items.snap +++ b/crates/ohno_macros_impl/tests/snapshots/public_api__the_suppressing_flags_remove_their_items.snap @@ -109,6 +109,7 @@ impl ::ohno::ErrorExt for T { self.inner.backtrace() } } +#[automatically_derived] impl ::core::fmt::Debug for T { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_struct("T").field("inner", &self.inner).finish()