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 ba6aecfd7 fix(Rust): prevent obtaining generic type metadata on custom 
types(struct/enum) (#3057)
ba6aecfd7 is described below

commit ba6aecfd7b82a6fce7ff502f2786e8854ffa8b59
Author: urlyy <[email protected]>
AuthorDate: Wed Dec 17 17:11:17 2025 +0800

    fix(Rust): prevent obtaining generic type metadata on custom 
types(struct/enum) (#3057)
    
    ## Why?
    Issue #3049 reveals a problem. For example, when serializing the field
    `my_vec: Vec<Wrapper<Inner>>`, the current Fory-Rust writes
    ```
    (id: Vec::type_id, generics: [
            ( id: Wrapper<Inner>::type_id, generics: [] )
    ])
    ```
    That is, the concrete generic type parameters on structs/enums are not
    written into the `FieldType` metadata. This results in inconsistent
    metadata between the sender and the receiver, and causes
    `assign_field_ids()` to assign `remote_field.field_id = -1`, leading to
    the issue.
    
    
    ## What does this PR do?
    When obtaining the field type on the sender side, for field_type
    `Vec<Wrapper<Inner>>`, the `FieldType` objects for `Vec` and
    `Wrapper<Inner>` have already been created, so there is no need to
    further create a `FieldType` object for `Inner`. Therefore, further
    recursion over generic types on structs/enums is truncated in the
    runtime.
    
    Meanwhile, because different languages provide different levels of
    support for generic types on classes, serializing structs/enums with
    generics is `not supported in xlang mode`. A `runtime check` has been
    added for this.
    
    
    ## Related issues
    Fixes #3049
---
 rust/fory-core/src/error.rs                  |   6 +-
 rust/fory-core/src/fory.rs                   |   1 +
 rust/fory-core/src/resolver/type_resolver.rs |  12 +++
 rust/fory-derive/src/object/util.rs          |   8 ++
 rust/tests/tests/compatible/test_struct.rs   | 111 +++++++++++++++++++++++++++
 5 files changed, 135 insertions(+), 3 deletions(-)

diff --git a/rust/fory-core/src/error.rs b/rust/fory-core/src/error.rs
index 12c119364..a27fb4d85 100644
--- a/rust/fory-core/src/error.rs
+++ b/rust/fory-core/src/error.rs
@@ -163,7 +163,7 @@ pub enum Error {
     ///
     /// Do not construct this variant directly; use [`Error::unsupported`] 
instead.
     #[error("{0}")]
-    Uunsupported(Cow<'static, str>),
+    Unsupported(Cow<'static, str>),
 
     /// Operation not allowed in current context.
     ///
@@ -381,7 +381,7 @@ impl Error {
         err
     }
 
-    /// Creates a new [`Error::Uunsupported`] from a string or static message.
+    /// Creates a new [`Error::Unsupported`] from a string or static message.
     ///
     /// If `FORY_PANIC_ON_ERROR` environment variable is set, this will panic 
with the error message.
     ///
@@ -396,7 +396,7 @@ impl Error {
     #[cold]
     #[track_caller]
     pub fn unsupported<S: Into<Cow<'static, str>>>(s: S) -> Self {
-        let err = Error::Uunsupported(s.into());
+        let err = Error::Unsupported(s.into());
         if PANIC_ON_ERROR {
             panic!("FORY_PANIC_ON_ERROR: {}", err);
         }
diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs
index a0081f8a6..4a6632879 100644
--- a/rust/fory-core/src/fory.rs
+++ b/rust/fory-core/src/fory.rs
@@ -183,6 +183,7 @@ impl Fory {
         if !self.config.check_struct_version {
             self.config.check_struct_version = !self.config.compatible;
         }
+        self.type_resolver.set_xlang(xlang);
         self
     }
 
diff --git a/rust/fory-core/src/resolver/type_resolver.rs 
b/rust/fory-core/src/resolver/type_resolver.rs
index 37cf10735..4a88f8d2c 100644
--- a/rust/fory-core/src/resolver/type_resolver.rs
+++ b/rust/fory-core/src/resolver/type_resolver.rs
@@ -458,6 +458,7 @@ pub struct TypeResolver {
     // Fast lookup by numeric ID for common types
     type_id_index: Vec<u32>,
     compatible: bool,
+    xlang: bool,
 }
 
 // Safety: TypeResolver instances are only shared through higher-level 
synchronization that
@@ -478,6 +479,7 @@ impl Default for TypeResolver {
             type_id_index: Vec::new(),
             partial_type_infos: HashMap::new(),
             compatible: false,
+            xlang: false,
         };
         registry.register_builtin_types().unwrap();
         registry
@@ -1000,6 +1002,14 @@ impl TypeResolver {
         self.compatible = compatible;
     }
 
+    pub(crate) fn set_xlang(&mut self, xlang: bool) {
+        self.xlang = xlang;
+    }
+
+    pub fn is_xlang(&self) -> bool {
+        self.xlang
+    }
+
     /// Builds the final TypeResolver by completing all partial type infos 
created during registration.
     ///
     /// This method processes all types that were registered with lazy 
initialization enabled.
@@ -1061,6 +1071,7 @@ impl TypeResolver {
             partial_type_infos: HashMap::new(),
             type_id_index,
             compatible: self.compatible,
+            xlang: self.xlang,
         })
     }
 
@@ -1142,6 +1153,7 @@ impl TypeResolver {
             partial_type_infos: HashMap::new(),
             type_id_index: self.type_id_index.clone(),
             compatible: self.compatible,
+            xlang: self.xlang,
         }
     }
 }
diff --git a/rust/fory-derive/src/object/util.rs 
b/rust/fory-derive/src/object/util.rs
index c2f16ebdb..c814a1dde 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -534,6 +534,14 @@ pub(super) fn generic_tree_to_tokens(node: &TypeNode) -> 
TokenStream {
                     vec![]
                 ));
             }
+            let is_custom = !fory_core::types::is_internal_type(type_id & 
0xff);
+            if is_custom {
+                if type_resolver.is_xlang() && generics.len() > 0 {
+                    return 
Err(fory_core::error::Error::unsupported("serialization of generic structs and 
enums is not supported in xlang mode"));
+                } else {
+                    generics = vec![];
+                }
+            }
             fory_core::meta::FieldType::new(type_id, #nullable, generics)
         }
     }
diff --git a/rust/tests/tests/compatible/test_struct.rs 
b/rust/tests/tests/compatible/test_struct.rs
index 62f3f5b59..44c32726b 100644
--- a/rust/tests/tests/compatible/test_struct.rs
+++ b/rust/tests/tests/compatible/test_struct.rs
@@ -16,8 +16,10 @@
 // under the License.
 
 use fory_core::fory::Fory;
+use fory_core::{Error, ForyDefault, ReadContext, Serializer, TypeResolver, 
WriteContext};
 use fory_derive::ForyObject;
 use std::collections::{HashMap, HashSet};
+use std::marker::PhantomData;
 
 // RUSTFLAGS="-Awarnings" cargo expand -p tests --test test_struct
 #[test]
@@ -599,3 +601,112 @@ fn boxed() {
     let item2_f6: Option<i32> = fory2.deserialize(&bytes).unwrap();
     assert_eq!(item2.f6, item2_f6);
 }
+
+#[test]
+fn test_struct_with_generic() {
+    #[derive(Debug, PartialEq)]
+    struct Wrapper<T> {
+        value: String,
+        _marker: PhantomData<T>,
+        data: T,
+    }
+
+    #[derive(ForyObject, Debug, PartialEq)]
+    #[fory(debug)]
+    struct MyStruct {
+        my_vec: Vec<Wrapper<Another>>,
+        my_vec1: Vec<Wrapper<i32>>,
+    }
+
+    #[derive(ForyObject, Debug, PartialEq)]
+    #[fory(debug)]
+    struct Another {
+        f1: i32,
+    }
+
+    impl<T: 'static + Serializer + ForyDefault> Serializer for Wrapper<T> {
+        fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), 
Error> {
+            context.writer.write_varuint32(self.value.len() as u32);
+            context.writer.write_utf8_string(&self.value);
+            self.data.fory_write_data(context)?;
+            Ok(())
+        }
+
+        fn fory_read_data(context: &mut ReadContext) -> Result<Self, Error> {
+            let len = context.reader.read_varuint32()? as usize;
+            let value = context.reader.read_utf8_string(len)?;
+            let data = T::fory_read_data(context)?;
+            Ok(Self {
+                value,
+                _marker: PhantomData,
+                data,
+            })
+        }
+
+        fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> 
Result<u32, Error> {
+            Self::fory_get_type_id(type_resolver)
+        }
+
+        fn as_any(&self) -> &dyn std::any::Any {
+            self
+        }
+    }
+
+    impl<T: ForyDefault> ForyDefault for Wrapper<T> {
+        fn fory_default() -> Self {
+            Self {
+                value: "".into(),
+                _marker: PhantomData,
+                data: T::fory_default(),
+            }
+        }
+    }
+
+    let mut fory1 = Fory::default().compatible(true);
+    let mut fory2 = Fory::default(); // Without compatible it works fine.
+    let mut fory3 = Fory::default().xlang(true); // Works fine with xlang 
enabled
+
+    fn inner_test(fory: &mut Fory) -> Result<(), Error> {
+        fory.register::<MyStruct>(1)?;
+        fory.register::<Another>(2)?;
+        fory.register_serializer::<Wrapper<Another>>(3)?;
+        fory.register_serializer::<Wrapper<i32>>(4)?;
+
+        let w1 = Wrapper::<Another> {
+            value: "Value1".into(),
+            _marker: PhantomData,
+            data: Another { f1: 10 },
+        };
+        let w2 = Wrapper::<Another> {
+            value: "Value2".into(),
+            _marker: PhantomData,
+            data: Another { f1: 11 },
+        };
+
+        let w3 = Wrapper::<i32> {
+            value: "Value3".into(),
+            _marker: PhantomData,
+            data: 12,
+        };
+        let w4 = Wrapper::<i32> {
+            value: "Value4".into(),
+            _marker: PhantomData,
+            data: 13,
+        };
+
+        let ms = MyStruct {
+            my_vec: vec![w1, w2],
+            my_vec1: vec![w3, w4],
+        };
+
+        let bytes = fory.serialize(&ms)?;
+        let new_ms = fory.deserialize::<MyStruct>(&bytes)?;
+        assert_eq!(ms, new_ms);
+        Ok(())
+    }
+
+    for fory in [&mut fory1, &mut fory2] {
+        assert!(inner_test(fory).is_ok());
+    }
+    assert!(inner_test(&mut fory3).is_err());
+}


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

Reply via email to