This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new 910b565c2 feat(rust): add union and none type support (#3084)
910b565c2 is described below

commit 910b565c24486836e6302b4de3678d4838e6ddda
Author: Damon Zhao <[email protected]>
AuthorDate: Wed Dec 24 21:43:39 2025 +0800

    feat(rust): add union and none type support (#3084)
    
    ## Why?
    
    Add `UNION` and `NONE` to type ids that align with [xlang
    
spec](https://github.com/apache/fory/blob/main/docs/specification/xlang_serialization_spec.md#internal-type-id-table)
    
    ## What does this PR do?
    
    This PR adds `UNION` and `NONE` to type ids:
    
    1. add `UNION` and `NONE` to type ids
    2. change `()` and `PhantomData` type id from `UNKNOWN` to `NONE`
    3. skip early when encounter `NONE` in `skip_any_value`
    
    ## Related issues
    
    None
    
    ## Does this PR introduce any user-facing change?
    
    - [x] Does this PR introduce any public API change?
      - Yes, adds new public types support: `UNION` and `NONE` to TypeId
    - [ ] Does this PR introduce any binary protocol compatibility change?
      - No
    
    ## Benchmark
---
 rust/fory-core/src/serializer/marker.rs | 12 ++++-----
 rust/fory-core/src/serializer/skip.rs   | 16 +++++++++++
 rust/fory-core/src/serializer/tuple.rs  | 11 +++++---
 rust/fory-core/src/serializer/util.rs   |  4 ++-
 rust/fory-core/src/types.rs             | 11 +++++++-
 rust/tests/tests/test_marker.rs         | 47 ++++++++++++++++++++++++++++++++-
 6 files changed, 88 insertions(+), 13 deletions(-)

diff --git a/rust/fory-core/src/serializer/marker.rs 
b/rust/fory-core/src/serializer/marker.rs
index 49cc198cb..615107b96 100644
--- a/rust/fory-core/src/serializer/marker.rs
+++ b/rust/fory-core/src/serializer/marker.rs
@@ -44,20 +44,20 @@ impl<T: 'static> Serializer for PhantomData<T> {
 
     #[inline(always)]
     fn fory_get_type_id(_: &TypeResolver) -> Result<u32, Error> {
-        // Use UNKNOWN to skip type registry lookup - PhantomData<T> has no 
runtime data
-        Ok(TypeId::UNKNOWN as u32)
+        // Use NONE - PhantomData<T> has no runtime data, skip can return early
+        Ok(TypeId::NONE as u32)
     }
 
     #[inline(always)]
     fn fory_type_id_dyn(&self, _: &TypeResolver) -> Result<u32, Error> {
-        // Use UNKNOWN to skip type registry lookup - PhantomData<T> has no 
runtime data
-        Ok(TypeId::UNKNOWN as u32)
+        // Use NONE - PhantomData<T> has no runtime data, skip can return early
+        Ok(TypeId::NONE as u32)
     }
 
     #[inline(always)]
     fn fory_static_type_id() -> TypeId {
-        // Use UNKNOWN to skip type registry lookup - PhantomData<T> has no 
runtime data
-        TypeId::UNKNOWN
+        // Use NONE - PhantomData<T> has no runtime data, skip can return early
+        TypeId::NONE
     }
 
     #[inline(always)]
diff --git a/rust/fory-core/src/serializer/skip.rs 
b/rust/fory-core/src/serializer/skip.rs
index cac9c5ee2..411838d59 100644
--- a/rust/fory-core/src/serializer/skip.rs
+++ b/rust/fory-core/src/serializer/skip.rs
@@ -63,6 +63,11 @@ pub fn skip_any_value(context: &mut ReadContext, 
read_ref_flag: bool) -> Result<
     let type_id = context.reader.read_varuint32()?;
     let internal_id = type_id & 0xff;
 
+    // NONE type has no data - return early
+    if internal_id == types::NONE {
+        return Ok(());
+    }
+
     // For struct-like types, also read meta_index to get type_info
     // This is critical for polymorphic collections where elements are struct 
types.
     let (field_type, type_info_opt) = match internal_id {
@@ -558,6 +563,17 @@ fn skip_value(
             // UNKNOWN (0) is used for polymorphic types in cross-language 
serialization
             return skip_any_value(context, false);
         }
+        types::NONE => {
+            // NONE represents an empty/unit value with no data - nothing to 
skip
+            return Ok(());
+        }
+        types::UNION => {
+            // UNION format: index (varuint32) + value (xreadRef)
+            // Skip the index
+            let _ = context.reader.read_varuint32()?;
+            // Skip the value (which is written via xwriteRef)
+            return skip_any_value(context, true);
+        }
 
         _ => {
             return Err(Error::type_error(format!(
diff --git a/rust/fory-core/src/serializer/tuple.rs 
b/rust/fory-core/src/serializer/tuple.rs
index 50b631003..19c724ea1 100644
--- a/rust/fory-core/src/serializer/tuple.rs
+++ b/rust/fory-core/src/serializer/tuple.rs
@@ -24,7 +24,7 @@ use crate::serializer::{ForyDefault, Serializer};
 use crate::types::TypeId;
 use std::mem;
 
-// Unit type () implementation
+// Unit type () implementation - represents an empty/unit value with no data
 impl Serializer for () {
     #[inline(always)]
     fn fory_write_data(&self, _context: &mut WriteContext) -> Result<(), 
Error> {
@@ -45,17 +45,20 @@ impl Serializer for () {
 
     #[inline(always)]
     fn fory_get_type_id(_: &TypeResolver) -> Result<u32, Error> {
-        Ok(TypeId::STRUCT as u32)
+        // Use NONE - unit type has no runtime data, skip can return early
+        Ok(TypeId::NONE as u32)
     }
 
     #[inline(always)]
     fn fory_type_id_dyn(&self, _: &TypeResolver) -> Result<u32, Error> {
-        Ok(TypeId::STRUCT as u32)
+        // Use NONE - unit type has no runtime data, skip can return early
+        Ok(TypeId::NONE as u32)
     }
 
     #[inline(always)]
     fn fory_static_type_id() -> TypeId {
-        TypeId::STRUCT
+        // Use NONE - unit type has no runtime data, skip can return early
+        TypeId::NONE
     }
 
     #[inline(always)]
diff --git a/rust/fory-core/src/serializer/util.rs 
b/rust/fory-core/src/serializer/util.rs
index 2e09f1f6a..388a2be93 100644
--- a/rust/fory-core/src/serializer/util.rs
+++ b/rust/fory-core/src/serializer/util.rs
@@ -22,7 +22,7 @@ use crate::serializer::Serializer;
 use crate::types::TypeId;
 use crate::types::{
     is_user_type, BOOL, ENUM, FLOAT32, FLOAT64, INT128, INT16, INT32, INT64, 
INT8, NAMED_ENUM,
-    U128, U16, U32, U64, U8,
+    NONE, U128, U16, U32, U64, U8,
 };
 
 #[inline(always)]
@@ -68,6 +68,7 @@ pub const fn field_need_write_ref_into(type_id: u32, 
nullable: bool) -> bool {
         return true;
     }
     let internal_type_id = type_id & 0xff;
+    // NONE type has no data, so no ref tracking needed
     !matches!(
         internal_type_id,
         BOOL | INT8
@@ -82,6 +83,7 @@ pub const fn field_need_write_ref_into(type_id: u32, 
nullable: bool) -> bool {
             | U32
             | U64
             | U128
+            | NONE
     )
 }
 
diff --git a/rust/fory-core/src/types.rs b/rust/fory-core/src/types.rs
index 42867859e..8450eec5f 100644
--- a/rust/fory-core/src/types.rs
+++ b/rust/fory-core/src/types.rs
@@ -81,6 +81,10 @@ pub enum TypeId {
     FLOAT16_ARRAY = 35,
     FLOAT32_ARRAY = 36,
     FLOAT64_ARRAY = 37,
+    // A tagged union type that can hold one of several alternative types.
+    UNION = 38,
+    // Represents an empty/unit value with no data.
+    NONE = 39,
     U8 = 64,
     U16 = 65,
     U32 = 66,
@@ -147,6 +151,8 @@ pub const INT64_ARRAY: u32 = TypeId::INT64_ARRAY as u32;
 pub const FLOAT16_ARRAY: u32 = TypeId::FLOAT16_ARRAY as u32;
 pub const FLOAT32_ARRAY: u32 = TypeId::FLOAT32_ARRAY as u32;
 pub const FLOAT64_ARRAY: u32 = TypeId::FLOAT64_ARRAY as u32;
+pub const UNION: u32 = TypeId::UNION as u32;
+pub const NONE: u32 = TypeId::NONE as u32;
 pub const U8: u32 = TypeId::U8 as u32;
 pub const U16: u32 = TypeId::U16 as u32;
 pub const U32: u32 = TypeId::U32 as u32;
@@ -347,10 +353,11 @@ pub(crate) const fn need_to_write_type_for_field(type_id: 
TypeId) -> bool {
 
 /// Keep as const fn for compile time evaluation or constant folding
 #[inline(always)]
-pub(crate) const fn is_user_type(type_id: u32) -> bool {
+pub const fn is_user_type(type_id: u32) -> bool {
     matches!(
         type_id,
         ENUM | NAMED_ENUM
+            | UNION
             | STRUCT
             | COMPATIBLE_STRUCT
             | NAMED_STRUCT
@@ -481,6 +488,8 @@ pub fn format_type_id(type_id: u32) -> String {
         35 => "FLOAT16_ARRAY",
         36 => "FLOAT32_ARRAY",
         37 => "FLOAT64_ARRAY",
+        38 => "UNION",
+        39 => "NONE",
         64 => "U8",
         65 => "U16",
         66 => "U32",
diff --git a/rust/tests/tests/test_marker.rs b/rust/tests/tests/test_marker.rs
index 90b41afa0..ce0bd9f22 100644
--- a/rust/tests/tests/test_marker.rs
+++ b/rust/tests/tests/test_marker.rs
@@ -15,13 +15,17 @@
 // specific language governing permissions and limitations
 // under the License.
 
-//! Tests for marker types like `PhantomData<T>`.
+//! Tests for marker types like `PhantomData<T>` and TypeId constants.
 //!
 //! `PhantomData<T>` is a zero-sized marker type used for type-level 
information
 //! without any runtime data. These tests verify that structs containing
 //! `PhantomData<T>` can be serialized correctly.
+//!
+//! Also tests `UNION` and `NONE` TypeId constants defined in xlang spec.
 
 use fory_core::fory::Fory;
+use fory_core::serializer::Serializer;
+use fory_core::types::TypeId;
 use fory_derive::ForyObject;
 use std::marker::PhantomData;
 
@@ -105,3 +109,44 @@ fn test_nested_struct_with_phantom_data() {
     let result: OuterWithPhantom = fory.deserialize(&bytes).unwrap();
     assert_eq!(result, value);
 }
+
+// ============================================================================
+// TypeId Tests for UNION and NONE
+// ============================================================================
+
+/// Test that UNION TypeId matches xlang spec (38)
+#[test]
+fn test_union_type_id() {
+    assert_eq!(TypeId::UNION as i16, 38);
+    assert_eq!(fory_core::types::UNION, 38);
+}
+
+/// Test that NONE TypeId matches xlang spec (39)
+#[test]
+fn test_none_type_id() {
+    assert_eq!(TypeId::NONE as i16, 39);
+    assert_eq!(fory_core::types::NONE, 39);
+}
+
+/// Test that PhantomData uses NONE TypeId (no runtime data)
+#[test]
+fn test_phantom_data_uses_none_type_id() {
+    assert_eq!(
+        <PhantomData<i32> as Serializer>::fory_static_type_id(),
+        TypeId::NONE
+    );
+    assert_eq!(
+        <PhantomData<String> as Serializer>::fory_static_type_id(),
+        TypeId::NONE
+    );
+    assert_eq!(
+        <PhantomData<Vec<u8>> as Serializer>::fory_static_type_id(),
+        TypeId::NONE
+    );
+}
+
+/// Test that unit type () uses NONE TypeId (empty/unit value with no data)
+#[test]
+fn test_unit_type_uses_none_type_id() {
+    assert_eq!(<() as Serializer>::fory_static_type_id(), TypeId::NONE);
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to