diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-21 05:49:00 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-21 05:49:00 +0800 |
| commit | 700c049f64b66f424cda5da3021dfce4462655ca (patch) | |
| tree | b4fd21e5e6f82be29b18c47d8617814b28c159a6 | |
| parent | 2097238b57810d78989d3ecab8bc97ce1b8a6aa1 (diff) | |
feat(core): add generic parameter `C` to `StructuralData` trait
Make `StructuralData` and `StructuralDataSealed` generic over a program
collector type to bypass the orphan rule for `group_structural!`. This
enables external types to implement `StructuralData<crate::ThisProgram>`
without violating coherence.
| -rw-r--r-- | CHANGELOG.md | 20 | ||||
| -rw-r--r-- | mingling_core/src/lib.rs | 8 | ||||
| -rw-r--r-- | mingling_core/src/renderer/structural.rs | 16 | ||||
| -rw-r--r-- | mingling_core/src/renderer/structural/structural_data.rs | 8 | ||||
| -rw-r--r-- | mingling_core/tests/test-all/tests/integration.rs | 9 | ||||
| -rw-r--r-- | mingling_core/tests/test-structural-renderer/tests/integration.rs | 3 | ||||
| -rw-r--r-- | mingling_macros/src/func/pack_err.rs | 4 | ||||
| -rw-r--r-- | mingling_macros/src/systems/structural_data.rs | 12 |
8 files changed, 60 insertions, 20 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9982cc0..8b81e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,26 @@ None _No behavioral changes — all existing functionality is preserved. Downstream code that ignores the return value continues to work without modification._ +6. **[`core`]** **`StructuralData` trait now takes a generic parameter `C`.** The `StructuralData` trait and its sealed supertrait `StructuralDataSealed` (both under `::mingling::__private`) have been made generic over a program collector type `C: ProgramCollect<Enum = C>`. This change is necessary for `group_structural!` to bypass the orphan rule — by tying `StructuralData<C>` to `crate::ThisProgram` (which is defined in the user's crate), external types can implement `StructuralData<crate::ThisProgram>` without violating coherence. + + **Migration guide (only relevant for manual `StructuralData` implementations):** + + - All `impl StructuralData for MyType` must be updated to `impl StructuralData<crate::ThisProgram> for MyType`. + - All `impl StructuralDataSealed for MyType` must be updated to `impl StructuralDataSealed<crate::ThisProgram> for MyType`. + - All trait bounds `T: StructuralData` must be updated to `T: StructuralData<C>` with an additional `C: ProgramCollect<Enum = C>` bound. + - The `StructuralRenderer::render` method signature has changed from `render<T: StructuralData + Send>(...)` to `render<T, C>(...) where T: StructuralData<C> + Send, C: ProgramCollect<Enum = C>`. + + **Internal changes:** + + - `StructuralDataSealed` in `mingling_core::__private` now takes a `C` type parameter with `C: ProgramCollect<Enum = C>`. + - `StructuralData` in `mingling_core::renderer::structural::structural_data` now takes a `C` type parameter with `C: ProgramCollect<Enum = C>`. + - Both traits remain under `::mingling::__private`, so this change does **not** affect the public API surface. + - `StructuralRenderer::render` now takes an additional generic parameter `C: ProgramCollect<Enum = C>`. + - All derive macro and `pack_structural!` / `pack_err_structural!` / `group_structural!` implementations have been updated to emit `impl StructuralDataSealed<crate::ThisProgram>` and `impl StructuralData<crate::ThisProgram>` instead of the non-generic form. + - Test code has been updated to use `MockProgramCollect` where appropriate, and integration tests now use `crate::ThisProgram` and call `gen_program!()`. + + _No behavioral changes — this is purely a type-system refactoring to enable `group_structural!` to work with external types. Since both traits are defined in `::mingling::__private`, this change has **no impact on the public API** — end users interact with `StructuralData` only through auto-generated derive macros and `pack_structural!`/`group_structural!` macros, which are automatically updated. Only users with manual `impl StructuralData` blocks (an advanced/rare case) need to update their code. + #### Features: 1. **[`core`]** Added `RenderResult::new()` method for creating a new `RenderResult` with default values (empty text and exit code 0). This provides a more explicit and discoverable constructor compared to `RenderResult::default()`, making it clearer when a fresh result is being created for use with `write!`/`writeln!`. 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/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!(); + diff --git a/mingling_macros/src/func/pack_err.rs b/mingling_macros/src/func/pack_err.rs index 36e550a..2a318bc 100644 --- a/mingling_macros/src/func/pack_err.rs +++ b/mingling_macros/src/func/pack_err.rs @@ -139,8 +139,8 @@ pub(crate) fn pack_err_structural(input: TokenStream) -> TokenStream { .insert(type_name_str); let structural_data = quote! { - impl ::mingling::__private::StructuralDataSealed for #type_name {} - impl ::mingling::__private::StructuralData for #type_name {} + impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {} + impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {} }; // Generate the struct + impls (same as pack_err! but with Serialize derive + sealed) diff --git a/mingling_macros/src/systems/structural_data.rs b/mingling_macros/src/systems/structural_data.rs index 74bcf09..0a783ec 100644 --- a/mingling_macros/src/systems/structural_data.rs +++ b/mingling_macros/src/systems/structural_data.rs @@ -29,8 +29,8 @@ pub(crate) fn derive_structural_data(input: TokenStream) -> TokenStream { // Users cannot implement StructuralDataSealed manually (it's #[doc(hidden)]), // so the only way to get StructuralData is through this derive macro. let expanded = quote! { - impl ::mingling::__private::StructuralDataSealed for #type_name {} - impl ::mingling::__private::StructuralData for #type_name {} + impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {} + impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {} }; expanded.into() @@ -149,8 +149,8 @@ pub(crate) fn pack_structural(input: TokenStream) -> TokenStream { // StructuralData impl + sealed + registration let structural_impl = quote! { - impl ::mingling::__private::StructuralDataSealed for #type_name {} - impl ::mingling::__private::StructuralData for #type_name {} + impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {} + impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {} }; let expanded = quote! { @@ -303,8 +303,8 @@ pub(crate) fn group_structural(input: TokenStream) -> TokenStream { } } - impl ::mingling::__private::StructuralDataSealed for #type_name {} - impl ::mingling::__private::StructuralData for #type_name {} + impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {} + impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {} ::mingling::macros::register_type!(#type_name); } |
