From fbc75811b2afe253e9b2a841797da5207c6b8e7f Mon Sep 17 00:00:00 2001 From: MageSlayer Date: Sun, 1 Mar 2026 14:03:03 +0200 Subject: [PATCH 1/7] Propagate enum "repr(C)" into variant structs. --- src/lib.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 88addbc..1063b4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,7 +151,7 @@ extern crate alloc; extern crate proc_macro; -use alloc::vec::Vec; +use alloc::{string::ToString, vec::Vec}; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use proc_macro_roids::{namespace_parameters, FieldsExt}; @@ -187,9 +187,27 @@ fn enum_variant_type_impl(ast: DeriveInput) -> proc_macro2::TokenStream { let mut wrap_in_module = None::; let mut derive_for_all_variants = None::; let mut marker_trait_paths = Vec::::new(); + let mut repr_c = false; for attr in ast.attrs.iter() { - if attr.path.is_ident("evt") { + if attr.path.is_ident("repr") { + // wrap each enum struct in "repr(C)" ? + if let Ok(Meta::List(list)) = attr.parse_meta() { + for item in list.nested.iter() { + match item { + NestedMeta::Meta(Meta::Path(Path { segments, .. })) => { + segments.iter().for_each(|x| match x { + &syn::PathSegment { ref ident, .. } if ident.to_string() == "C" => { + repr_c = true; + } + _ => {} + }); + } + _ => {} + } + } + } + } else if attr.path.is_ident("evt") { if let Ok(Meta::List(list)) = attr.parse_meta() { for item in list.nested.iter() { match item { @@ -256,7 +274,7 @@ fn enum_variant_type_impl(ast: DeriveInput) -> proc_macro2::TokenStream { .collect::>(); let evt_meta_lists = namespace_parameters(&variant.attrs, &ns); - let variant_struct_attrs = evt_meta_lists + let mut variant_struct_attrs = evt_meta_lists .into_iter() .fold( proc_macro2::TokenStream::new(), @@ -265,6 +283,13 @@ fn enum_variant_type_impl(ast: DeriveInput) -> proc_macro2::TokenStream { attrs_tokens }, ); + + if repr_c { + variant_struct_attrs.extend(quote! { + #[repr(C)] + }) + } + let variant_fields = &variant.fields; // Need to attach visibility modifier to fields. @@ -683,4 +708,64 @@ mod tests { assert_eq!(expected_tokens.to_string(), actual_tokens.to_string()); } + + #[test] + fn derive_marker_repr() { + let ast: DeriveInput = parse_quote! { + #[derive(Debug)] + #[repr(C)] + pub enum MyEnum { + A { i: i64 }, + B { i: i64 }, + } + }; + + let actual_tokens = enum_variant_type_impl(ast); + let expected_tokens = quote! { + + #[repr(C)] + pub struct A { pub i: i64, } + + impl core::convert::From for MyEnum { + fn from(variant_struct: A) -> Self { + let A { i, } = variant_struct; + MyEnum::A { i, } + } + } + + impl core::convert::TryFrom for A { + type Error = MyEnum; + fn try_from(enum_variant: MyEnum) -> Result { + if let MyEnum::A { i, } = enum_variant { + core::result::Result::Ok(A { i, }) + } else { + core::result::Result::Err(enum_variant) + } + } + } + + #[repr(C)] + pub struct B { pub i: i64, } + + impl core::convert::From for MyEnum { + fn from(variant_struct: B) -> Self { + let B { i, } = variant_struct; + MyEnum::B { i, } + } + } + + impl core::convert::TryFrom for B { + type Error = MyEnum; + fn try_from(enum_variant: MyEnum) -> Result { + if let MyEnum::B { i, } = enum_variant { + core::result::Result::Ok(B { i, }) + } else { + core::result::Result::Err(enum_variant) + } + } + } + }; + + assert_eq!(expected_tokens.to_string(), actual_tokens.to_string()); + } } From 97534ff1667ab63a4655b28fdc45f850822c8c13 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Mon, 2 Mar 2026 19:33:52 +1300 Subject: [PATCH 2/7] Address clippy lints. --- Cargo.toml | 3 +++ src/lib.rs | 19 ++++++++----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b3aa518..57fb2d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,6 @@ syn = { version = "1.0.82", features = ["extra-traits", "visit"] } [dev-dependencies] pretty_assertions = "1.0.0" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(tarpaulin_include)"] } diff --git a/src/lib.rs b/src/lib.rs index 1063b4c..1fdb60a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,7 +151,7 @@ extern crate alloc; extern crate proc_macro; -use alloc::{string::ToString, vec::Vec}; +use alloc::vec::Vec; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use proc_macro_roids::{namespace_parameters, FieldsExt}; @@ -194,16 +194,13 @@ fn enum_variant_type_impl(ast: DeriveInput) -> proc_macro2::TokenStream { // wrap each enum struct in "repr(C)" ? if let Ok(Meta::List(list)) = attr.parse_meta() { for item in list.nested.iter() { - match item { - NestedMeta::Meta(Meta::Path(Path { segments, .. })) => { - segments.iter().for_each(|x| match x { - &syn::PathSegment { ref ident, .. } if ident.to_string() == "C" => { - repr_c = true; - } - _ => {} - }); - } - _ => {} + if let NestedMeta::Meta(Meta::Path(Path { segments, .. })) = item { + segments.iter().for_each(|x| match x { + syn::PathSegment { ident, .. } if *ident == "C" => { + repr_c = true; + } + _ => {} + }); } } } From b60112c07956e89c05b4ff49d1a3145c09dabf63 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Mon, 2 Mar 2026 19:36:36 +1300 Subject: [PATCH 3/7] Update dependency versions. --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57fb2d8..a93ef4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,13 +15,13 @@ path = "src/lib.rs" proc-macro = true [dependencies] -proc-macro2 = "1.0.34" -proc_macro_roids = "0.7.0" -quote = "1.0.10" -syn = { version = "1.0.82", features = ["extra-traits", "visit"] } +proc-macro2 = "1.0.106" +proc_macro_roids = "0.8.0" +quote = "1.0.44" +syn = { version = "2.0.117", features = ["extra-traits", "visit"] } [dev-dependencies] -pretty_assertions = "1.0.0" +pretty_assertions = "1.4.1" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ["cfg(tarpaulin_include)"] } From 82df73cd508e70b9bb11f8676b48b714b82d1c0e Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Mon, 2 Mar 2026 20:07:16 +1300 Subject: [PATCH 4/7] Updated code to be compatible with syn 2. --- src/lib.rs | 120 +++++++++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1fdb60a..19d08f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,8 +157,8 @@ use proc_macro2::{Ident, Span}; use proc_macro_roids::{namespace_parameters, FieldsExt}; use quote::quote; use syn::{ - parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields, Lit, - Meta, NestedMeta, Path, + parse_macro_input, parse_quote, Attribute, Data, DataEnum, DeriveInput, Field, Fields, LitStr, + Meta, Path, }; /// Attributes that should be copied across. @@ -190,63 +190,73 @@ fn enum_variant_type_impl(ast: DeriveInput) -> proc_macro2::TokenStream { let mut repr_c = false; for attr in ast.attrs.iter() { - if attr.path.is_ident("repr") { + if attr.path().is_ident("repr") { // wrap each enum struct in "repr(C)" ? - if let Ok(Meta::List(list)) = attr.parse_meta() { - for item in list.nested.iter() { - if let NestedMeta::Meta(Meta::Path(Path { segments, .. })) = item { - segments.iter().for_each(|x| match x { - syn::PathSegment { ident, .. } if *ident == "C" => { - repr_c = true; - } - _ => {} + if let Meta::List(list) = &attr.meta { + list.parse_nested_meta(|parse_nested_meta| { + if parse_nested_meta.path.is_ident("C") { + repr_c = true; + } + Ok(()) + }) + .unwrap_or_else(|e| panic!("Failed to parse repr attribute. Error: {}", e)); + } + } else if attr.path().is_ident("evt") { + if let Meta::List(list) = &attr.meta { + list.parse_nested_meta(|parse_nested_meta| { + if parse_nested_meta.path.is_ident("module") { + // `#[evt(module = \"some_module_name\")]` + let module_name: LitStr = parse_nested_meta + .value() + .and_then(|value| value.parse()) + .unwrap_or_else(|e| { + panic!( + "Expected `evt` attribute argument in the form: \ + `#[evt(module = \"some_module_name\")]`. Error: {}", + e + ) + }); + + wrap_in_module = Some(Ident::new(&module_name.value(), Span::call_site())); + return Ok(()); + } + // `#[evt(derive(Clone, Debug))]` + if parse_nested_meta.path.is_ident("derive") { + let mut items = Vec::new(); + list.parse_nested_meta(|parse_nested_meta| { + items.push(parse_nested_meta.path); + Ok(()) + })?; + + derive_for_all_variants = Some(parse_quote! { + #[derive( #(#items),* )] }); + return Ok(()); } - } - } - } else if attr.path.is_ident("evt") { - if let Ok(Meta::List(list)) = attr.parse_meta() { - for item in list.nested.iter() { - match item { - NestedMeta::Meta(Meta::NameValue(name_value)) => { - if let (true, Lit::Str(lit_str)) = - (name_value.path.is_ident("module"), &name_value.lit) - { - wrap_in_module = - Some(Ident::new(&lit_str.value(), Span::call_site())); - } else { - panic!("Expected `evt` attribute argument in the form: `#[evt(module = \"some_module_name\")]`"); - } - } - NestedMeta::Meta(Meta::List(list)) => { - if list.path.is_ident("derive") { - let items = list.nested.iter().map(|nested_meta| { - if let NestedMeta::Meta(Meta::Path(path)) = nested_meta { - path.clone() - } else { - panic!("Expected `evt` attribute argument in the form: `#[evt(derive(Clone, Debug))]`"); - } - }); - derive_for_all_variants = Some(parse_quote! { - #[derive( #(#items),* )] - }); - } else if list.path.is_ident("implement_marker_traits") { - marker_trait_paths = list.nested - .iter() - .map(|nested| if let NestedMeta::Meta(Meta::Path(path)) = nested { - path.clone() - } else { - panic!("Expected `evt` attribute argument in the form #[evt(implement_marker_traits(MarkerTrait1, MarkerTrait2))]"); - }).collect(); - } - } - _ => { - panic!("Unexpected usage of `evt` attribute, please see examples at:\n") - } + + // `#[evt(implement_marker_traits(MarkerTrait1, MarkerTrait2))]` + if parse_nested_meta.path.is_ident("implement_marker_traits") { + list.parse_nested_meta(|parse_nested_meta| { + marker_trait_paths.push(parse_nested_meta.path); + Ok(()) + })?; + + return Ok(()); } - } + + panic!( + "Unexpected usage of `evt` attribute, please see examples at:\n\ + " + ) + }) + .unwrap_or_else(|e| { + panic!("Failed to process evt attribute. Error: {}", e); + }); } else { - panic!("Unexpected usage of `evt` attribute, please see examples at:\n") + panic!( + "Unexpected usage of `evt` attribute, please see examples at:\ + \n" + ) } } } @@ -266,7 +276,7 @@ fn enum_variant_type_impl(ast: DeriveInput) -> proc_macro2::TokenStream { .filter(|attribute| { ATTRIBUTES_TO_COPY .iter() - .any(|attr_to_copy| attribute.path.is_ident(attr_to_copy)) + .any(|attr_to_copy| attribute.path().is_ident(attr_to_copy)) }) .collect::>(); From e9f216c270aa9577979fe8e35ec8266343e365d9 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Mon, 2 Mar 2026 20:07:55 +1300 Subject: [PATCH 5/7] Update `rustfmt.toml`. --- rustfmt.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustfmt.toml b/rustfmt.toml index e15e953..fe4970e 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,2 @@ format_code_in_doc_comments = true -merge_imports = true +imports_granularity = "crate" From 7a4e224c732909345d2ba4e617ec8b8ee1c08f83 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Mon, 2 Mar 2026 20:09:49 +1300 Subject: [PATCH 6/7] Update `CHANGELOG.md`. --- CHANGELOG.md | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b53b50..ba0c80f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,33 +1,46 @@ # Changelog +## unreleased + +* Propagate `#[repr(C)]` onto generated structs. ([#11][#11], [#12][#12]) +* Update to `syn` `2`. ([#12][#12]) + +[#11]: https://github.com/azriel91/enum_variant_type/issues/11 +[#12]: https://github.com/azriel91/enum_variant_type/pull/12 + + ## 0.3.1 (2021-12-22) -* Import all items from parent scope when generating structs in submodule. ([#9]) +* Import all items from parent scope when generating structs in submodule. ([#9][#9]) + +[#9]: https://github.com/azriel91/enum_variant_type/pull/9 -[#9]: https://github.com/azriel91/enum_variant_type/pulls/9 ## 0.3.0 (2021-12-18) -* `#[evt(derive(..))]` on enum adds derives on every variant. ([#6], [#7]) -* `#[evt(module = "module1")]` generates structs inside `mod module1`. ([#5], [#7]) -* `#[evt(implement_marker_traits(MarkerTrait1))]` on enum generates `impl MarkerTrait1` for all generated structs. ([#7]) +* `#[evt(derive(..))]` on enum adds derives on every variant. ([#6][#6], [#7][#7]) +* `#[evt(module = "module1")]` generates structs inside `mod module1`. ([#5][#5], [#7][#7]) +* `#[evt(implement_marker_traits(MarkerTrait1))]` on enum generates `impl MarkerTrait1` for all generated structs. ([#7][#7]) [#5]: https://github.com/azriel91/enum_variant_type/issues/5 [#6]: https://github.com/azriel91/enum_variant_type/issues/6 -[#7]: https://github.com/azriel91/enum_variant_type/pulls/7 +[#7]: https://github.com/azriel91/enum_variant_type/pull/7 + ## 0.2.1 (2021-04-24) -* `no-std` support by default. ([#2], [#3]) +* `no-std` support by default. ([#2][#2], [#3][#3]) [#2]: https://github.com/azriel91/enum_variant_type/issues/2 [#3]: https://github.com/azriel91/enum_variant_type/pull/3 + ## 0.2.0 (2020-01-13) * Allow variants to be skipped using `#[evt(skip)]`. * ***Breaking:*** `#[evt(..)]` specifies the attributes to attach to the generated type (previously `#[evt_attr(..)]`). + ## 0.1.0 (2020-01-10) * Generates unit, tuple, named struct for each enum variant. From 4585d20ee55f31c4065a8c9796826e7e50cf40f7 Mon Sep 17 00:00:00 2001 From: MageSlayer Date: Mon, 2 Mar 2026 12:41:09 +0200 Subject: [PATCH 7/7] Fix failing tests. --- src/lib.rs | 91 +++++++++++++++++++++++++----------------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 19d08f7..7b71407 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,62 +202,55 @@ fn enum_variant_type_impl(ast: DeriveInput) -> proc_macro2::TokenStream { .unwrap_or_else(|e| panic!("Failed to parse repr attribute. Error: {}", e)); } } else if attr.path().is_ident("evt") { - if let Meta::List(list) = &attr.meta { - list.parse_nested_meta(|parse_nested_meta| { - if parse_nested_meta.path.is_ident("module") { - // `#[evt(module = \"some_module_name\")]` - let module_name: LitStr = parse_nested_meta - .value() - .and_then(|value| value.parse()) - .unwrap_or_else(|e| { - panic!( - "Expected `evt` attribute argument in the form: \ + attr.parse_nested_meta(|nested_meta| { + if nested_meta.path.is_ident("module") { + // `#[evt(module = \"some_module_name\")]` + let module_name: LitStr = nested_meta + .value() + .and_then(|value| value.parse()) + .unwrap_or_else(|e| { + panic!( + "Expected `evt` attribute argument in the form: \ `#[evt(module = \"some_module_name\")]`. Error: {}", - e - ) - }); - - wrap_in_module = Some(Ident::new(&module_name.value(), Span::call_site())); - return Ok(()); - } - // `#[evt(derive(Clone, Debug))]` - if parse_nested_meta.path.is_ident("derive") { - let mut items = Vec::new(); - list.parse_nested_meta(|parse_nested_meta| { - items.push(parse_nested_meta.path); - Ok(()) - })?; - - derive_for_all_variants = Some(parse_quote! { - #[derive( #(#items),* )] + e + ) }); - return Ok(()); - } - // `#[evt(implement_marker_traits(MarkerTrait1, MarkerTrait2))]` - if parse_nested_meta.path.is_ident("implement_marker_traits") { - list.parse_nested_meta(|parse_nested_meta| { - marker_trait_paths.push(parse_nested_meta.path); - Ok(()) - })?; + wrap_in_module = Some(Ident::new(&module_name.value(), Span::call_site())); + return Ok(()); + } + // `#[evt(derive(Clone, Debug))]` + if nested_meta.path.is_ident("derive") { + let mut items = Vec::new(); + nested_meta.parse_nested_meta(|parse_nested_meta| { + items.push(parse_nested_meta.path); + Ok(()) + })?; + + derive_for_all_variants = Some(parse_quote! { + #[derive( #(#items),* )] + }); + return Ok(()); + } - return Ok(()); - } + // `#[evt(implement_marker_traits(MarkerTrait1, MarkerTrait2))]` + if nested_meta.path.is_ident("implement_marker_traits") { + nested_meta.parse_nested_meta(|parse_nested_meta| { + marker_trait_paths.push(parse_nested_meta.path); + Ok(()) + })?; + + return Ok(()); + } - panic!( - "Unexpected usage of `evt` attribute, please see examples at:\n\ - " - ) - }) - .unwrap_or_else(|e| { - panic!("Failed to process evt attribute. Error: {}", e); - }); - } else { panic!( - "Unexpected usage of `evt` attribute, please see examples at:\ - \n" + "Unexpected usage of `evt` attribute, please see examples at:\n\ + " ) - } + }) + .unwrap_or_else(|e| { + panic!("Failed to process evt attribute. Error: {}", e); + }); } }