Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/ohno/examples/default_constructors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use ohno::{Error, OhnoCore};

#[derive(Error)]
#[display("cannot open {path}")]
struct MyError {
path: String,
inner: OhnoCore,
Expand Down
3 changes: 2 additions & 1 deletion crates/ohno/examples/derive_with_fields.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
2 changes: 2 additions & 0 deletions crates/ohno/examples/from_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
#[expect(dead_code, reason = "the example shows that #[from] fills the remaining fields")]
count: u32,
}

Expand Down
1 change: 1 addition & 0 deletions crates/ohno/examples/multiple_core_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions crates/ohno/tests/constructor_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
9 changes: 8 additions & 1 deletion crates/ohno/tests/debug_implementation_comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand All @@ -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);

Expand Down Expand Up @@ -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,
}

Expand Down
5 changes: 4 additions & 1 deletion crates/ohno/tests/derive_auto_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
12 changes: 8 additions & 4 deletions crates/ohno_macros/docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ 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` 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.
Expand Down Expand Up @@ -278,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 item that is **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.
`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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ pub(crate) fn generate(model: &Model) -> TokenStream {
let caused_by_body = construct(&model.shape, &initializers(model, &quote!(#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")]
Expand Down
4 changes: 1 addition & 3 deletions crates/ohno_macros_impl/src/derive_error/generate/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions crates/ohno_macros_impl/src/error_attr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,18 @@ fn inject_core(item: &mut ItemStruct) {
}
Fields::Unnamed(fields) => fields.unnamed.push(unnamed_core(&marker)),
Fields::Unit => {
// 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::default(),
paren_token: syn::token::Paren(span),
unnamed: syn::punctuated::Punctuated::new(),
Comment thread
Vaiz marked this conversation as resolved.
};
unnamed.unnamed.push(unnamed_core(&marker));
item.fields = Fields::Unnamed(unnamed);
item.semi_token = Some(<syn::Token![;]>::default());
item.semi_token = Some(syn::Token![;](span));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -47,7 +48,6 @@ impl ::core::fmt::Debug for T {
.finish()
}
}
#[automatically_derived]
impl T {
/// Creates the error with no source.
#[allow(
Expand Down Expand Up @@ -133,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")
Expand All @@ -141,7 +142,6 @@ impl ::core::fmt::Debug for T {
.finish()
}
}
#[automatically_derived]
impl T {
/// Creates the error with no source.
#[allow(
Expand Down Expand Up @@ -237,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")
Expand All @@ -245,7 +246,6 @@ impl ::core::fmt::Debug for T {
.finish()
}
}
#[automatically_derived]
impl T {
/// Creates the error with no source.
#[allow(
Expand Down Expand Up @@ -331,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")
Expand All @@ -339,7 +340,6 @@ impl ::core::fmt::Debug for T {
.finish()
}
}
#[automatically_derived]
impl T {
/// Creates the error with no source.
#[allow(
Expand Down Expand Up @@ -422,12 +422,12 @@ 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()
}
}
#[automatically_derived]
impl T {
/// Creates the error with no source.
#[allow(
Expand Down Expand Up @@ -505,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")
Expand All @@ -514,7 +515,6 @@ impl ::core::fmt::Debug for T {
.finish()
}
}
#[automatically_derived]
impl T {
/// Creates the error with no source.
#[allow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ 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()
}
}
#[automatically_derived]
impl T {
/// Creates the error with no source.
#[allow(
Expand Down Expand Up @@ -134,12 +134,12 @@ 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()
}
}
#[automatically_derived]
impl T {
/// Creates the error with no source.
#[allow(
Expand Down Expand Up @@ -223,12 +223,12 @@ 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()
}
}
#[automatically_derived]
impl T {
/// Creates the error with no source.
#[allow(
Expand Down
Loading
Loading