aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-21 19:16:10 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-21 19:16:31 +0800
commit1803dd427416ef19918c0ec7d50093b6d45f29af (patch)
tree4caef66649536886c27e83617066b25ad6c4ac3b
parent02823638a9b9c954e13b8ae7d29bd0ae98deaf51 (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.md35
-rw-r--r--GETTING_STARTED.md2
-rw-r--r--docs/_zh_CN/pages/13-hook.md2
-rw-r--r--docs/pages/13-hook.md2
-rw-r--r--examples/example-hook/src/main.rs2
-rw-r--r--mingling/src/example_docs.rs2
-rw-r--r--mingling_core/src/any.rs122
-rw-r--r--mingling_core/src/any/group.rs12
-rw-r--r--mingling_core/src/program/collection/mock.rs7
-rw-r--r--mingling_core/src/program/hook.rs12
-rw-r--r--mingling_macros/src/derive/grouped.rs12
-rw-r--r--mingling_macros/src/func/gen_program.rs22
-rw-r--r--mingling_macros/src/func/group.rs6
-rw-r--r--mingling_macros/src/func/pack.rs6
-rw-r--r--mingling_macros/src/systems/structural_data.rs12
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
}