diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-21 19:16:10 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-21 19:16:31 +0800 |
| commit | 1803dd427416ef19918c0ec7d50093b6d45f29af (patch) | |
| tree | 4caef66649536886c27e83617066b25ad6c4ac3b | |
| parent | 02823638a9b9c954e13b8ae7d29bd0ae98deaf51 (diff) | |
feat(any): make `Grouped` trait unsafe and encapsulate `AnyOutput`
fields
Declare `Grouped` as `unsafe trait` to make its soundness invariant
visible.
Make `AnyOutput.type_id` and `member_id` private; add public accessors
and
the `unsafe fn new_bare` constructor.
| -rw-r--r-- | CHANGELOG.md | 35 | ||||
| -rw-r--r-- | GETTING_STARTED.md | 2 | ||||
| -rw-r--r-- | docs/_zh_CN/pages/13-hook.md | 2 | ||||
| -rw-r--r-- | docs/pages/13-hook.md | 2 | ||||
| -rw-r--r-- | examples/example-hook/src/main.rs | 2 | ||||
| -rw-r--r-- | mingling/src/example_docs.rs | 2 | ||||
| -rw-r--r-- | mingling_core/src/any.rs | 122 | ||||
| -rw-r--r-- | mingling_core/src/any/group.rs | 12 | ||||
| -rw-r--r-- | mingling_core/src/program/collection/mock.rs | 7 | ||||
| -rw-r--r-- | mingling_core/src/program/hook.rs | 12 | ||||
| -rw-r--r-- | mingling_macros/src/derive/grouped.rs | 12 | ||||
| -rw-r--r-- | mingling_macros/src/func/gen_program.rs | 22 | ||||
| -rw-r--r-- | mingling_macros/src/func/group.rs | 6 | ||||
| -rw-r--r-- | mingling_macros/src/func/pack.rs | 6 | ||||
| -rw-r--r-- | mingling_macros/src/systems/structural_data.rs | 12 |
15 files changed, 219 insertions, 37 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d7b8bb..86584cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -566,6 +566,41 @@ None All examples and internal usages have been updated across the codebase to reflect these changes (e.g., `repl_basic_setup` now calls `println!("{}", r.result)` instead of `println!("{}", r.result.trim())`, since `Display` no longer adds a trailing newline). +7. **[`any`]** **[`macros`]** Made `AnyOutput`'s `type_id` and `member_id` fields private (`pub(crate)`) and added public accessor methods `type_id()` and `member_id()`. Added the `unsafe fn new_bare<T>(value: T, member_id: G) -> Self` constructor that bypasses the `Grouped` trait, allowing manual specification of `member_id` without requiring the concrete type to implement `Grouped`. + + - **`type_id`** field changed from `pub` to `pub(crate)` — accessible via `type_id()` accessor. + - **`member_id`** field changed from `pub` to `pub(crate)` — accessible via `member_id()` accessor (requires `G: Copy`). + - **`new_bare`** — Unsafe constructor that takes a raw `member_id` value without invoking `Grouped::member_id()`. The caller must ensure the provided `member_id` correctly corresponds to the concrete type `T`. + - Updated all internal `match any.member_id { ... }` patterns in `gen_program.rs` to use `match any.member_id() { ... }` instead. + - Updated the panic message in `do_chain` (both sync and async) from `any.type_id` to `any.type_id()`. + - Updated the example-hook `main.rs` to call `info.output.member_id()` instead of accessing `info.output.member_id` directly. + - Added `Copy` derive to the generated enum to enable `member_id()`'s `Copy` requirement on the enum type. + + _No behavioral changes for existing code — the accessor methods provide the same values as the previously-public fields._ + +8. **[`any`]** **[`macros`]** **[BREAKING]** Marked `Grouped` trait as `unsafe trait`. The `Grouped` trait has always been inherently unsafe — the `member_id()` return value must exactly correspond to the variant registered by `register_type!` for the concrete type, otherwise dispatching on that type will result in **undefined behavior**. This unsoundness has existed since the trait's inception but was previously unenforced at the type system level. + + By making `Grouped` an `unsafe trait`, implementors must now explicitly acknowledge this safety contract with `unsafe impl Grouped<...> for ...`. This change makes the existing safety invariant visible to developers and enables soundness warnings at compile time. + + **Changes made:** + + - **`Grouped` trait** in `mingling_core/src/any/group.rs` changed from `pub trait Grouped<Group>` to `pub unsafe trait Grouped<Group>`, with a safety doc comment explaining that manually implementing the trait with an incorrect `member_id` leads to undefined behavior. + + - **Derive macros** (`#[derive(Grouped)]`, `#[derive(GroupedSerialize)]`) now generate `unsafe impl` instead of `impl`, with a SAFETY comment stating that the derive macro guarantees correctness because the `Ident` used in `register_type!` matches the `Ident` returned by `member_id()`. + + - **`pack!`, `pack_structural!`, `group!`, `group_structural!`** macros now generate `unsafe impl` instead of `impl`, with analogous SAFETY comments. + + - **All manual test implementations** of `Grouped` across the codebase (in `any.rs` tests, `hook.rs` tests, `mock.rs`) updated to `unsafe impl` with SAFETY comments explaining why they are safe in their test contexts. + + - **`MockProgramCollect::member_id()`** changed from `MockProgramCollect::Foo` to `panic!("Attempting to read an unsafe enum type")` to prevent accidental execution in production paths. + + **Migration guide:** + + - Existing code that uses `Grouped` only through the derive macro or `pack!`/`group!` macros is automatically migrated — no changes needed. + - Code with **manual** `impl Grouped<...> for ...` blocks must add `unsafe` before `impl` and verify that the `member_id()` return value correctly corresponds to the type's registered variant. Only proceed if the correspondence is guaranteed. + + _This is a breaking change only for code with manual `Grouped` implementations._ + --- ## Release 0.2.2 (2026-07-10) diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 1aad8d5..621471e 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -551,7 +551,7 @@ fn main() { .on_pre_chain(|info| { println!("[DEBUG] Pre chain: {}", info.input); }) - .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id)) + .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id())) .on_finish(|_| { println!("[DEBUG] Loop end"); ProgramControlUnit::OverrideExitCode(0) // Override exit code diff --git a/docs/_zh_CN/pages/13-hook.md b/docs/_zh_CN/pages/13-hook.md index 6d6018a..ad3008a 100644 --- a/docs/_zh_CN/pages/13-hook.md +++ b/docs/_zh_CN/pages/13-hook.md @@ -71,7 +71,7 @@ fn main() { eprintln!("[hook] executing chain for: {}", info.input); }) .on_post_chain(|info| { - eprintln!("[hook] chain output: {}", info.output.member_id); + eprintln!("[hook] chain output: {}", info.output.member_id()); }), ); diff --git a/docs/pages/13-hook.md b/docs/pages/13-hook.md index 26f8712..d927a9c 100644 --- a/docs/pages/13-hook.md +++ b/docs/pages/13-hook.md @@ -71,7 +71,7 @@ fn main() { eprintln!("[hook] executing chain for: {}", info.input); }) .on_post_chain(|info| { - eprintln!("[hook] chain output: {}", info.output.member_id); + eprintln!("[hook] chain output: {}", info.output.member_id()); }), ); diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs index 23b87c7..da92045 100644 --- a/examples/example-hook/src/main.rs +++ b/examples/example-hook/src/main.rs @@ -40,7 +40,7 @@ fn main() { .on_pre_chain(|info| { println!("[DEBUG] Pre chain: {}", info.input); }) - .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id)) + .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id())) .on_finish(|_| { println!("[DEBUG] Loop end"); ProgramControlUnit::OverrideExitCode(0) // Override exit code diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index 3b36f00..89d5af1 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -1649,7 +1649,7 @@ pub mod example_help {} /// .on_pre_chain(|info| { /// println!("[DEBUG] Pre chain: {}", info.input); /// }) -/// .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id)) +/// .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id())) /// .on_finish(|_| { /// println!("[DEBUG] Loop end"); /// ProgramControlUnit::OverrideExitCode(0) // Override exit code diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs index ec29a1b..3e8fdf0 100644 --- a/mingling_core/src/any.rs +++ b/mingling_core/src/any.rs @@ -22,14 +22,14 @@ pub struct AnyOutput<G> { /// /// This is set during construction and used for type-checking /// in downcast, restore, and is methods. - pub type_id: std::any::TypeId, + pub(crate) type_id: std::any::TypeId, /// The variant identifier returned by [`Grouped::member_id`] for the /// concrete type stored in `inner`. /// /// This is used by the scheduler to dispatch on the correct enum /// variant when routing the output. - pub member_id: G, + pub(crate) member_id: G, } impl<G> AnyOutput<G> { @@ -45,6 +45,52 @@ impl<G> AnyOutput<G> { } } + /// Create an `AnyOutput` from a raw value with a manually specified member_id. + /// + /// This function bypasses the [`Grouped`] trait, meaning the `member_id` you provide + /// does **not** have to match the actual concrete type `T`. The scheduler uses + /// `member_id` to determine which enum variant the output belongs to, and later + /// attempts to restore the value to the concrete type `T` based on that variant. + /// + /// # Safety + /// + /// - The caller must ensure that `member_id` correctly corresponds to the concrete + /// type `T` according to the scheduling logic. If `member_id` does not match, + /// calling [`restore`](Self::restore) or [`downcast`](Self::downcast) with the + /// type associated with `member_id` will cause **undefined behavior**. + /// - This safety contract is the caller's responsibility; the compiler cannot + /// enforce the correspondence between `member_id` and the stored type. + pub unsafe fn new_bare<T>(value: T, member_id: G) -> Self + where + T: Send + 'static, + { + Self { + inner: Box::new(value), + type_id: std::any::TypeId::of::<T>(), + member_id, + } + } + + /// Get the [`TypeId`] of the concrete type stored in `inner`. + /// + /// The `TypeId` is set during construction (via [`AnyOutput::new`] or [`AnyOutput::new_bare`]) + /// and is used for subsequent downcasting and type checking. + pub fn type_id(&self) -> std::any::TypeId { + self.type_id + } + + /// Get the [`member_id`] of the concrete type stored in `inner`. + /// + /// [`member_id`] is set during construction (via [`AnyOutput::new`] or [`AnyOutput::new_bare`]) + /// and identifies which variant of the output enum this value corresponds to. + /// The scheduler uses this value to dispatch the output to the correct next step. + pub fn member_id(&self) -> G + where + G: Copy, + { + self.member_id + } + /// Attempt to downcast the `AnyOutput` to a concrete type. /// /// # Errors @@ -190,7 +236,17 @@ mod tests { value: i32, } - impl Grouped<MockGroup> for AlphaData { + /// # Safety + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for AlphaData { fn member_id() -> MockGroup { MockGroup::Alpha } @@ -202,7 +258,17 @@ mod tests { name: String, } - impl Grouped<MockGroup> for BetaData { + /// # Safety + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for BetaData { fn member_id() -> MockGroup { MockGroup::Beta } @@ -213,7 +279,17 @@ mod tests { #[cfg_attr(feature = "structural_renderer", derive(serde::Serialize))] struct GammaData; - impl Grouped<MockGroup> for GammaData { + /// # Safety + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for GammaData { fn member_id() -> MockGroup { MockGroup::Gamma } @@ -369,7 +445,17 @@ mod tests { x: i32, } - impl Grouped<MockGroup> for SerData { + /// SAFETY: + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for SerData { fn member_id() -> MockGroup { MockGroup::Gamma } @@ -396,13 +482,33 @@ mod tests { b: String, } - impl Grouped<MockGroup> for SerA { + /// SAFETY: + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for SerA { fn member_id() -> MockGroup { MockGroup::Alpha } } - impl Grouped<MockGroup> for SerB { + /// SAFETY: + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for SerB { fn member_id() -> MockGroup { MockGroup::Beta } diff --git a/mingling_core/src/any/group.rs b/mingling_core/src/any/group.rs index 2813ad5..5e5e347 100644 --- a/mingling_core/src/any/group.rs +++ b/mingling_core/src/any/group.rs @@ -2,10 +2,14 @@ use crate::{AnyOutput, ChainProcess, ProgramCollect, Routable}; /// Used to mark a type with a unique enum ID, assisting dynamic dispatch /// -/// **Note:** Unlike earlier versions, `Grouped` no longer requires `Serialize` -/// even when the `structural_renderer` feature is enabled. Structured output is -/// controlled separately via the \[`StructuralData`\] trait. -pub trait Grouped<Group> +/// # Safety +/// +/// The returned `Group` value is an enum variant created by `register_type!` when +/// registering the type's ID. Whether the variant matches correctly is guaranteed +/// by `Grouped derive` or macros like `pack!`. If implemented manually, and the +/// type name written in `member_id()` does not match the actually registered type, +/// dispatching to that type will result in **100% undefined behavior**. +pub unsafe trait Grouped<Group> where Self: Sized + 'static, { diff --git a/mingling_core/src/program/collection/mock.rs b/mingling_core/src/program/collection/mock.rs index 5847f10..dbe4789 100644 --- a/mingling_core/src/program/collection/mock.rs +++ b/mingling_core/src/program/collection/mock.rs @@ -23,9 +23,12 @@ pub enum MockProgramCollect { Bar, } -impl Grouped<MockProgramCollect> for MockProgramCollect { +/// SAFETY: This is a mock type used only for temporary testing. +/// It will never actually enter the macro system. +/// The internal `panic!` ensures that `member_id` will never be executed. +unsafe impl Grouped<MockProgramCollect> for MockProgramCollect { fn member_id() -> MockProgramCollect { - MockProgramCollect::Foo + panic!("Attempting to read an unsafe enum type"); } } diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs index 48f632f..7d94a21 100644 --- a/mingling_core/src/program/hook.rs +++ b/mingling_core/src/program/hook.rs @@ -695,7 +695,17 @@ mod tests { } } - impl Grouped<MockHookEnum> for MockHookEnum { + /// SAFETY: + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockHookEnum> for MockHookEnum { fn member_id() -> MockHookEnum { MockHookEnum::A } diff --git a/mingling_macros/src/derive/grouped.rs b/mingling_macros/src/derive/grouped.rs index 307aab6..a00eea1 100644 --- a/mingling_macros/src/derive/grouped.rs +++ b/mingling_macros/src/derive/grouped.rs @@ -16,7 +16,11 @@ pub(crate) fn derive_grouped(input: TokenStream) -> TokenStream { let expanded = quote! { ::mingling::macros::register_type!(#struct_name); - impl ::mingling::Grouped<#group_ident> for #struct_name { + /// SAFETY: This is an internal implementation of the `Grouped` derive macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<#group_ident> for #struct_name { fn member_id() -> #group_ident { #group_ident::#struct_name } @@ -46,7 +50,11 @@ pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream { ::mingling::macros::register_type!(#struct_name); - impl ::mingling::Grouped<#group_ident> for #struct_name { + /// SAFETY: This is an internal implementation of the `Grouped` derive macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<#group_ident> for #struct_name { fn member_id() -> #group_ident { #group_ident::#struct_name } diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs index de4da06..db82504 100644 --- a/mingling_macros/src/func/gen_program.rs +++ b/mingling_macros/src/func/gen_program.rs @@ -342,7 +342,7 @@ fn main() { ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> { #[allow(unused_imports)] #(#pathf_uses)* - match any.member_id { + match any.member_id() { #(#structural_renderer_tokens)* _ => { // Non-structural types: render ResultEmpty (which implements @@ -408,7 +408,7 @@ fn main() { fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { #[allow(unused_imports)] #(#pathf_uses)* - match any.member_id { + match any.member_id() { #(#completion_tokens)* _ => ::mingling::Suggest::FileCompletion, } @@ -442,7 +442,7 @@ fn main() { }).collect(); quote! { fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - match any.member_id { + match any.member_id() { #(#render_arms)* _ => ::mingling::RenderResult::default(), } @@ -494,9 +494,9 @@ fn main() { fn do_chain( any: ::mingling::AnyOutput<Self::Enum>, ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::mingling::ChainProcess<Self::Enum>> + ::std::marker::Send>> { - match any.member_id { + match any.member_id() { #(#chain_arms_async)* - _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), + _ => ::core::panic!("No chain found for type id: {:?}", any.type_id()), } } } @@ -505,9 +505,9 @@ fn main() { fn do_chain( any: ::mingling::AnyOutput<Self::Enum>, ) -> ::mingling::ChainProcess<Self::Enum> { - match any.member_id { + match any.member_id() { #(#chain_arms_sync)* - _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), + _ => ::core::panic!("No chain found for type id: {:?}", any.type_id()), } } } @@ -535,7 +535,7 @@ fn main() { let expanded = quote! { #pathf_hint - #[derive(Debug, PartialEq, Eq, Clone)] + #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(#repr_type)] #[allow(nonstandard_style)] pub enum #name { @@ -569,19 +569,19 @@ fn main() { fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { #[allow(unused_imports)] #(#pathf_uses)* - match any.member_id { + match any.member_id() { #(#help_tokens)* _ => ::mingling::RenderResult::default(), } } fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { + match any.member_id() { #(#renderer_exist_tokens)* _ => false } } fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { + match any.member_id() { #(#chain_exist_tokens)* _ => false } diff --git a/mingling_macros/src/func/group.rs b/mingling_macros/src/func/group.rs index b865913..edb1fe1 100644 --- a/mingling_macros/src/func/group.rs +++ b/mingling_macros/src/func/group.rs @@ -133,7 +133,11 @@ pub(crate) fn group_macro(input: TokenStream) -> TokenStream { #type_use #alias_use - impl ::mingling::Grouped<__MinglingProgram> for #type_name { + /// SAFETY: This is an internal implementation of the `group!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<__MinglingProgram> for #type_name { fn member_id() -> __MinglingProgram { __MinglingProgram::#type_name } diff --git a/mingling_macros/src/func/pack.rs b/mingling_macros/src/func/pack.rs index a1a7e6b..7206b8e 100644 --- a/mingling_macros/src/func/pack.rs +++ b/mingling_macros/src/func/pack.rs @@ -138,7 +138,11 @@ pub(crate) fn pack(input: TokenStream) -> TokenStream { } } - impl ::mingling::Grouped<#group_name> for #type_name { + /// SAFETY: This is an internal implementation of the `pack!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<#group_name> for #type_name { fn member_id() -> #group_name { #group_name::#type_name } diff --git a/mingling_macros/src/systems/structural_data.rs b/mingling_macros/src/systems/structural_data.rs index 0a783ec..46d7cf8 100644 --- a/mingling_macros/src/systems/structural_data.rs +++ b/mingling_macros/src/systems/structural_data.rs @@ -176,7 +176,11 @@ pub(crate) fn pack_structural(input: TokenStream) -> TokenStream { } } - impl ::mingling::Grouped<#program_path> for #type_name { + /// SAFETY: This is an internal implementation of the `pack_structural!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<#program_path> for #type_name { fn member_id() -> #program_path { #program_path::#type_name } @@ -297,7 +301,11 @@ pub(crate) fn group_structural(input: TokenStream) -> TokenStream { #type_use #alias_use - impl ::mingling::Grouped<__MinglingProgram> for #type_name { + /// SAFETY: This is an internal implementation of the `pack_structural!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<__MinglingProgram> for #type_name { fn member_id() -> __MinglingProgram { __MinglingProgram::#type_name } |
