aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_core/src')
-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
4 files changed, 138 insertions, 15 deletions
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
}