From 194d00d5f75fbe75b1749991fc165d353670a341 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 19:28:44 +0000 Subject: [PATCH 1/6] Initial plan From c044f3324b3d16cb406fe7b14a66bf594dc604bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 19:31:59 +0000 Subject: [PATCH 2/6] Move other_attrs after allow attribute to prevent overwriting user attrs Co-authored-by: LuisFerLCC <81205298+LuisFerLCC@users.noreply.github.com> --- impl/src/types/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/impl/src/types/mod.rs b/impl/src/types/mod.rs index 07e7b99..172dec0 100644 --- a/impl/src/types/mod.rs +++ b/impl/src/types/mod.rs @@ -73,10 +73,9 @@ impl ToTokens for ErrorStackDeriveInput { .map(util::generic_reduced_to_ident) .collect(); - // TODO: move `other_attrs` down to avoid overwriting users' attrs tokens.extend(quote! { - #(#other_attrs)* #[allow(single_use_lifetimes)] + #(#other_attrs)* impl #generics ::core::fmt::Display for #ident #type_generics #where_clause { @@ -85,8 +84,8 @@ impl ToTokens for ErrorStackDeriveInput { } } - #(#other_attrs)* #[allow(single_use_lifetimes)] + #(#other_attrs)* impl #error_trait_generics ::core::error::Error for #ident #type_generics #where_clause { @@ -118,7 +117,7 @@ mod tests { let output = quote! { #input }; assert_eq!( output.to_string(), - "# [test_attribute] # [test_attribute_2] # [allow (single_use_lifetimes)] impl :: core :: fmt :: Display for CustomType { fn fmt (& self , f : & mut :: core :: fmt :: Formatter < '_ >) -> :: core :: fmt :: Result { :: core :: write ! (f , \"custom type\" ,) } } # [test_attribute] # [test_attribute_2] # [allow (single_use_lifetimes)] impl :: core :: error :: Error for CustomType { }" + "# [allow (single_use_lifetimes)] # [test_attribute] # [test_attribute_2] impl :: core :: fmt :: Display for CustomType { fn fmt (& self , f : & mut :: core :: fmt :: Formatter < '_ >) -> :: core :: fmt :: Result { :: core :: write ! (f , \"custom type\" ,) } } # [allow (single_use_lifetimes)] # [test_attribute] # [test_attribute_2] impl :: core :: error :: Error for CustomType { }" ); } From 175c5847f0bb0146776a10660a433fa46af95d3b Mon Sep 17 00:00:00 2001 From: LuisFerLCC Date: Wed, 31 Dec 2025 13:26:17 -0600 Subject: [PATCH 3/6] Change `#[expect(redundant-_lifetimes)]` FIXME comment: wait for the Rust team to fix it --- tests/src/structs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/structs.rs b/tests/src/structs.rs index 9f45926..3fc2f9f 100644 --- a/tests/src/structs.rs +++ b/tests/src/structs.rs @@ -126,7 +126,7 @@ mod tests { assert_eq!(test_val.to_string(), "inner = 8"); } - // FIXME: #[expect(redundant_lifetimes)] on type fails to remove warning + // TODO: move #[expect(redundant_lifetimes)] to type when fixed #[test] #[expect( redundant_lifetimes, From 8280c2c76bb74a40f02280488d8065efd9faee34 Mon Sep 17 00:00:00 2001 From: LuisFerLCC Date: Wed, 31 Dec 2025 15:02:52 -0600 Subject: [PATCH 4/6] Use `TypeData::EmptyEnum` (now `EmptyType`) for structs with never field --- impl/src/types/fmt/mod.rs | 40 +++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/impl/src/types/fmt/mod.rs b/impl/src/types/fmt/mod.rs index abf57e7..e5dbc08 100644 --- a/impl/src/types/fmt/mod.rs +++ b/impl/src/types/fmt/mod.rs @@ -4,7 +4,9 @@ use std::fmt::{self, Debug, Formatter}; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{ToTokens, quote}; -use syn::{Attribute, Data, Fields, Ident, LitStr, spanned::Spanned as _}; +use syn::{ + Attribute, Data, Fields, Ident, LitStr, Type, spanned::Spanned as _, +}; mod input; use input::{StructFormatInput, VariantFormatInput}; @@ -21,8 +23,7 @@ pub(crate) enum TypeData { variant_display_inputs: Vec, }, - // TODO: also use for structs with never (!) field - EmptyEnum, + EmptyType, } impl TypeData { @@ -34,8 +35,18 @@ impl TypeData { let default_display_attr = super::util::take_display_attr(attrs); match input_data { - Data::Struct(_) => { - drop(input_data); + Data::Struct(data) => { + let has_never_type = data + .fields + .iter() + .any(|field| matches!(field.ty, Type::Never(_))); + + drop(data); + + if has_never_type { + drop(default_display_attr); + return Ok(Self::EmptyType); + } let display_attr = default_display_attr .ok_or_else(|| syn::Error::new(ident_span, "missing `display` attribute for struct with `#[derive(Error)]`"))?; @@ -48,7 +59,7 @@ impl TypeData { let variants = data.variants; if variants.is_empty() { drop(variants); - return Ok(Self::EmptyEnum); + return Ok(Self::EmptyType); } let variant_display_inputs = @@ -159,10 +170,10 @@ impl ToTokens for TypeData { }); } - Self::EmptyEnum => { + Self::EmptyType => { // TODO: fully qualify macro path tokens.extend(quote! { - unreachable!("attempted to format an empty enum") + unreachable!("attempted to format an empty type") }); } } @@ -239,6 +250,19 @@ mod tests { use quote::quote; use syn::DeriveInput; + #[test] + fn empty_struct_works_without_display_attr() { + let derive_input: ErrorStackDeriveInput = + syn::parse2(quote! { struct EmptyStructType(!); }) + .expect("malformed test stream"); + + let output = quote! { #derive_input }; + assert_eq!( + output.to_string(), + "# [allow (single_use_lifetimes)] impl :: core :: fmt :: Display for EmptyStructType { fn fmt (& self , f : & mut :: core :: fmt :: Formatter < '_ >) -> :: core :: fmt :: Result { unreachable ! (\"attempted to format an empty type\") } } # [allow (single_use_lifetimes)] impl :: core :: error :: Error for EmptyStructType { }" + ); + } + #[test] fn struct_data_requires_display_attr() { let mut derive_input: DeriveInput = From f2cd956478a62b0807c09c7454e08da5ecbd3ed0 Mon Sep 17 00:00:00 2001 From: LuisFerLCC Date: Wed, 31 Dec 2025 15:30:00 -0600 Subject: [PATCH 5/6] Fully qualify `unreachable` macro path --- impl/src/types/fmt/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/impl/src/types/fmt/mod.rs b/impl/src/types/fmt/mod.rs index e5dbc08..a57307c 100644 --- a/impl/src/types/fmt/mod.rs +++ b/impl/src/types/fmt/mod.rs @@ -171,9 +171,8 @@ impl ToTokens for TypeData { } Self::EmptyType => { - // TODO: fully qualify macro path tokens.extend(quote! { - unreachable!("attempted to format an empty type") + ::core::unreachable!("attempted to format an empty type") }); } } @@ -259,7 +258,7 @@ mod tests { let output = quote! { #derive_input }; assert_eq!( output.to_string(), - "# [allow (single_use_lifetimes)] impl :: core :: fmt :: Display for EmptyStructType { fn fmt (& self , f : & mut :: core :: fmt :: Formatter < '_ >) -> :: core :: fmt :: Result { unreachable ! (\"attempted to format an empty type\") } } # [allow (single_use_lifetimes)] impl :: core :: error :: Error for EmptyStructType { }" + "# [allow (single_use_lifetimes)] impl :: core :: fmt :: Display for EmptyStructType { fn fmt (& self , f : & mut :: core :: fmt :: Formatter < '_ >) -> :: core :: fmt :: Result { :: core :: unreachable ! (\"attempted to format an empty type\") } } # [allow (single_use_lifetimes)] impl :: core :: error :: Error for EmptyStructType { }" ); } From 6327cb4e54123e11b99a009b2aceed93f738161b Mon Sep 17 00:00:00 2001 From: LuisFerLCC Date: Wed, 31 Dec 2025 15:34:19 -0600 Subject: [PATCH 6/6] Remove "Variant" from the names of variants of `EnumType` everywhere --- tests/src/enum_variants.rs | 29 ++++++++++++++--------------- tests/src/enums.rs | 2 +- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/src/enum_variants.rs b/tests/src/enum_variants.rs index dac46ec..e74253a 100644 --- a/tests/src/enum_variants.rs +++ b/tests/src/enum_variants.rs @@ -7,26 +7,25 @@ mod tests { #[derive(Debug, Error)] enum EnumType { #[display("unit variant")] - UnitVariant, + Unit, } - assert_eq!(EnumType::UnitVariant.to_string(), "unit variant"); + assert_eq!(EnumType::Unit.to_string(), "unit variant"); } #[test] fn named_field_variant_works_without_interpolation() { #[derive(Debug, Error)] enum EnumType { - // TODO: rename all variants in all `EnumType`s #[display("named field variant")] - NamedFieldVariant { + NamedFields { _length: usize, _is_ascii: bool, _inner: String, }, } - let test_val = EnumType::NamedFieldVariant { + let test_val = EnumType::NamedFields { _length: 5, _is_ascii: true, _inner: String::from("hello"), @@ -39,14 +38,14 @@ mod tests { #[derive(Debug, Error)] enum EnumType { #[display("named field variant: {inner:?} has {length} characters")] - NamedFieldVariant { + NamedFields { length: usize, _is_ascii: bool, inner: String, }, } - let test_val = EnumType::NamedFieldVariant { + let test_val = EnumType::NamedFields { length: 5, _is_ascii: true, inner: String::from("hello"), @@ -64,14 +63,14 @@ mod tests { #[display( "named field variant: {inner:?} has {length} characters and is ascii={is_ascii}" )] - NamedFieldVariant { + NamedFields { length: usize, is_ascii: bool, inner: String, }, } - let test_val = EnumType::NamedFieldVariant { + let test_val = EnumType::NamedFields { length: 5, is_ascii: true, inner: String::from("hello"), @@ -87,10 +86,10 @@ mod tests { #[derive(Debug, Error)] enum EnumType { #[display("tuple variant")] - TupleVariant(isize, isize, isize), + Tuple(isize, isize, isize), } - let test_val = EnumType::TupleVariant(5, 10, 15); + let test_val = EnumType::Tuple(5, 10, 15); assert_eq!(test_val.to_string(), "tuple variant"); } @@ -99,10 +98,10 @@ mod tests { #[derive(Debug, Error)] enum EnumType { #[display("tuple variant: point with y value {1}")] - TupleVariant(isize, isize, isize), + Tuple(isize, isize, isize), } - let test_val = EnumType::TupleVariant(5, 10, 15); + let test_val = EnumType::Tuple(5, 10, 15); assert_eq!( test_val.to_string(), "tuple variant: point with y value 10" @@ -116,10 +115,10 @@ mod tests { #[display( "tuple variant: point {2} units in front of the origin, and with x and y coords ({0}, {1})" )] - TupleVariant(isize, isize, isize), + Tuple(isize, isize, isize), } - let test_val = EnumType::TupleVariant(5, 10, 15); + let test_val = EnumType::Tuple(5, 10, 15); assert_eq!( test_val.to_string(), "tuple variant: point 15 units in front of the origin, and with x and y coords (5, 10)" diff --git a/tests/src/enums.rs b/tests/src/enums.rs index 32dc1a2..ef6d216 100644 --- a/tests/src/enums.rs +++ b/tests/src/enums.rs @@ -18,7 +18,7 @@ mod tests { #[test] #[expect( dead_code, - reason = "this test requires `EnumType::TupleVariant`'s fields to exist, even though they won't be read" + reason = "this test requires `EnumType::Tuple`'s fields to exist, even though they won't be read" )] fn enum_works_with_display_attr_default() { #[derive(Debug, Error)]