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]