Skip to content

Add transparent error support to derive macro#34

Merged
RAprogramm merged 1 commit intonew_versionfrom
codex/implement-error-transparent-attribute-in-derive-macro
Sep 17, 2025
Merged

Add transparent error support to derive macro#34
RAprogramm merged 1 commit intonew_versionfrom
codex/implement-error-transparent-attribute-in-derive-macro

Conversation

@RAprogramm
Copy link
Owner

Summary

  • add support for #[error(transparent)] in the derive macro with validation of wrapper shape
  • delegate Display/source and From generation for transparent errors and update docs
  • cover transparent success and failure scenarios with unit tests and trybuild UI cases

Testing

  • cargo +nightly fmt --
  • cargo clippy -- -D warnings
  • cargo build --all-targets
  • cargo test --all
  • cargo doc --no-deps

https://chatgpt.com/codex/tasks/task_e_68ca3690e7d0832b8c92ead521e47d24

@RAprogramm RAprogramm merged commit 3bf3eff into new_version Sep 17, 2025
1 of 2 checks passed
@RAprogramm RAprogramm deleted the codex/implement-error-transparent-attribute-in-derive-macro branch September 17, 2025 04:34
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

Codex Review: Here are some suggestions.

fn build_struct_error(
ident: &Ident,
generics: &Generics,
fields: &ParsedFields,
display: &DisplayAttribute
) -> syn::Result<TokenStream2> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let body = match display {
DisplayAttribute::Transparent(attr) => {
let context = format!("struct `{ident}`");
let field = ensure_transparent_field(fields, attr, &context)?;
let member = &field.member;
let binding = format_ident!("__masterror_transparent_inner");
quote! {
let #binding = &self.#member;
let _: &(dyn ::std::error::Error + 'static) = #binding;
::std::error::Error::source(#binding)
}

[P1] Transparent struct omits wrapped error from source chain

When deriving a transparent struct, source() calls ::std::error::Error::source on the inner value instead of returning the inner error itself. For a wrapper like #[error(transparent)] struct Wrapper(#[from] std::io::Error);, this implementation yields None because io::Error often has no deeper source, so the original io::Error is no longer reachable through the error chain and cannot be downcast after boxing. Transparent wrappers should expose the wrapped error as the direct source (Some(&self.0)) to preserve the chain.


fn build_enum_error(
ident: &Ident,
generics: &Generics,
variants: &[VariantInfo]
) -> syn::Result<TokenStream2> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut arms = Vec::with_capacity(variants.len());
for variant in variants {
let variant_ident = &variant.ident;
let arm = match &variant.display {
DisplayAttribute::Transparent(attr) => {
let context = format!("variant `{variant_ident}`");
let field = ensure_transparent_field(&variant.fields, attr, &context)?;
match variant.fields.style {
FieldsStyle::Named => {
let field_ident = field.ident.clone().ok_or_else(|| {
syn::Error::new(attr.span, "named field missing identifier")
})?;
let binding = format_ident!("__masterror_transparent_inner");
quote! {
Self::#variant_ident { #field_ident } => {
let #binding = #field_ident;
let _: &(dyn ::std::error::Error + 'static) = #binding;
::std::error::Error::source(#binding)
}
}
}
FieldsStyle::Unnamed => {
let binding = field.binding.clone();
let inner = format_ident!("__masterror_transparent_inner");
quote! {
Self::#variant_ident( #binding ) => {
let #inner = #binding;
let _: &(dyn ::std::error::Error + 'static) = #inner;
::std::error::Error::source(#inner)
}

[P1] Transparent enum variants skip the wrapped error in source()

The source() arm generated for transparent enum variants delegates to the inner error’s own source() rather than returning the inner error itself. As with structs, this causes the wrapped error to disappear from the chain: TransparentEnum::TransparentVariant(std::io::Error::new(...)) reports no source, so callers cannot recover or downcast the actual inner error once it is boxed. The arm should return Some(inner) to keep the wrapper transparent without losing the immediate cause.


Reply with @codex fix comments to fix any unresolved comments.

About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant