From ae8d75c0e1f1df8a6ca53a9adc5f19ff915729df Mon Sep 17 00:00:00 2001 From: RAprogramm Date: Sun, 12 Oct 2025 10:23:05 +0700 Subject: [PATCH 1/2] #173 fix: Rust 2024 edition compatibility for non_shorthand_field_patterns This commit resolves issue #173 by updating the macro code generation to use shorthand field pattern syntax required by Rust 2024 edition. Changes: - Modified error_trait.rs to generate shorthand patterns (field vs field: field) - Added deny directive for non_shorthand_field_patterns in error_derive test - Created new rust_2024_edition.rs test with comprehensive coverage - Fixed telemetry_flushes_after_subscriber_install test race condition All tests pass successfully with Rust 2024 edition. --- README.md | 1 - masterror-derive/src/error_trait.rs | 30 ++++++-- src/app_error/tests.rs | 26 +++---- tests/error_derive.rs | 3 +- tests/rust_2024_edition.rs | 106 ++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 22 deletions(-) create mode 100644 tests/rust_2024_edition.rs diff --git a/README.md b/README.md index e1eebd4..67f2210 100644 --- a/README.md +++ b/README.md @@ -459,4 +459,3 @@ assert_eq!(problem.grpc.expect("grpc").name, "UNAUTHENTICATED"); MSRV: **1.90** · License: **MIT OR Apache-2.0** · No `unsafe` - diff --git a/masterror-derive/src/error_trait.rs b/masterror-derive/src/error_trait.rs index 7b2fa4c..1875696 100644 --- a/masterror-derive/src/error_trait.rs +++ b/masterror-derive/src/error_trait.rs @@ -115,14 +115,14 @@ fn variant_transparent_source(variant: &VariantData) -> TokenStream { match &variant.fields { Fields::Unit => quote! { Self::#variant_ident => None }, Fields::Named(fields) => { - let binding = fields[0].ident.clone().expect("named field"); + let field_ident = fields[0].ident.clone().expect("named field"); let pattern = if fields.len() == 1 { - quote!(Self::#variant_ident { #binding }) + quote!(Self::#variant_ident { #field_ident }) } else { - quote!(Self::#variant_ident { #binding, .. }) + quote!(Self::#variant_ident { #field_ident, .. }) }; quote! { - #pattern => std::error::Error::source(#binding) + #pattern => std::error::Error::source(#field_ident) } } Fields::Unnamed(fields) => { @@ -162,7 +162,13 @@ fn variant_template_source(variant: &VariantData) -> TokenStream { (Fields::Named(fields), Some(field)) => { let field_ident = field.ident.clone().expect("named field"); let binding = binding_ident(field); - let pattern = if fields.len() == 1 { + let pattern = if field_ident == binding { + if fields.len() == 1 { + quote!(Self::#variant_ident { #field_ident }) + } else { + quote!(Self::#variant_ident { #field_ident, .. }) + } + } else if fields.len() == 1 { quote!(Self::#variant_ident { #field_ident: #binding }) } else { quote!(Self::#variant_ident { #field_ident: #binding, .. }) @@ -253,7 +259,13 @@ fn variant_backtrace_arm(variant: &VariantData) -> TokenStream { let field = backtrace.field(); let field_ident = field.ident.clone().expect("named field"); let binding = binding_ident(field); - let pattern = if fields.len() == 1 { + let pattern = if field_ident == binding { + if fields.len() == 1 { + quote!(Self::#variant_ident { #field_ident }) + } else { + quote!(Self::#variant_ident { #field_ident, .. }) + } + } else if fields.len() == 1 { quote!(Self::#variant_ident { #field_ident: #binding }) } else { quote!(Self::#variant_ident { #field_ident: #binding, .. }) @@ -488,7 +500,11 @@ fn variant_provide_named_arm( if needs_binding { let binding = binding_ident(field); let pattern_binding = binding.clone(); - entries.push(quote!(#ident: #pattern_binding)); + if ident == pattern_binding { + entries.push(quote!(#ident)); + } else { + entries.push(quote!(#ident: #pattern_binding)); + } if backtrace.is_some_and(|candidate| candidate.index() == field.index) { backtrace_binding = Some(binding.clone()); diff --git a/src/app_error/tests.rs b/src/app_error/tests.rs index 74348d4..7acb1d6 100644 --- a/src/app_error/tests.rs +++ b/src/app_error/tests.rs @@ -619,26 +619,26 @@ fn telemetry_flushes_after_subscriber_install() { let _guard = TELEMETRY_GUARD.lock().expect("telemetry guard"); use telemetry_support::new_recording_dispatch; - use tracing::dispatcher; - - let err = AppError::internal("boom"); + use tracing::{callsite::rebuild_interest_cache, dispatcher}; let (dispatch, events) = new_recording_dispatch(); let events = events.clone(); dispatcher::with_default(&dispatch, || { + rebuild_interest_cache(); + let err = AppError::internal("boom"); err.log(); - }); - let events = events.lock().expect("events lock"); - assert_eq!( - events.len(), - 1, - "expected telemetry after subscriber install" - ); - let event = &events[0]; - assert_eq!(event.code.as_deref(), Some(AppCode::Internal.as_str())); - assert_eq!(event.category.as_deref(), Some("Internal")); + let events = events.lock().expect("events lock"); + assert_eq!( + events.len(), + 1, + "expected telemetry after subscriber install" + ); + let event = &events[0]; + assert_eq!(event.code.as_deref(), Some(AppCode::Internal.as_str())); + assert_eq!(event.category.as_deref(), Some("Internal")); + }); } #[cfg(feature = "metrics")] diff --git a/tests/error_derive.rs b/tests/error_derive.rs index fcb44e1..978797a 100644 --- a/tests/error_derive.rs +++ b/tests/error_derive.rs @@ -1,4 +1,5 @@ -#![allow(unused_variables, non_shorthand_field_patterns)] +#![allow(unused_variables)] +#![deny(non_shorthand_field_patterns)] // SPDX-FileCopyrightText: 2025 RAprogramm // diff --git a/tests/rust_2024_edition.rs b/tests/rust_2024_edition.rs new file mode 100644 index 0000000..a3fb5db --- /dev/null +++ b/tests/rust_2024_edition.rs @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2025 RAprogramm +// +// SPDX-License-Identifier: MIT + +//! Test for Rust 2024 edition compatibility +//! +//! This test ensures that the macro-generated code does not trigger the +//! `non_shorthand_field_patterns` lint introduced in Rust 2024 edition. + +#![deny(non_shorthand_field_patterns)] + +use std::error::Error as StdError; + +use masterror::Error; + +#[derive(Debug, Error)] +#[error("parse error: {source}")] +pub struct ParseError { + #[source] + source: std::num::ParseIntError +} + +#[derive(Debug, Error)] +#[error("io error")] +pub struct IoError { + #[source] + source: std::io::Error +} + +#[derive(Debug, Error)] +pub enum AppError { + #[error("failed to parse: {source}")] + Parse { + #[source] + source: std::num::ParseIntError + }, + #[error("io failure: {source}")] + Io { + #[source] + source: std::io::Error + }, + #[error("network error: {0}")] + Network(#[source] std::io::Error), + #[error("unknown error")] + Unknown +} + +#[derive(Debug, Error)] +#[error("multi-field error: {message}, context: {context:?}")] +pub struct MultiFieldError { + message: String, + #[source] + source: std::io::Error, + context: Option +} + +#[derive(Debug, Error)] +pub enum ComplexError { + #[error("complex variant: {message}, code: {code}, caused by: {source}")] + Complex { + message: String, + #[source] + source: std::io::Error, + code: u16 + } +} + +#[test] +fn test_struct_with_source() { + let inner = "not a number".parse::().unwrap_err(); + let error = ParseError { + source: inner + }; + assert!(error.source().is_some()); +} + +#[test] +fn test_enum_with_source() { + let inner = "not a number".parse::().unwrap_err(); + let error = AppError::Parse { + source: inner + }; + assert!(error.source().is_some()); +} + +#[test] +fn test_multi_field_struct() { + let io_error = std::io::Error::other("test"); + let error = MultiFieldError { + message: "test message".to_string(), + source: io_error, + context: Some("additional context".to_string()) + }; + assert!(error.source().is_some()); +} + +#[test] +fn test_complex_enum_variant() { + let io_error = std::io::Error::other("test"); + let error = ComplexError::Complex { + message: "test".to_string(), + source: io_error, + code: 500 + }; + assert!(error.source().is_some()); +} From 696c53636b5347f2fdb965fc4e8511ec32e76007 Mon Sep 17 00:00:00 2001 From: RAprogramm Date: Sun, 12 Oct 2025 10:36:22 +0700 Subject: [PATCH 2/2] #173 chore: bump version to 0.24.19 and update changelog --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- README.md | 4 ++-- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f0a61..ded9e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,43 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.24.19] - 2025-10-12 + +### Fixed +- Updated macro code generation in `masterror-derive` to emit shorthand field + patterns (`field` instead of `field: field`) when the field name matches the + binding identifier, ensuring compatibility with Rust 2024 edition's + `non_shorthand_field_patterns` lint. +- Modified pattern generation in `error_trait.rs` for `source`, `backtrace`, + and `provide` implementations to conditionally use shorthand syntax, + eliminating redundant field-to-binding mappings that trigger warnings under + the new edition. +- Fixed race condition in `telemetry_flushes_after_subscriber_install` test by + moving error construction inside the dispatcher scope and calling + `rebuild_interest_cache()` before logging, ensuring the tracing subscriber + registers interest before event emission. + +### Added +- Comprehensive `rust_2024_edition` integration test suite covering struct and + enum error types with `#[source]` attributes, validating that generated code + passes under `#![deny(non_shorthand_field_patterns)]`. +- Deny directive `#![deny(non_shorthand_field_patterns)]` in existing + `error_derive` test to enforce compliance and prevent future regressions. + +### Changed +- Pattern generation logic now checks if field identifiers match binding names + before deciding between shorthand (`field`) and explicit (`field: binding`) + syntax, maintaining backward compatibility while adhering to Rust 2024 + edition requirements. + +### Why This Matters +Rust 2024 edition introduced the `non_shorthand_field_patterns` lint to +encourage cleaner, more idiomatic pattern matching. Without this fix, code +using `#[derive(Error)]` with `#[source]` attributes would trigger compiler +warnings (or errors with `-D warnings`) when upgrading to edition 2024, +breaking existing projects that rely on strict lint enforcement. This release +ensures seamless adoption of Rust 2024 edition for all `masterror` users. + ## [0.24.18] - 2025-10-09 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 662dde5..23e7f28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "masterror" -version = "0.24.18" +version = "0.24.19" rust-version = "1.90" edition = "2024" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index 67f2210..25b437f 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,9 @@ The build script keeps the full feature snippet below in sync with ~~~toml [dependencies] -masterror = { version = "0.24.18", default-features = false } +masterror = { version = "0.24.19", default-features = false } # or with features: -# masterror = { version = "0.24.18", features = [ +# masterror = { version = "0.24.19", features = [ # "std", "axum", "actix", "openapi", # "serde_json", "tracing", "metrics", "backtrace", # "sqlx", "sqlx-migrate", "reqwest", "redis",