aboutsummaryrefslogtreecommitdiff
path: root/mingling_core
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_core')
-rw-r--r--mingling_core/src/any.rs122
-rw-r--r--mingling_core/src/any/group.rs12
-rw-r--r--mingling_core/src/lib.rs8
-rw-r--r--mingling_core/src/program/collection/mock.rs7
-rw-r--r--mingling_core/src/program/hook.rs12
-rw-r--r--mingling_core/src/renderer/structural.rs16
-rw-r--r--mingling_core/src/renderer/structural/structural_data.rs8
-rw-r--r--mingling_core/tests/test-all/tests/integration.rs9
-rw-r--r--mingling_core/tests/test-structural-renderer/tests/integration.rs3
9 files changed, 170 insertions, 27 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/lib.rs b/mingling_core/src/lib.rs
index 2cb24b9..31476c9 100644
--- a/mingling_core/src/lib.rs
+++ b/mingling_core/src/lib.rs
@@ -90,8 +90,14 @@ pub mod setup {
/// Private API — not intended for direct use.
#[doc(hidden)]
pub mod __private {
+ use crate::ProgramCollect;
+
/// Sealed trait for `StructuralData` — only implementable via derive macro.
- pub trait StructuralDataSealed {}
+ pub trait StructuralDataSealed<C>
+ where
+ C: ProgramCollect<Enum = C>,
+ {
+ }
/// Re-export so the derive macro can reference the trait without
/// conflicting with the derive macro name at `::mingling::StructuralData`.
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_core/src/renderer/structural.rs b/mingling_core/src/renderer/structural.rs
index 30255aa..0449e72 100644
--- a/mingling_core/src/renderer/structural.rs
+++ b/mingling_core/src/renderer/structural.rs
@@ -1,5 +1,5 @@
use crate::{
- RenderResult, StructuralRendererSetting,
+ ProgramCollect, RenderResult, StructuralRendererSetting,
renderer::structural::error::StructuralRendererSerializeError,
};
use serde::Serialize;
@@ -24,11 +24,15 @@ impl StructuralRenderer {
///
/// Returns `Err(StructuralRendererSerializeError)` if serialization fails.
#[allow(unused_variables)]
- pub fn render<T: StructuralData + Send>(
+ pub fn render<T, C>(
data: &T,
setting: &StructuralRendererSetting,
r: &mut RenderResult,
- ) -> Result<(), StructuralRendererSerializeError> {
+ ) -> Result<(), StructuralRendererSerializeError>
+ where
+ T: StructuralData<C> + Send,
+ C: ProgramCollect<Enum = C>,
+ {
match setting {
StructuralRendererSetting::Disable => Ok(()),
#[cfg(feature = "json_serde_fmt")]
@@ -148,7 +152,7 @@ impl StructuralRenderer {
#[cfg(test)]
mod tests {
use super::*;
- use crate::RenderResult;
+ use crate::{MockProgramCollect, RenderResult};
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
@@ -157,8 +161,8 @@ mod tests {
value: i32,
}
- impl crate::__private::StructuralDataSealed for TestData {}
- impl StructuralData for TestData {}
+ impl crate::__private::StructuralDataSealed<MockProgramCollect> for TestData {}
+ impl StructuralData<MockProgramCollect> for TestData {}
fn test_data() -> TestData {
TestData {
diff --git a/mingling_core/src/renderer/structural/structural_data.rs b/mingling_core/src/renderer/structural/structural_data.rs
index 1cafac3..583808c 100644
--- a/mingling_core/src/renderer/structural/structural_data.rs
+++ b/mingling_core/src/renderer/structural/structural_data.rs
@@ -1,5 +1,7 @@
use serde::Serialize;
+use crate::ProgramCollect;
+
/// Marker trait for types that support structured output (JSON / YAML / TOML / RON).
///
/// This trait is a **supertrait** of `serde::Serialize` and is sealed via
@@ -12,4 +14,8 @@ use serde::Serialize;
/// These entry points also register the type in the global `STRUCTURED_TYPES`
/// registry, which is required for the `structural_render` match arm to be generated.
#[doc(hidden)]
-pub trait StructuralData: Serialize + crate::__private::StructuralDataSealed {}
+pub trait StructuralData<C>: Serialize + crate::__private::StructuralDataSealed<C>
+where
+ C: ProgramCollect<Enum = C>,
+{
+}
diff --git a/mingling_core/tests/test-all/tests/integration.rs b/mingling_core/tests/test-all/tests/integration.rs
index 08703b0..d36b9df 100644
--- a/mingling_core/tests/test-all/tests/integration.rs
+++ b/mingling_core/tests/test-all/tests/integration.rs
@@ -1,5 +1,4 @@
use mingling::Flag;
-use mingling::MockProgramCollect;
use mingling::NextProcess;
use mingling::Node;
use mingling::Program;
@@ -125,13 +124,13 @@ fn test_structural_renderer_json() {
#[test]
fn test_is_completing() {
- let program: Program<MockProgramCollect> = Program::new_with_args(["app", "__comp"]);
+ let program: Program<crate::ThisProgram> = Program::new_with_args(["app", "__comp"]);
assert!(program.is_completing());
}
#[test]
fn test_is_not_completing() {
- let program: Program<MockProgramCollect> = Program::new_with_args(["app", "greet"]);
+ let program: Program<crate::ThisProgram> = Program::new_with_args(["app", "greet"]);
assert!(!program.is_completing());
}
@@ -141,7 +140,7 @@ fn test_is_not_completing() {
fn test_hook_setup() {
static CALLED: AtomicBool = AtomicBool::new(false);
- let hook = ProgramHook::<MockProgramCollect>::empty().on_begin::<_, ()>(|_| {
+ let hook = ProgramHook::<crate::ThisProgram>::empty().on_begin::<_, ()>(|_| {
CALLED.store(true, Ordering::SeqCst);
});
@@ -166,3 +165,5 @@ fn test_string_vec_from_array() {
let v: Vec<String> = sv.into();
assert_eq!(v, vec!["a", "b", "c"]);
}
+
+mingling::macros::gen_program!();
diff --git a/mingling_core/tests/test-structural-renderer/tests/integration.rs b/mingling_core/tests/test-structural-renderer/tests/integration.rs
index 0bcf53a..e4057f8 100644
--- a/mingling_core/tests/test-structural-renderer/tests/integration.rs
+++ b/mingling_core/tests/test-structural-renderer/tests/integration.rs
@@ -74,3 +74,6 @@ fn test_render_ron() {
assert!(output.contains("value:"));
assert!(output.contains("42"));
}
+
+mingling::macros::gen_program!();
+