Skip to content

fix(ohno_macros): correct the syntax context of a rewritten unit struct and the #[automatically_derived] placements - #723

Open
Evgenii (Vaiz) wants to merge 6 commits into
mainfrom
u/vaiz/2026/09/02/ohno-unit-struct-span
Open

fix(ohno_macros): correct the syntax context of a rewritten unit struct and the #[automatically_derived] placements#723
Evgenii (Vaiz) wants to merge 6 commits into
mainfrom
u/vaiz/2026/09/02/ohno-unit-struct-span

Conversation

@Vaiz

@Vaiz Evgenii (Vaiz) commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🤖 Clawpilot here! Posted automatically by Clawpilot (an AI agent), not by a human. Please verify before acting.

Three fixes to #[ohno::error] / #[derive(ohno::Error)]. The third changes what consumers see — please read that part first.

1. A unit struct is emitted in the macro's syntax context

An error type declared as a unit struct is never reported by dead_code, even when nothing in the crate constructs or names it. A caller who writes #[expect(dead_code)] on such a type gets unfulfilled_lint_expectations instead, which fails a -D warnings gate with nothing pointing at ohno as the cause.

#[expect(dead_code, reason = "kept for a later change")]
#[ohno::error]
pub(crate) struct XsoError;      // warning: this lint expectation is unfulfilled

Cause

#[ohno::error] gives a unit struct room for the OhnoCore field by turning it into a tuple struct. The parentheses and the semicolon it synthesises for that came from Paren::default() and <Token![;]>::default(), both of which carry Span::call_site(), so the rewritten item landed in the macro's syntax context rather than the caller's.

rustc_passes::dead reports a struct at ident_span.with_ctxt(def_span.ctxt()) — the identifier's span re-tagged with the item's context — and lint_level cancels any lint whose primary span sits in an external macro expansion. The dead_code diagnostic was therefore dropped before it could fulfil the expectation.

Only the unit shape is affected. The named and tuple shapes push a field onto delimiters the author wrote, and #[derive(ohno::Error)] never rebuilds the item at all.

Fix

Take the synthesised spans from the declaration's own identifier. Measured against a consumer crate declaring all four forms with #[expect(dead_code)], on rustc 1.97 (ok = the expectation is fulfilled):

form before after
#[ohno::error] struct E; unfulfilled ok
#[ohno::error] struct E(u32); ok ok
#[ohno::error] struct E { n: u32 } ok ok
#[derive(ohno::Error)] struct E(#[error] OhnoCore); ok ok

Why there is no regression test

A test would have to assert that a #[expect(dead_code)] is fulfilled, which means denying unfulfilled_lint_expectations. That cannot pass on the repo's MSRV: rustc kept an error type alive through the #[allow(dead_code)] on its generated constructors until rust-lang/rust#154377 landed in 1.97, and RUST_MSRV is 1.95. Such a test becomes possible once the MSRV reaches 1.97.

That rustc bug is a separate, second cause of the same symptom: on 1.93–1.96 every shape is affected, including the derive form, and no change to ohno avoids it. This PR fixes the part that is ohno's, and is the part still reproducible on 1.97 and later.

2. #[automatically_derived] on the inherent constructors impl

That attribute is accepted only on a trait impl. The derive put it on the inherent impl holding new and caused_by, where rustc answers:

warning: `#[automatically_derived]` attribute cannot be used on inherent impl blocks
  = warning: this was previously accepted by the compiler but is being phased out;
             it will become a hard error in a future release!

No consumer sees that warning, because lints raised inside an external macro expansion are discarded. The unit-struct fix above does not change that — those tokens come from the derive rather than from the caller's declaration, which was verified by reverting the attribute independently and re-running a consumer crate. The defect is latent: it costs nothing until the release that promotes it to an error, at which point it breaks every #[ohno::error] and #[derive(ohno::Error)] user at once.

Removing it changes no behaviour. The constructors carry their own #[allow(dead_code)] and never relied on it.

3. The generated Debug now carries #[automatically_derived]

This one changes what consumers see. 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 deliberately, to avoid making a field that only Debug reads look unused.

It does make it look unused — and that is the correct answer. Such a field is unused, #[derive(Debug)] reports it, and the remedy belongs to the author: #[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 wider than the original reasoning assumed. Display, Error, Enrichable and ErrorExt are not #[rustc_trivial_field_reads], so their reads still count. Measured on one crate carrying all four cases, rustc 1.95:

field is read by reported?
#[display("cannot open {path}")] no
a user accessor no
the generated Debug only yes
nothing — it is the core no

So the core stays live through the other four impls, and a field named by the #[display] template stays live through Display. Only a genuinely unused field is reported.

Effect on this repo

A workspace check surfaced 9 such fields, all in ohno's own examples and tests, none in any other crate. 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 now asserts the values the constructor stored, which the sibling test already did.
  • The rest carry #[expect(dead_code)] with a reason — fields that exist to demonstrate a struct shape, or to be compared through Debug.

Effect on consumers

A consumer with an error field that nothing reads but Debug will get a new dead_code warning, and a build failure under -D warnings. That is the point of the change, but it is a visible break and worth a release note.

Validation

cargo test --all-features for ohno, ohno_macros and ohno_macros_impl passes on MSRV 1.95 — 49 + 53 unit tests, 12 trybuild UI tests, all insta snapshots — as do just anvil-fmt, just anvil-clippy, the nightly unstable-rustfmt.toml check, and a clean cargo check --workspace --all-targets.

Related

Reported internally as AB#7829355, whose original diagnosis blamed the constructors' #[allow(dead_code)]; that work item now carries the corrected analysis. Real-world instance: AssistantsOxide PR 5605707 deleted an #[expect(dead_code)] purely to work around part 1. That removal remains necessary there until the repo moves off ms-prod-1.95, because the rustc half applies regardless of this PR.

…nit struct

`#[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 `<Token![;]>::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>
Copilot AI lite review requested due to automatic review settings September 2, 2026 13:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The change is narrowly scoped to span construction for unit-struct rewriting and aligns with the stated diagnostic/context issue without introducing broader behavioral changes.

Pull request overview

This PR adjusts #[ohno::error]’s unit-struct rewrite so that newly synthesized delimiters (the tuple parens and trailing semicolon) inherit the caller’s syntax context, avoiding dead_code diagnostics being treated as “external macro” and therefore not fulfilling a caller’s #[expect(dead_code)].

Changes:

  • For unit structs, construct syn::token::Paren and syn::Token![;] using the struct identifier’s span instead of Span::call_site().
  • Add an inline comment documenting the lint-context rationale for future maintainers.
File summaries
File Description
crates/ohno_macros_impl/src/error_attr/mod.rs Preserves caller syntax context when rewriting unit structs by sourcing delimiter spans from item.ident.span().
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (c6addf0) to head (55e0008).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #723   +/-   ##
=======================================
  Coverage   100.0%   100.0%           
=======================================
  Files         587      587           
  Lines       63002    63007    +5     
=======================================
+ Hits        63002    63007    +5     
Flag Coverage Δ
linux-arm 94.8% <100.0%> (?)
windows 94.8% <100.0%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…rs impl

`#[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>
Copilot AI review requested due to automatic review settings September 2, 2026 14:35
@Vaiz Evgenii (Vaiz) changed the title fix(ohno_macros): keep the caller's syntax context when rewriting a unit struct fix(ohno_macros): keep the caller's syntax context on a unit struct, and drop the invalid #[automatically_derived] Sep 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are small, targeted, and align generated code with correct span hygiene and valid attribute placement without introducing new API surface.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

… applies

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>
Copilot AI review requested due to automatic review settings September 2, 2026 14:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The unit-struct rewrite constructs syn::token::Paren in a way that does not correctly populate its delimiter span field in syn 3.x, undermining the intended caller-context preservation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines 109 to 111
let mut unnamed = FieldsUnnamed {
paren_token: syn::token::Paren::default(),
paren_token: syn::token::Paren(span),
unnamed: syn::punctuated::Punctuated::new(),
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>
Copilot AI review requested due to automatic review settings September 2, 2026 14:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are narrowly scoped, address concrete macro/codegen correctness issues, and the accompanying documentation and snapshot updates are consistent with the new generated output.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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>
Copilot AI review requested due to automatic review settings September 2, 2026 14:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are narrowly scoped to span hygiene and removing an invalid attribute, with documentation and snapshot updates aligned to the new behavior.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

⚠️ Potential breaking changes detected

cargo semver-checks flagged the following on this PR. This is informational -- breaking changes between commits are expected; the major-version bump happens at release time, not on every PR.

anyspawn_azure

     Cloning origin/main
    Building anyspawn_azure v0.3.0 (current)
       Built [  14.108s] (current)
     Parsing anyspawn_azure v0.3.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-anyspawn_azure-0_3_0-default-8a8c741d160dc3b0/target/doc/anyspawn_azure.json
(supported formats are v55, v56, v57)

arty_executor

     Cloning origin/main
    Building arty_executor v0.1.1 (current)
       Built [   3.981s] (current)
     Parsing arty_executor v0.1.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-arty_executor-0_1_1-default-58ae7becc93be6ab/target/doc/arty_executor.json
(supported formats are v55, v56, v57)

automation

     Cloning origin/main
    Building automation v0.1.0 (current)
       Built [   5.653s] (current)
     Parsing automation v0.1.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-automation-0_1_0-default-01666ec060466c14/target/doc/automation.json
(supported formats are v55, v56, v57)

bytesbuf_io

     Cloning origin/main
    Building bytesbuf_io v0.9.0 (current)
       Built [   5.066s] (current)
     Parsing bytesbuf_io v0.9.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-bytesbuf_io-0_9_0-default-af8b2c0d74d14958/target/doc/bytesbuf_io.json
(supported formats are v55, v56, v57)

cachet

     Cloning origin/main
    Building cachet v0.13.0 (current)
       Built [  11.185s] (current)
     Parsing cachet v0.13.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-cachet-0_13_0-default-4aa0ab9725815152/target/doc/cachet.json
(supported formats are v55, v56, v57)

cachet_memory

     Cloning origin/main
    Building cachet_memory v0.7.0 (current)
       Built [   7.369s] (current)
     Parsing cachet_memory v0.7.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-cachet_memory-0_7_0-default-01666ec060466c14/target/doc/cachet_memory.json
(supported formats are v55, v56, v57)

cachet_service

     Cloning origin/main
    Building cachet_service v0.5.0 (current)
       Built [   4.181s] (current)
     Parsing cachet_service v0.5.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-cachet_service-0_5_0-default-01666ec060466c14/target/doc/cachet_service.json
(supported formats are v55, v56, v57)

cachet_tier

     Cloning origin/main
    Building cachet_tier v0.5.0 (current)
       Built [   4.453s] (current)
     Parsing cachet_tier v0.5.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-cachet_tier-0_5_0-default-d7b8c5ec6ce39049/target/doc/cachet_tier.json
(supported formats are v55, v56, v57)

fetch

     Cloning origin/main
    Building fetch v0.16.1 (current)
       Built [  35.720s] (current)
     Parsing fetch v0.16.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch-0_16_1-default-325273521c672bb6/target/doc/fetch.json
(supported formats are v55, v56, v57)

fetch_azure

     Cloning origin/main
    Building fetch_azure v0.6.1 (current)
       Built [  19.525s] (current)
     Parsing fetch_azure v0.6.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch_azure-0_6_1-default-01666ec060466c14/target/doc/fetch_azure.json
(supported formats are v55, v56, v57)

fetch_hyper

     Cloning origin/main
    Building fetch_hyper v0.7.1 (current)
       Built [  15.687s] (current)
     Parsing fetch_hyper v0.7.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch_hyper-0_7_1-default-66e07b58f00fba56/target/doc/fetch_hyper.json
(supported formats are v55, v56, v57)

fetch_tls

     Cloning origin/main
    Building fetch_tls v0.4.0 (current)
       Built [   7.394s] (current)
     Parsing fetch_tls v0.4.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch_tls-0_4_0-default-96384260aee26d8f/target/doc/fetch_tls.json
(supported formats are v55, v56, v57)

fetch_winhttp

     Cloning origin/main
    Building fetch_winhttp v0.1.1 (current)
       Built [   0.372s] (current)
     Parsing fetch_winhttp v0.1.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch_winhttp-0_1_1-default-01666ec060466c14/target/doc/fetch_winhttp.json
(supported formats are v55, v56, v57)

fetch_winhttp_impl

     Cloning origin/main
    Building fetch_winhttp_impl v0.1.1 (current)
       Built [   0.373s] (current)
     Parsing fetch_winhttp_impl v0.1.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch_winhttp_impl-0_1_1-default-f6bbf157605592e2/target/doc/fetch_winhttp_impl.json
(supported formats are v55, v56, v57)

http_extensions

     Cloning origin/main
    Building http_extensions v0.10.0 (current)
       Built [  11.184s] (current)
     Parsing http_extensions v0.10.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-http_extensions-0_10_0-default-af0120d9f0530326/target/doc/http_extensions.json
(supported formats are v55, v56, v57)

msvc_spectre_libs

     Cloning origin/main
    Building msvc_spectre_libs v0.2.0 (current)
       Built [   4.155s] (current)
     Parsing msvc_spectre_libs v0.2.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-msvc_spectre_libs-0_2_0-default-7bc93237a011c201/target/doc/msvc_spectre_libs.json
(supported formats are v55, v56, v57)

msvc_spectre_libs_build

     Cloning origin/main
    Building msvc_spectre_libs_build v0.1.0 (current)
       Built [   3.734s] (current)
     Parsing msvc_spectre_libs_build v0.1.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-msvc_spectre_libs_build-0_1_0-default-01666ec060466c14/target/doc/msvc_spectre_libs_build.json
(supported formats are v55, v56, v57)

observed

     Cloning origin/main
    Building observed v0.25.0 (current)
       Built [   5.702s] (current)
     Parsing observed v0.25.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed-0_25_0-default-d7b8c5ec6ce39049/target/doc/observed.json
(supported formats are v55, v56, v57)

observed_testing

     Cloning origin/main
    Building observed_testing v0.0.0 (current)
       Built [   6.101s] (current)
     Parsing observed_testing v0.0.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed_testing-0_0_0-default-01666ec060466c14/target/doc/observed_testing.json
(supported formats are v55, v56, v57)

observed_utils

     Cloning origin/main
    Building observed_utils v0.2.0 (current)
       Built [   5.814s] (current)
     Parsing observed_utils v0.2.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed_utils-0_2_0-default-01666ec060466c14/target/doc/observed_utils.json
(supported formats are v55, v56, v57)

ohno

     Cloning origin/main
    Building ohno v0.5.0 (current)
       Built [   3.528s] (current)
     Parsing ohno v0.5.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-ohno-0_5_0-default-fd2f176c69f3ec17/target/doc/ohno.json
(supported formats are v55, v56, v57)

ohno_macros_impl

     Cloning origin/main
    Building ohno_macros_impl v0.5.1 (current)
       Built [   2.181s] (current)
     Parsing ohno_macros_impl v0.5.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-ohno_macros_impl-0_5_1-default-01666ec060466c14/target/doc/ohno_macros_impl.json
(supported formats are v55, v56, v57)

recoverable

     Cloning origin/main
    Building recoverable v0.2.0 (current)
       Built [   0.304s] (current)
     Parsing recoverable v0.2.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-recoverable-0_2_0-default-01666ec060466c14/target/doc/recoverable.json
(supported formats are v55, v56, v57)

seatbelt

     Cloning origin/main
    Building seatbelt v0.8.0 (current)
       Built [   5.873s] (current)
     Parsing seatbelt v0.8.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-seatbelt-0_8_0-default-0cae1599156c7909/target/doc/seatbelt.json
(supported formats are v55, v56, v57)

seatbelt_http

     Cloning origin/main
    Building seatbelt_http v0.8.0 (current)
       Built [  10.719s] (current)
     Parsing seatbelt_http v0.8.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-seatbelt_http-0_8_0-default-b6ff85dfca11b668/target/doc/seatbelt_http.json
(supported formats are v55, v56, v57)

templated_uri

     Cloning origin/main
    Building templated_uri v0.5.0 (current)
       Built [   7.605s] (current)
     Parsing templated_uri v0.5.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-templated_uri-0_5_0-default-4343b0346f1eaeb1/target/doc/templated_uri.json
(supported formats are v55, v56, v57)

templated_uri_macros_impl

     Cloning origin/main
    Building templated_uri_macros_impl v0.5.0 (current)
       Built [   5.705s] (current)
     Parsing templated_uri_macros_impl v0.5.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-templated_uri_macros_impl-0_5_0-default-01666ec060466c14/target/doc/templated_uri_macros_impl.json
(supported formats are v55, v56, v57)

tick

     Cloning origin/main
    Building tick v0.6.0 (current)
       Built [   2.934s] (current)
     Parsing tick v0.6.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-tick-0_6_0-default-d3b4fd8dc0a15942/target/doc/tick.json
(supported formats are v55, v56, v57)

…ved]`

`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>
Copilot AI review requested due to automatic review settings September 2, 2026 15:15
@Vaiz Evgenii (Vaiz) changed the title fix(ohno_macros): keep the caller's syntax context on a unit struct, and drop the invalid #[automatically_derived] fix(ohno_macros): correct the syntax context of a rewritten unit struct and the #[automatically_derived] placements Sep 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes proc-macro hygiene and consumer-visible lint behavior, which warrants final human review despite the updates being internally consistent.

Review details
  • Files reviewed: 19/19 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants