diff --git a/AGENTS.md b/AGENTS.md index d6d7d31afe..aca7ebaec1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,7 +68,9 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th - User guide docs must explain user-visible behavior, commands, and examples. Do not add implementation details, internal ownership rationale, build flags, or type-id-space caveats unless they directly clarify a confusion users can - act on. + act on. Translate internal owner-model details into concrete user actions, and + avoid phrases such as "serializer-owned capability" or "registration alone + does not..." in user-facing docs. - Add comments only when behavior is hard to understand or an algorithm is non-obvious. - Do not remove existing code comments unless they are stale, misleading, redundant, or no longer necessary after the change. - Only add tests that verify internal behaviors or fix specific bugs; do not create unnecessary tests unless requested. diff --git a/docs/guide/rust/custom-serializers.md b/docs/guide/rust/custom-serializers.md index c3c5e8f90e..e5c273e0ab 100644 --- a/docs/guide/rust/custom-serializers.md +++ b/docs/guide/rust/custom-serializers.md @@ -31,7 +31,7 @@ For types that don't support `#[derive(ForyStruct)]`, implement the `Serializer` ## Implementing the Serializer Trait ```rust -use fory::{Fory, ReadContext, WriteContext, Serializer, ForyDefault, Error}; +use fory::{Error, Fory, ForyDefault, ReadContext, Serializer, TypeResolver, WriteContext}; use std::any::Any; #[derive(Debug, PartialEq, Default)] @@ -41,20 +41,21 @@ struct CustomType { } impl Serializer for CustomType { - fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) { + fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error> { context.writer.write_i32(self.value); - context.writer.write_varuint32(self.name.len() as u32); + context.writer.write_var_u32(self.name.len() as u32); context.writer.write_utf8_string(&self.name); + Ok(()) } - fn fory_read_data(context: &mut ReadContext, is_field: bool) -> Result { - let value = context.reader.read_i32(); - let len = context.reader.read_varuint32() as usize; - let name = context.reader.read_utf8_string(len); + fn fory_read_data(context: &mut ReadContext) -> Result { + let value = context.reader.read_i32()?; + let len = context.reader.read_var_u32()? as usize; + let name = context.reader.read_utf8_string(len)?; Ok(Self { value, name }) } - fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> u32 { + fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result { Self::fory_get_type_id(type_resolver) } @@ -76,6 +77,28 @@ impl ForyDefault for CustomType { > > **Tip**: If your type supports `#[derive(ForyStruct)]`, you can use `#[fory(generate_default)]` to automatically generate both `ForyDefault` and `Default` implementations. +## Manual Serializers and Arc Any + +If a manually registered serializer needs its type to round-trip behind +`Arc` or preserve `UnknownCase` payloads, implement the +send-sync Any reader and return the concrete value as a boxed `Any` value: + +```rust +impl Serializer for CustomType { + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> { + Ok(Box::new(Self::fory_read_data(context)?)) + } + + // Implement the ordinary Serializer methods as shown above. + // ... +} +``` + +Do not override this method for values that contain fields whose types are not +`Send + Sync`, such as `Rc`, `RcWeak`, `RefCell`, or `Cell`. + ## Registering Custom Serializers ```rust diff --git a/docs/guide/rust/native-serialization.md b/docs/guide/rust/native-serialization.md index e2bf6e74f4..133b022b93 100644 --- a/docs/guide/rust/native-serialization.md +++ b/docs/guide/rust/native-serialization.md @@ -100,7 +100,10 @@ Native serialization owns the Rust-specific object surface: - `Box`, `Rc`, `Arc`, `RcWeak`, and `ArcWeak`. - `RefCell` and `Mutex`. - Trait objects such as `Box`, `Rc`, and `Arc`. -- Runtime type dispatch with `Rc` and `Arc`. +- Runtime type dispatch with `Box`, `Rc`, and + `Arc` for registered non-container payloads. Wrap + containers in registered structs, enums, or unions before using them behind + erased `Any` carriers. - Date and time carriers, including optional `chrono` support. Use [Basic Serialization](basic-serialization.md), [References](references.md), and diff --git a/docs/guide/rust/polymorphism.md b/docs/guide/rust/polymorphism.md index fa87735edd..de8547c1b7 100644 --- a/docs/guide/rust/polymorphism.md +++ b/docs/guide/rust/polymorphism.md @@ -85,12 +85,12 @@ assert_eq!(decoded.star_animal.speak(), "Woof!"); ## Serializing dyn Any Trait Objects -Apache Fory™ supports serializing `Rc` and +Apache Fory™ supports serializing `Box`, `Rc`, and `Arc` for runtime type dispatch: **Key points:** -- Works with any type that implements `Serializer` +- Works with registered concrete non-container types that implement `Serializer` - Requires downcasting after deserialization to access the concrete type - Type information is preserved during serialization - Useful for plugin systems and dynamic type handling @@ -132,6 +132,46 @@ let unwrapped = decoded.downcast_ref::().unwrap(); assert_eq!(unwrapped.name, "Buddy"); ``` +`Box`, `Rc`, and `Arc` are supported +erased `Any` carriers for registered concrete non-container payloads. +Use `Arc` when the erased payload must be shareable +across threads; the concrete payload type must also satisfy `Send + Sync`. +Registered structs, enums, and unions that satisfy those bounds can be used as +the erased payload. + +The unsupported case is a generic container used directly as the top-level +erased payload. This applies to all erased `Any` carriers: `Box`, +`Rc`, and `Arc`. Unsupported direct payloads +include list-, map-, and set-like containers such as `Vec`, `Vec`, +`HashMap`, `HashSet`, and `LinkedList`. + +If you need to put a container in an erased `Any` payload, wrap it in a +registered struct, enum, or union and use that wrapper as the erased payload: + +```rust +use fory::{Fory, ForyStruct}; +use std::any::Any; +use std::sync::Arc; + +#[derive(ForyStruct)] +struct IntList { + values: Vec, +} + +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(100)?; + +let value: Arc = Arc::new(IntList { + values: vec![1, 2, 3], +}); +let bytes = fory.serialize(&value)?; +let decoded: Arc = fory.deserialize(&bytes)?; +``` + +The wrapper makes the erased payload a concrete registered type while the +container remains a normal typed field. The same wrapper model is the supported +path for `Box` and `Rc`. + ## Rc/Arc-Based Trait Objects in Structs For fields with `Rc` or `Arc`, Fory automatically handles the conversion: @@ -180,7 +220,7 @@ assert_eq!(decoded.animals_arc[0].speak(), "Woof!"); Due to Rust's orphan rule, `Rc` and `Arc` cannot implement `Serializer` directly. For standalone serialization (not inside struct fields), the `register_trait_type!` macro generates wrapper types. -**Note:** If you don't want to use wrapper types, you can serialize as `Rc` or `Arc` instead (see the dyn Any section above). +**Note:** If you don't want to use wrapper types for concrete non-container payloads, you can serialize as `Box`, `Rc`, or `Arc` instead (see the dyn Any section above). The `register_trait_type!` macro generates `AnimalRc` and `AnimalArc` wrapper types: diff --git a/docs/guide/rust/schema-evolution.md b/docs/guide/rust/schema-evolution.md index 2766641ca9..14c536afc4 100644 --- a/docs/guide/rust/schema-evolution.md +++ b/docs/guide/rust/schema-evolution.md @@ -129,14 +129,16 @@ let decoded: Value = fory.deserialize(&bytes)?; assert_eq!(value, decoded); ``` -For typed ADT unions whose schema cases are unit or single-payload variants, -`#[fory(unknown)] Unknown(::fory::UnknownCase)` is only the runtime -forward-compatibility carrier. It cannot be the default variant, and the union -must include at least one real schema case. The marker only selects the carrier -and does not add an entry to the schema case table; schema cases use -non-negative IDs. `UnknownCase` stores its payload as -`Arc`, so locally registered future payload types must be -thread-safe to be preserved as unknown cases. +For typed ADT unions whose cases are unit or single-payload variants, add an +`#[fory(unknown)] Unknown(::fory::UnknownCase)` variant when you need to +preserve future payload variants. Do not make the unknown variant the default; +keep a real schema case marked `#[fory(default)]`. Register future payload types +locally before deserializing unknown cases you need to preserve. + +`UnknownCase` stores its payload as `Arc`, so preserved +payload types must satisfy `Send + Sync`. Direct generic containers are not +supported as erased `Any` payloads; wrap the container in a registered derived +type if an unknown case needs to preserve it. ### Enum Schema Evolution diff --git a/rust/README.md b/rust/README.md index dd9ea0708d..8f238c0ce0 100644 --- a/rust/README.md +++ b/rust/README.md @@ -251,6 +251,18 @@ The examples in this section use native mode because Rust trait objects and `dyn - `Box`/`Rc`/`Arc` - Any trait type objects - `Vec>`, `HashMap>` - Collections of trait objects +`Box`, `Rc`, and `Arc` are supported +erased `Any` carriers for registered concrete non-container payloads. +Use `Arc` when the erased payload must be shareable +across threads; the concrete payload type must also satisfy `Send + Sync`. +Registered structs, enums, and unions that satisfy those bounds can be used as +the erased payload. +Generic containers such as `Vec`, `HashMap`, `HashSet`, and +`LinkedList` are not supported directly as top-level erased `Any` payloads +behind any of those carriers. This also includes primitive vector encodings such +as `Vec`. Wrap the container in a registered derived type when it needs to +travel behind an erased `Any` carrier. + **Basic Trait Object Serialization Example:** ```rust diff --git a/rust/fory-core/src/error.rs b/rust/fory-core/src/error.rs index c12a9f7517..9311a8a4a2 100644 --- a/rust/fory-core/src/error.rs +++ b/rust/fory-core/src/error.rs @@ -552,6 +552,18 @@ impl Error { } } +#[cold] +#[inline(never)] +pub(crate) fn unsupported_send_sync_type() -> Error +where + T: ?Sized, +{ + Error::type_error(format!( + "{} cannot be represented as Arc", + std::any::type_name::() + )) +} + /// Ensures a condition is true; otherwise returns an [`enum@Error`]. /// /// # Examples diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs index 2b9b7f6214..4a491616b1 100644 --- a/rust/fory-core/src/fory.rs +++ b/rust/fory-core/src/fory.rs @@ -954,16 +954,6 @@ impl Fory { .register_serializer_by_name::(namespace, type_name) } - /// Registers a generic trait object type for serialization. - /// This method should be used to register collection types such as `Vec`, `HashMap`, etc. - /// Don't register concrete struct types with this method. Use `register()` instead. - pub fn register_generic_trait( - &mut self, - ) -> Result<(), Error> { - self.check_registration_allowed()?; - self.type_resolver.register_generic_trait::() - } - /// Writes the serialization header to the writer. #[inline(always)] pub fn write_head(&self, writer: &mut Writer) { diff --git a/rust/fory-core/src/resolver/type_resolver.rs b/rust/fory-core/src/resolver/type_resolver.rs index bb9ae8e536..3a5c9968b1 100644 --- a/rust/fory-core/src/resolver/type_resolver.rs +++ b/rust/fory-core/src/resolver/type_resolver.rs @@ -28,7 +28,6 @@ use crate::types::{Date, Duration, Timestamp}; use crate::TypeId; #[cfg(feature = "chrono")] use chrono::{Duration as ChronoDuration, NaiveDate, NaiveDateTime}; -use std::collections::{HashSet, LinkedList}; use std::rc::Rc; use std::vec; @@ -63,9 +62,9 @@ type ReadFn = type WriteDataFn = fn(&dyn Any, &mut WriteContext, has_generics: bool) -> Result<(), Error>; type ReadDataFn = fn(&mut ReadContext) -> Result, Error>; -type ReadDataSendSyncFn = fn(&mut ReadContext) -> Result, Error>; +type ReadDataAsSendSyncAnyFn = fn(&mut ReadContext) -> Result, Error>; type ReadCompatibleFn = fn(&mut ReadContext, Rc) -> Result, Error>; -type ReadCompatibleSendSyncFn = +type ReadCompatibleAsSendSyncAnyFn = fn(&mut ReadContext, Rc) -> Result, Error>; type ToSerializerFn = fn(Box) -> Result, Error>; type BuildTypeInfosFn = fn(&TypeResolver) -> Result, Error>; @@ -90,10 +89,9 @@ pub struct Harness { read_fn: ReadFn, write_data_fn: WriteDataFn, read_data_fn: ReadDataFn, - read_data_send_sync_fn: ReadDataSendSyncFn, + read_data_as_send_sync_any_fn: ReadDataAsSendSyncAnyFn, read_compatible_fn: Option, - read_compatible_send_sync_fn: Option, - threadsafe: bool, + read_compatible_as_send_sync_any_fn: Option, to_serializer: ToSerializerFn, build_type_infos: BuildTypeInfosFn, } @@ -105,10 +103,9 @@ impl Harness { read_fn: stub_read_fn, write_data_fn: stub_write_data_fn, read_data_fn: stub_read_data_fn, - read_data_send_sync_fn: stub_read_data_send_sync_fn, + read_data_as_send_sync_any_fn: stub_read_data_as_send_sync_any_fn, read_compatible_fn: None, - read_compatible_send_sync_fn: None, - threadsafe: false, + read_compatible_as_send_sync_any_fn: None, to_serializer: stub_to_serializer_fn, build_type_infos: stub_build_type_infos, } @@ -166,23 +163,17 @@ impl Harness { /// This path never upgrades an ordinary `Box`; it delegates to /// type-owned readers that construct the send-sync trait object directly. #[inline(always)] - pub fn read_polymorphic_data_send_sync( + pub fn read_polymorphic_data_as_send_sync_any( &self, context: &mut ReadContext, typeinfo: &Rc, ) -> Result, Error> { - if !self.threadsafe { - return Err(Error::type_error(format!( - "{}::{} cannot be represented as Arc", - typeinfo.namespace.original, typeinfo.type_name.original - ))); - } if context.is_compatible() { - if let Some(read_compatible_fn) = self.read_compatible_send_sync_fn { + if let Some(read_compatible_fn) = self.read_compatible_as_send_sync_any_fn { return read_compatible_fn(context, typeinfo.clone()); } } - (self.read_data_send_sync_fn)(context) + (self.read_data_as_send_sync_any_fn)(context) } } @@ -331,10 +322,9 @@ impl TypeInfo { read_fn: stub_read_fn, write_data_fn: stub_write_data_fn, read_data_fn: stub_read_data_fn, - read_data_send_sync_fn: stub_read_data_send_sync_fn, + read_data_as_send_sync_any_fn: stub_read_data_as_send_sync_any_fn, read_compatible_fn: None, - read_compatible_send_sync_fn: None, - threadsafe: false, + read_compatible_as_send_sync_any_fn: None, to_serializer: stub_to_serializer_fn, build_type_infos: stub_build_type_infos, } @@ -384,7 +374,9 @@ fn stub_read_data_fn(_: &mut ReadContext) -> Result, Error> { )) } -fn stub_read_data_send_sync_fn(_: &mut ReadContext) -> Result, Error> { +fn stub_read_data_as_send_sync_any_fn( + _: &mut ReadContext, +) -> Result, Error> { Err(Error::type_error( "Cannot deserialize unknown remote type as Arc - type not registered locally", )) @@ -826,15 +818,6 @@ impl TypeResolver { self.register_internal_serializer::>(TypeId::U128_ARRAY)?; self.register_internal_serializer::>(TypeId::ISIZE_ARRAY)?; self.register_internal_serializer::>(TypeId::INT128_ARRAY)?; - self.register_generic_trait::>()?; - self.register_generic_trait::>()?; - self.register_generic_trait::>()?; - self.register_generic_trait::>()?; - self.register_generic_trait::>()?; - self.register_generic_trait::>()?; - self.register_generic_trait::>()?; - self.register_generic_trait::>()?; - self.register_generic_trait::>()?; Ok(()) } @@ -914,6 +897,13 @@ impl TypeResolver { } else { id }; + let supports_compatible_read = matches!( + actual_type_id, + x if x == TypeId::STRUCT as u32 + || x == TypeId::COMPATIBLE_STRUCT as u32 + || x == TypeId::NAMED_STRUCT as u32 + || x == TypeId::NAMED_COMPATIBLE_STRUCT as u32 + ); fn write( this: &dyn Any, @@ -966,18 +956,10 @@ impl TypeResolver { } } - fn read_data_send_sync( + fn read_data_as_send_sync_any( context: &mut ReadContext, ) -> Result, Error> { - if T2::fory_is_threadsafe_type() { - T2::fory_read_data_send_sync(context) - } else if crate::serializer::is_known_threadsafe_static_type_id( - T2::fory_static_type_id(), - ) { - crate::serializer::read_known_threadsafe_data::(context) - } else { - Err(crate::serializer::unsupported_threadsafe_type::()) - } + T2::fory_read_data_as_send_sync_any(context) } fn to_serializer( @@ -1002,11 +984,11 @@ impl TypeResolver { Ok(Box::new(T2::fory_read_compatible(context, type_info)?)) } - fn read_compatible_send_sync( + fn read_compatible_as_send_sync_any( context: &mut ReadContext, type_info: Rc, ) -> Result, Error> { - T2::fory_read_compatible_send_sync(context, type_info) + T2::fory_read_compatible_as_send_sync_any(context, type_info) } let harness = Harness { @@ -1014,11 +996,17 @@ impl TypeResolver { read_fn: read::, write_data_fn: write_data::, read_data_fn: read_data::, - read_data_send_sync_fn: read_data_send_sync::, - read_compatible_fn: Some(read_compatible::), - read_compatible_send_sync_fn: Some(read_compatible_send_sync::), - threadsafe: T::fory_is_threadsafe_type() - || crate::serializer::is_known_threadsafe_static_type_id(T::fory_static_type_id()), + read_data_as_send_sync_any_fn: read_data_as_send_sync_any::, + read_compatible_fn: if supports_compatible_read { + Some(read_compatible::) + } else { + None + }, + read_compatible_as_send_sync_any_fn: if supports_compatible_read { + Some(read_compatible_as_send_sync_any::) + } else { + None + }, to_serializer: to_serializer::, build_type_infos: build_type_infos::, }; @@ -1212,18 +1200,10 @@ impl TypeResolver { } } - fn read_data_send_sync( + fn read_data_as_send_sync_any( context: &mut ReadContext, ) -> Result, Error> { - if T2::fory_is_threadsafe_type() { - T2::fory_read_data_send_sync(context) - } else if crate::serializer::is_known_threadsafe_static_type_id( - T2::fory_static_type_id(), - ) { - crate::serializer::read_known_threadsafe_data::(context) - } else { - Err(crate::serializer::unsupported_threadsafe_type::()) - } + T2::fory_read_data_as_send_sync_any(context) } fn to_serializer( @@ -1256,11 +1236,9 @@ impl TypeResolver { read_fn: read::, write_data_fn: write_data::, read_data_fn: read_data::, - read_data_send_sync_fn: read_data_send_sync::, + read_data_as_send_sync_any_fn: read_data_as_send_sync_any::, read_compatible_fn: None, - read_compatible_send_sync_fn: None, - threadsafe: T::fory_is_threadsafe_type() - || crate::serializer::is_known_threadsafe_static_type_id(T::fory_static_type_id()), + read_compatible_as_send_sync_any_fn: None, to_serializer: to_serializer::, build_type_infos: build_type_infos::, }; @@ -1319,27 +1297,6 @@ impl TypeResolver { Ok(()) } - /// Register a generic trait type like List, Map, Set - pub fn register_generic_trait( - &mut self, - ) -> Result<(), Error> { - let rs_type_id = std::any::TypeId::of::(); - if self.type_info_map.contains_key(&rs_type_id) { - return Err(Error::type_error(format!( - "Type:{:?} already registered", - rs_type_id - ))); - } - let type_id = T::fory_static_type_id(); - if type_id != TypeId::LIST && type_id != TypeId::MAP && type_id != TypeId::SET { - return Err(Error::not_allowed(format!( - "register_generic_trait can only be used for generic trait types: List, Map, Set, but got type {}", - type_id as u32 - ))); - } - self.register_internal_serializer::(type_id) - } - pub(crate) fn set_compatible(&mut self, compatible: bool) { self.compatible = compatible; } diff --git a/rust/fory-core/src/serializer/any.rs b/rust/fory-core/src/serializer/any.rs index e4e9067be8..e63bf698c9 100644 --- a/rust/fory-core/src/serializer/any.rs +++ b/rust/fory-core/src/serializer/any.rs @@ -37,24 +37,91 @@ fn resolve_registered_type_id( .ok_or_else(|| Error::type_error("Type not registered")) } -/// Check if the type info represents a generic container type (LIST, SET, MAP). -/// These types cannot be deserialized polymorphically via `Box` because -/// different generic instantiations (e.g., `Vec`, `Vec`) share the same type ID. #[inline] -pub(crate) fn check_generic_container_type(type_info: &TypeInfo) -> Result<(), Error> { - let type_id = type_info.get_type_id(); - if type_id == TypeId::LIST || type_id == TypeId::SET || type_id == TypeId::MAP { - return Err(Error::type_error( - "Cannot deserialize generic container types (Vec, HashSet, HashMap) polymorphically \ - via Box, Rc, or Arc. The serialization protocol does not preserve the element type \ - information needed to distinguish between different generic instantiations \ - (e.g., Vec vs Vec). Consider wrapping the container in a \ - named struct type instead.", - )); +fn is_erased_any_container_type(type_id: TypeId) -> bool { + matches!( + type_id, + TypeId::LIST + | TypeId::SET + | TypeId::MAP + | TypeId::BINARY + | TypeId::ARRAY + | TypeId::BOOL_ARRAY + | TypeId::INT8_ARRAY + | TypeId::INT16_ARRAY + | TypeId::INT32_ARRAY + | TypeId::INT64_ARRAY + | TypeId::UINT8_ARRAY + | TypeId::UINT16_ARRAY + | TypeId::UINT32_ARRAY + | TypeId::UINT64_ARRAY + | TypeId::FLOAT8_ARRAY + | TypeId::FLOAT16_ARRAY + | TypeId::BFLOAT16_ARRAY + | TypeId::FLOAT32_ARRAY + | TypeId::FLOAT64_ARRAY + | TypeId::U128_ARRAY + | TypeId::INT128_ARRAY + | TypeId::USIZE_ARRAY + | TypeId::ISIZE_ARRAY + ) +} + +#[cold] +#[inline(never)] +fn unsupported_erased_any_container() -> Error { + Error::type_error( + "List-, map-, and set-like containers cannot be used as top-level erased Any \ + payloads via Box, Rc, or Arc. This \ + includes Vec, byte and numeric vectors, LinkedList, HashSet, and \ + HashMap. Wrap the container in a registered struct, enum, or union and \ + use that wrapper as the erased payload.", + ) +} + +#[cold] +#[inline(never)] +fn erased_any_type_info_error(err: Error) -> Error { + Error::type_error(format!( + "{err}. Erased Any payloads require a registered concrete non-container type. \ + Top-level list-, map-, and set-like containers such as Vec, LinkedList, \ + HashSet, and HashMap are unsupported; wrap the container in a \ + registered struct, enum, or union." + )) +} + +#[inline] +pub(crate) fn check_erased_any_payload_type(type_info: &TypeInfo) -> Result<(), Error> { + if is_erased_any_container_type(type_info.get_type_id()) { + return Err(unsupported_erased_any_container()); } Ok(()) } +#[inline] +fn get_erased_any_type_info( + context: &WriteContext, + concrete_type_id: &std::any::TypeId, +) -> Result, Error> { + let type_info = context + .get_type_info(concrete_type_id) + .map_err(erased_any_type_info_error)?; + check_erased_any_payload_type(&type_info)?; + Ok(type_info) +} + +#[inline] +fn write_erased_any_type_info( + context: &mut WriteContext, + concrete_type_id: std::any::TypeId, +) -> Result, Error> { + let type_info = context + .write_any_type_info(TypeId::UNKNOWN as u32, concrete_type_id) + .map_err(erased_any_type_info_error)?; + check_erased_any_payload_type(&type_info)?; + Ok(type_info) +} + /// Helper function to deserialize to `Box` pub fn deserialize_any_box(context: &mut ReadContext) -> Result, Error> { context.inc_depth()?; @@ -63,8 +130,7 @@ pub fn deserialize_any_box(context: &mut ReadContext) -> Result, Er return Err(Error::invalid_ref("Expected NotNullValue for Box")); } let typeinfo = context.read_any_type_info()?; - // Check for generic container types which cannot be deserialized polymorphically - check_generic_container_type(&typeinfo)?; + check_erased_any_payload_type(&typeinfo)?; let result = typeinfo .get_harness() .read_polymorphic_data(context, &typeinfo); @@ -101,7 +167,7 @@ impl Serializer for Box { has_generics: bool, ) -> Result<(), Error> { let concrete_type_id = (**self).type_id(); - let typeinfo = context.get_type_info(&concrete_type_id)?; + let typeinfo = get_erased_any_type_info(context, &concrete_type_id)?; let serializer_fn = typeinfo.get_harness().get_write_data_fn(); serializer_fn(&**self, context, has_generics) } @@ -131,7 +197,7 @@ impl Serializer for Box { fn fory_read_data(_: &mut ReadContext) -> Result { Err(Error::not_allowed( - "fory_read_data should not be called directly on polymorphic Rc trait object", + "fory_read_data should not be called directly on polymorphic Box trait object", )) } @@ -188,9 +254,9 @@ pub fn write_box_any( } let concrete_type_id = value.type_id(); let typeinfo = if write_type_info { - context.write_any_type_info(TypeId::UNKNOWN as u32, concrete_type_id)? + write_erased_any_type_info(context, concrete_type_id)? } else { - context.get_type_info(&concrete_type_id)? + get_erased_any_type_info(context, &concrete_type_id)? }; let serializer_fn = typeinfo.get_harness().get_write_data_fn(); serializer_fn(value, context, has_generics) @@ -222,8 +288,7 @@ pub fn read_box_any( ); context.read_any_type_info()? }; - // Check for generic container types which cannot be deserialized polymorphically - check_generic_container_type(&typeinfo)?; + check_erased_any_payload_type(&typeinfo)?; let result = typeinfo .get_harness() .read_polymorphic_data(context, &typeinfo); @@ -251,16 +316,12 @@ impl Serializer for Rc { .try_write_rc_ref(&mut context.writer, self) { let concrete_type_id: std::any::TypeId = (**self).type_id(); - let write_data_fn = if write_type_info { - let typeinfo = - context.write_any_type_info(TypeId::UNKNOWN as u32, concrete_type_id)?; - typeinfo.get_harness().get_write_data_fn() + let typeinfo = if write_type_info { + write_erased_any_type_info(context, concrete_type_id)? } else { - context - .get_type_info(&concrete_type_id)? - .get_harness() - .get_write_data_fn() + get_erased_any_type_info(context, &concrete_type_id)? }; + let write_data_fn = typeinfo.get_harness().get_write_data_fn(); write_data_fn(&**self, context, has_generics)?; } Ok(()) @@ -298,10 +359,9 @@ impl Serializer for Rc { } fn fory_read_data(_: &mut ReadContext) -> Result { - Err(Error::not_allowed(format!( - "fory_read_data should not be called directly on polymorphic Rc trait object", - stringify!($trait_name) - ))) + Err(Error::not_allowed( + "fory_read_data should not be called directly on polymorphic Rc trait object", + )) } fn fory_get_type_id(_: &TypeResolver) -> Result { @@ -321,14 +381,6 @@ impl Serializer for Rc { fn fory_is_shared_ref() -> bool { true } - - fn fory_is_threadsafe_type() -> bool - where - Self: Sized, - { - true - } - fn fory_is_polymorphic() -> bool { true } @@ -381,8 +433,7 @@ pub fn read_rc_any( } else { type_info.ok_or_else(|| Error::type_error("No type info found for read"))? }; - // Check for generic container types which cannot be deserialized polymorphically - check_generic_container_type(&typeinfo)?; + check_erased_any_payload_type(&typeinfo)?; let boxed = typeinfo .get_harness() .read_polymorphic_data(context, &typeinfo)?; @@ -390,20 +441,20 @@ pub fn read_rc_any( Ok(Rc::::from(boxed)) } RefFlag::RefValue => { + let ref_id = context.ref_reader.reserve_ref_id(); context.inc_depth()?; let typeinfo = if read_type_info { context.read_any_type_info()? } else { type_info.ok_or_else(|| Error::type_error("No type info found for read"))? }; - // Check for generic container types which cannot be deserialized polymorphically - check_generic_container_type(&typeinfo)?; + check_erased_any_payload_type(&typeinfo)?; let boxed = typeinfo .get_harness() .read_polymorphic_data(context, &typeinfo)?; context.dec_depth(); let rc: Rc = Rc::from(boxed); - context.ref_reader.store_rc_ref(rc.clone()); + context.ref_reader.store_rc_ref_at(ref_id, rc.clone()); Ok(rc) } } @@ -430,18 +481,13 @@ impl Serializer for Arc { { let value: &dyn Any = self.as_ref(); let concrete_type_id: std::any::TypeId = value.type_id(); - if write_type_info { - let typeinfo = - context.write_any_type_info(TypeId::UNKNOWN as u32, concrete_type_id)?; - let serializer_fn = typeinfo.get_harness().get_write_data_fn(); - serializer_fn(value, context, has_generics)?; + let typeinfo = if write_type_info { + write_erased_any_type_info(context, concrete_type_id)? } else { - let serializer_fn = context - .get_type_info(&concrete_type_id)? - .get_harness() - .get_write_data_fn(); - serializer_fn(value, context, has_generics)?; - } + get_erased_any_type_info(context, &concrete_type_id)? + }; + let serializer_fn = typeinfo.get_harness().get_write_data_fn(); + serializer_fn(value, context, has_generics)?; } Ok(()) } @@ -504,7 +550,6 @@ impl Serializer for Arc { fn fory_is_shared_ref() -> bool { true } - fn fory_static_type_id() -> TypeId { TypeId::UNKNOWN } @@ -519,7 +564,7 @@ impl Serializer for Arc { Ok(()) } - fn fory_read_data_send_sync( + fn fory_read_data_as_send_sync_any( context: &mut ReadContext, ) -> Result, Error> where @@ -574,15 +619,15 @@ pub fn read_arc_any( Error::type_error("No type info found for read Arc") })? }; - // Check for generic container types which cannot be deserialized polymorphically - check_generic_container_type(&typeinfo)?; + check_erased_any_payload_type(&typeinfo)?; let boxed = typeinfo .get_harness() - .read_polymorphic_data_send_sync(context, &typeinfo)?; + .read_polymorphic_data_as_send_sync_any(context, &typeinfo)?; context.dec_depth(); Ok(Arc::::from(boxed)) } RefFlag::RefValue => { + let ref_id = context.ref_reader.reserve_ref_id(); context.inc_depth()?; let typeinfo = if read_type_info { context.read_any_type_info()? @@ -591,14 +636,13 @@ pub fn read_arc_any( Error::type_error("No type info found for read Arc") })? }; - // Check for generic container types which cannot be deserialized polymorphically - check_generic_container_type(&typeinfo)?; + check_erased_any_payload_type(&typeinfo)?; let boxed = typeinfo .get_harness() - .read_polymorphic_data_send_sync(context, &typeinfo)?; + .read_polymorphic_data_as_send_sync_any(context, &typeinfo)?; context.dec_depth(); let arc: Arc = Arc::from(boxed); - context.ref_reader.store_arc_ref(arc.clone()); + context.ref_reader.store_arc_ref_at(ref_id, arc.clone()); Ok(arc) } } diff --git a/rust/fory-core/src/serializer/arc.rs b/rust/fory-core/src/serializer/arc.rs index 5535dc7063..1e69ae15c3 100644 --- a/rust/fory-core/src/serializer/arc.rs +++ b/rust/fory-core/src/serializer/arc.rs @@ -28,15 +28,6 @@ impl Serializer for Arc fn fory_is_shared_ref() -> bool { true } - - #[inline(always)] - fn fory_is_threadsafe_type() -> bool - where - Self: Sized, - { - true - } - fn fory_write( &self, context: &mut WriteContext, @@ -129,7 +120,7 @@ impl Serializer for Arc } #[inline] - fn fory_read_data_send_sync( + fn fory_read_data_as_send_sync_any( context: &mut ReadContext, ) -> Result, Error> where diff --git a/rust/fory-core/src/serializer/bool.rs b/rust/fory-core/src/serializer/bool.rs index 09307e168c..dcc7bfbfd5 100644 --- a/rust/fory-core/src/serializer/bool.rs +++ b/rust/fory-core/src/serializer/bool.rs @@ -35,6 +35,17 @@ impl Serializer for bool { fn fory_read_data(context: &mut ReadContext) -> Result { Ok(context.reader.read_u8()? == 1) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { diff --git a/rust/fory-core/src/serializer/core.rs b/rust/fory-core/src/serializer/core.rs index 25a9f0c94e..2f155792d1 100644 --- a/rust/fory-core/src/serializer/core.rs +++ b/rust/fory-core/src/serializer/core.rs @@ -890,33 +890,20 @@ pub trait Serializer: 'static { where Self: Sized + ForyDefault; - /// Whether this serialized value can be safely represented behind - /// `Arc` after a dynamic read. - /// - /// The default is conservative. Implementations should return true only - /// when the concrete value produced by this serializer is `Send + Sync`. - #[inline(always)] - fn fory_is_threadsafe_type() -> bool - where - Self: Sized, - { - false - } - - /// Deserialize data for dynamic thread-safe carriers. + /// Deserialize data for dynamic send-sync carriers. /// /// This method must construct the `Box` from the /// concrete value before it is erased as `dyn Any`. It must never try to /// upgrade a `Box` returned by the ordinary dynamic read path. #[inline(always)] - fn fory_read_data_send_sync( + fn fory_read_data_as_send_sync_any( context: &mut ReadContext, ) -> Result, Error> where Self: Sized + ForyDefault, { let _ = context; - Err(unsupported_threadsafe_type::()) + Err(crate::error::unsupported_send_sync_type::()) } /// Read and validate type metadata from the buffer. @@ -1467,12 +1454,12 @@ pub trait StructSerializer: Serializer + 'static { where Self: Sized; - /// Deserialize compatible data for dynamic thread-safe carriers. + /// Deserialize compatible data for dynamic send-sync carriers. /// /// Implementations are generated only when the resulting struct/enum is /// known to be `Send + Sync`. #[inline(always)] - fn fory_read_compatible_send_sync( + fn fory_read_compatible_as_send_sync_any( context: &mut ReadContext, type_info: Rc, ) -> Result, Error> @@ -1481,7 +1468,7 @@ pub trait StructSerializer: Serializer + 'static { { let _ = context; let _ = type_info; - Err(unsupported_threadsafe_type::()) + Err(crate::error::unsupported_send_sync_type::()) } } @@ -1502,175 +1489,3 @@ pub fn write_data(this: &T, context: &mut WriteContext) -> Result pub fn read_data(context: &mut ReadContext) -> Result { T::fory_read_data(context) } - -#[inline(always)] -pub fn box_send_sync(value: T) -> Box -where - T: Any + Send + Sync, -{ - Box::new(value) -} - -#[inline(always)] -pub(crate) fn is_known_threadsafe_static_type_id(type_id: TypeId) -> bool { - matches!( - type_id, - TypeId::NONE - | TypeId::BOOL - | TypeId::INT8 - | TypeId::INT16 - | TypeId::INT32 - | TypeId::VARINT32 - | TypeId::INT64 - | TypeId::VARINT64 - | TypeId::TAGGED_INT64 - | TypeId::UINT8 - | TypeId::UINT16 - | TypeId::UINT32 - | TypeId::VAR_UINT32 - | TypeId::UINT64 - | TypeId::VAR_UINT64 - | TypeId::TAGGED_UINT64 - | TypeId::FLOAT16 - | TypeId::BFLOAT16 - | TypeId::FLOAT32 - | TypeId::FLOAT64 - | TypeId::STRING - | TypeId::DURATION - | TypeId::TIMESTAMP - | TypeId::DATE - | TypeId::DECIMAL - | TypeId::BINARY - | TypeId::BOOL_ARRAY - | TypeId::INT8_ARRAY - | TypeId::INT16_ARRAY - | TypeId::INT32_ARRAY - | TypeId::INT64_ARRAY - | TypeId::UINT8_ARRAY - | TypeId::UINT16_ARRAY - | TypeId::UINT32_ARRAY - | TypeId::UINT64_ARRAY - | TypeId::FLOAT16_ARRAY - | TypeId::BFLOAT16_ARRAY - | TypeId::FLOAT32_ARRAY - | TypeId::FLOAT64_ARRAY - | TypeId::U128 - | TypeId::INT128 - | TypeId::USIZE - | TypeId::ISIZE - | TypeId::U128_ARRAY - | TypeId::INT128_ARRAY - | TypeId::USIZE_ARRAY - | TypeId::ISIZE_ARRAY - ) -} - -pub(crate) fn read_known_threadsafe_data( - context: &mut ReadContext, -) -> Result, Error> -where - T: Serializer + ForyDefault, -{ - let boxed: Box = Box::new(T::fory_read_data(context)?); - box_known_threadsafe_data(T::fory_static_type_id(), boxed) -} - -#[cold] -#[inline(never)] -fn unexpected_threadsafe_type_id(type_id: TypeId) -> Error { - Error::type_error(format!( - "deserialized value did not match thread-safe static type id {:?}", - type_id - )) -} - -macro_rules! downcast_threadsafe_data { - ($boxed:expr, $type_id:expr, $ty:ty) => { - $boxed - .downcast::<$ty>() - .map(|value| value as Box) - .map_err(|_| unexpected_threadsafe_type_id($type_id)) - }; -} - -fn box_known_threadsafe_data( - type_id: TypeId, - boxed: Box, -) -> Result, Error> { - match type_id { - TypeId::NONE => downcast_threadsafe_data!(boxed, type_id, ()), - TypeId::BOOL => downcast_threadsafe_data!(boxed, type_id, bool), - TypeId::INT8 => downcast_threadsafe_data!(boxed, type_id, i8), - TypeId::INT16 => downcast_threadsafe_data!(boxed, type_id, i16), - TypeId::INT32 | TypeId::VARINT32 => downcast_threadsafe_data!(boxed, type_id, i32), - TypeId::INT64 | TypeId::VARINT64 | TypeId::TAGGED_INT64 => { - downcast_threadsafe_data!(boxed, type_id, i64) - } - TypeId::UINT8 => downcast_threadsafe_data!(boxed, type_id, u8), - TypeId::UINT16 => downcast_threadsafe_data!(boxed, type_id, u16), - TypeId::UINT32 | TypeId::VAR_UINT32 => downcast_threadsafe_data!(boxed, type_id, u32), - TypeId::UINT64 | TypeId::VAR_UINT64 | TypeId::TAGGED_UINT64 => { - downcast_threadsafe_data!(boxed, type_id, u64) - } - TypeId::FLOAT16 => { - downcast_threadsafe_data!(boxed, type_id, crate::types::float16::float16) - } - TypeId::BFLOAT16 => { - downcast_threadsafe_data!(boxed, type_id, crate::types::bfloat16::bfloat16) - } - TypeId::FLOAT32 => downcast_threadsafe_data!(boxed, type_id, f32), - TypeId::FLOAT64 => downcast_threadsafe_data!(boxed, type_id, f64), - TypeId::STRING => downcast_threadsafe_data!(boxed, type_id, String), - TypeId::DURATION => downcast_threadsafe_data!(boxed, type_id, crate::types::Duration), - TypeId::TIMESTAMP => downcast_threadsafe_data!(boxed, type_id, crate::types::Timestamp), - TypeId::DATE => downcast_threadsafe_data!(boxed, type_id, crate::types::Date), - TypeId::DECIMAL => downcast_threadsafe_data!(boxed, type_id, crate::types::Decimal), - TypeId::BINARY | TypeId::UINT8_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::BOOL_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::INT8_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::INT16_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::INT32_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::INT64_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::UINT16_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::UINT32_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::UINT64_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::FLOAT16_ARRAY => { - downcast_threadsafe_data!(boxed, type_id, Vec) - } - TypeId::BFLOAT16_ARRAY => { - downcast_threadsafe_data!(boxed, type_id, Vec) - } - TypeId::FLOAT32_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::FLOAT64_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::U128 => downcast_threadsafe_data!(boxed, type_id, u128), - TypeId::INT128 => downcast_threadsafe_data!(boxed, type_id, i128), - TypeId::USIZE => downcast_threadsafe_data!(boxed, type_id, usize), - TypeId::ISIZE => downcast_threadsafe_data!(boxed, type_id, isize), - TypeId::U128_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::INT128_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::USIZE_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - TypeId::ISIZE_ARRAY => downcast_threadsafe_data!(boxed, type_id, Vec), - _ => Err(unsupported_threadsafe_type_id(type_id)), - } -} - -#[cold] -#[inline(never)] -pub(crate) fn unsupported_threadsafe_type_id(type_id: TypeId) -> Error { - Error::type_error(format!( - "{:?} cannot be represented as Arc", - type_id - )) -} - -#[cold] -#[inline(never)] -pub fn unsupported_threadsafe_type() -> Error -where - T: ?Sized, -{ - Error::type_error(format!( - "{} cannot be represented as Arc", - std::any::type_name::() - )) -} diff --git a/rust/fory-core/src/serializer/datetime.rs b/rust/fory-core/src/serializer/datetime.rs index dec6eaddba..3edb169b30 100644 --- a/rust/fory-core/src/serializer/datetime.rs +++ b/rust/fory-core/src/serializer/datetime.rs @@ -40,6 +40,17 @@ impl Serializer for Timestamp { let nanos = context.reader.read_u32()?; Timestamp::new(seconds, nanos) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { @@ -109,6 +120,17 @@ impl Serializer for Date { }; Ok(Date::from_epoch_days(days)) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { @@ -168,6 +190,17 @@ impl Serializer for Duration { let nanos = context.reader.read_i32()?; Duration::new(seconds, nanos) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { @@ -228,6 +261,17 @@ mod chrono_support { fn fory_read_data(context: &mut ReadContext) -> Result { Timestamp::fory_read_data(context)?.try_into() } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { @@ -282,6 +326,17 @@ mod chrono_support { fn fory_read_data(context: &mut ReadContext) -> Result { Date::fory_read_data(context)?.try_into() } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { @@ -336,6 +391,17 @@ mod chrono_support { fn fory_read_data(context: &mut ReadContext) -> Result { Duration::fory_read_data(context)?.try_into() } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { diff --git a/rust/fory-core/src/serializer/decimal.rs b/rust/fory-core/src/serializer/decimal.rs index 4775df108f..0c1f88ece9 100644 --- a/rust/fory-core/src/serializer/decimal.rs +++ b/rust/fory-core/src/serializer/decimal.rs @@ -39,6 +39,17 @@ impl Serializer for Decimal { let unscaled = read_decimal_unscaled(&mut context.reader)?; Ok(Self { unscaled, scale }) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_get_type_id(_: &TypeResolver) -> Result { diff --git a/rust/fory-core/src/serializer/mod.rs b/rust/fory-core/src/serializer/mod.rs index 5de0bc095e..5e18880c3a 100644 --- a/rust/fory-core/src/serializer/mod.rs +++ b/rust/fory-core/src/serializer/mod.rs @@ -50,8 +50,5 @@ pub mod weak; mod core; mod decimal; pub use any::{read_box_any, write_box_any}; -pub use core::{ - box_send_sync, read_data, unsupported_threadsafe_type, write_data, ForyDefault, Serializer, - StructSerializer, -}; -pub(crate) use core::{is_known_threadsafe_static_type_id, read_known_threadsafe_data}; +pub use core::{read_data, write_data, ForyDefault, Serializer, StructSerializer}; +pub use util::send_sync::box_send_sync; diff --git a/rust/fory-core/src/serializer/number.rs b/rust/fory-core/src/serializer/number.rs index d42d4b219d..ebdb83a1fa 100644 --- a/rust/fory-core/src/serializer/number.rs +++ b/rust/fory-core/src/serializer/number.rs @@ -40,6 +40,17 @@ macro_rules! impl_num_serializer { fn fory_read_data(context: &mut ReadContext) -> Result { $reader(&mut context.reader) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { @@ -114,6 +125,17 @@ impl Serializer for float16 { fn fory_read_data(context: &mut ReadContext) -> Result { Reader::read_f16(&mut context.reader) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { std::mem::size_of::() @@ -162,6 +184,17 @@ impl Serializer for bfloat16 { fn fory_read_data(context: &mut ReadContext) -> Result { Reader::read_bf16(&mut context.reader) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { std::mem::size_of::() diff --git a/rust/fory-core/src/serializer/string.rs b/rust/fory-core/src/serializer/string.rs index abbb82d931..79771fd0ce 100644 --- a/rust/fory-core/src/serializer/string.rs +++ b/rust/fory-core/src/serializer/string.rs @@ -66,6 +66,17 @@ impl Serializer for String { }?; Ok(s) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { diff --git a/rust/fory-core/src/serializer/tuple.rs b/rust/fory-core/src/serializer/tuple.rs index a0789a9069..bbda7383b2 100644 --- a/rust/fory-core/src/serializer/tuple.rs +++ b/rust/fory-core/src/serializer/tuple.rs @@ -38,6 +38,17 @@ impl Serializer for () { // Unit type has no data to read Ok(()) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { diff --git a/rust/fory-core/src/serializer/unknown_case.rs b/rust/fory-core/src/serializer/unknown_case.rs index 7423f384e9..15a658a828 100644 --- a/rust/fory-core/src/serializer/unknown_case.rs +++ b/rust/fory-core/src/serializer/unknown_case.rs @@ -18,7 +18,7 @@ use crate::context::{ReadContext, WriteContext}; use crate::error::Error; use crate::resolver::{RefFlag, RefMode}; -use crate::serializer::any::check_generic_container_type; +use crate::serializer::any::check_erased_any_payload_type; use crate::serializer::{ForyDefault, Serializer}; use crate::type_id::{self, TypeId}; use crate::types::UnknownCase; @@ -154,10 +154,10 @@ pub fn read_payload(context: &mut ReadContext, case_id: u32) -> Result = Arc::from(boxed); if let Some(ref_id) = ref_id { context.ref_reader.store_arc_ref_at(ref_id, value.clone()); @@ -207,12 +207,7 @@ impl Serializer for UnknownCase { fn fory_read_data(context: &mut ReadContext) -> Result { read_payload(context, 0) } - - fn fory_is_threadsafe_type() -> bool { - true - } - - fn fory_read_data_send_sync( + fn fory_read_data_as_send_sync_any( context: &mut ReadContext, ) -> Result, Error> { Ok(crate::serializer::box_send_sync(read_payload(context, 0)?)) diff --git a/rust/fory-core/src/serializer/unsigned_number.rs b/rust/fory-core/src/serializer/unsigned_number.rs index e5f49d0084..6c679df7f9 100644 --- a/rust/fory-core/src/serializer/unsigned_number.rs +++ b/rust/fory-core/src/serializer/unsigned_number.rs @@ -38,6 +38,17 @@ macro_rules! impl_xlang_unsigned_num_serializer { fn fory_read_data(context: &mut ReadContext) -> Result { $reader(&mut context.reader) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { @@ -104,6 +115,17 @@ macro_rules! impl_rust_unsigned_num_serializer { fn fory_read_data(context: &mut ReadContext) -> Result { $reader(&mut context.reader) } + #[inline] + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> + where + Self: Sized + ForyDefault, + { + Ok(crate::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } #[inline(always)] fn fory_reserved_space() -> usize { diff --git a/rust/fory-core/src/serializer/util.rs b/rust/fory-core/src/serializer/util.rs index d339cc9725..559713261a 100644 --- a/rust/fory-core/src/serializer/util.rs +++ b/rust/fory-core/src/serializer/util.rs @@ -95,3 +95,15 @@ pub fn write_dyn_data_generic( .get_write_data_fn(); serializer_fn(any_value, context, has_generics) } + +pub(crate) mod send_sync { + use std::any::Any; + + #[inline(always)] + pub fn box_send_sync(value: T) -> Box + where + T: Any + Send + Sync, + { + Box::new(value) + } +} diff --git a/rust/fory-core/src/types/unknown_case.rs b/rust/fory-core/src/types/unknown_case.rs index 0496ef4f43..cc9a8c5e3d 100644 --- a/rust/fory-core/src/types/unknown_case.rs +++ b/rust/fory-core/src/types/unknown_case.rs @@ -27,7 +27,7 @@ pub struct UnknownCase { type_id: u32, // Keep resolver TypeInfo/Rc out of the carrier. Generated unions can outlive or move // independently from the resolver context, so the carrier stores only stable metadata - // plus a dynamic payload whose thread-safety is guaranteed by the trait object. + // plus a dynamic payload whose `Send + Sync` guarantee is carried by the trait object. value: Arc, } diff --git a/rust/fory-derive/src/lib.rs b/rust/fory-derive/src/lib.rs index 932c9c15a3..b337e5b3ab 100644 --- a/rust/fory-derive/src/lib.rs +++ b/rust/fory-derive/src/lib.rs @@ -143,6 +143,11 @@ //! **Custom Types:** //! - Any type that implements `Serializer` (for `Fory`) or `Row` (for `ForyRow`) //! +//! Derived structs, enums, and unions can be used behind +//! `Arc` when the concrete type satisfies `Send + Sync`. +//! Known non-`Send + Sync` field types such as `Rc` and `RefCell` are not +//! eligible for that carrier. +//! //! ## Usage with Fory //! //! After deriving the macros, you can use the types with the Fory serialization @@ -340,6 +345,8 @@ fn parse_fory_attrs(attrs: &[Attribute]) -> syn::Result { Some(_) => evolving_flag, None => Some(value), }; + } else { + return Err(meta.error("unsupported type-level fory attribute")); } Ok(()) })?; diff --git a/rust/fory-derive/src/object/field_codec.rs b/rust/fory-derive/src/object/field_codec.rs index 8543e044de..f310f4600c 100644 --- a/rust/fory-derive/src/object/field_codec.rs +++ b/rust/fory-derive/src/object/field_codec.rs @@ -766,7 +766,7 @@ pub(crate) fn codec_type_for( ); } return Ok(quote! { - compile_error!("Arc is not a shared thread-safe carrier; use Arc") + compile_error!("Arc is not a shared Send + Sync carrier; use Arc") }); } diff --git a/rust/fory-derive/src/object/serializer.rs b/rust/fory-derive/src/object/serializer.rs index 5721dd9dc9..9dc9dcd8cd 100644 --- a/rust/fory-derive/src/object/serializer.rs +++ b/rust/fory-derive/src/object/serializer.rs @@ -52,8 +52,8 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea } else { quote! {} }; - let threadsafe_tokens = generate_threadsafe_tokens(ast); - let serializer_threadsafe_ts = threadsafe_tokens.serializer.clone(); + let send_sync_tokens = generate_send_sync_tokens(ast); + let serializer_send_sync_ts = send_sync_tokens.serializer.clone(); // StructSerializer let ( @@ -62,7 +62,7 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea fields_info_ts, variants_fields_info_ts, read_compatible_ts, - read_compatible_send_sync_ts, + read_compatible_as_send_sync_any_ts, enum_variant_meta_types, ) = match &ast.data { syn::Data::Struct(s) => { @@ -79,7 +79,7 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea misc::gen_field_fields_info(&source_fields), quote! { ::std::result::Result::Ok(::std::vec::Vec::new()) }, // No variants for structs read::gen_read_compatible(&source_fields), - threadsafe_tokens.struct_read_compatible.clone(), + send_sync_tokens.struct_read_compatible.clone(), vec![], // No variant meta types for structs ) } @@ -185,7 +185,7 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea #read_compatible_ts } - #read_compatible_send_sync_ts + #read_compatible_as_send_sync_any_ts } impl #impl_generics ::fory_core::Serializer for #name #ty_generics #where_clause { @@ -250,7 +250,7 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea #read_data_ts } - #serializer_threadsafe_ts + #serializer_send_sync_ts #[inline(always)] fn fory_read_type_info(context: &mut ::fory_core::ReadContext) -> ::std::result::Result<(), ::fory_core::error::Error> { @@ -263,14 +263,14 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea code } -struct ThreadsafeTokens { +struct SendSyncTokens { serializer: proc_macro2::TokenStream, struct_read_compatible: proc_macro2::TokenStream, } -fn generate_threadsafe_tokens(ast: &syn::DeriveInput) -> ThreadsafeTokens { - if !derive_type_is_threadsafe(ast) { - return ThreadsafeTokens { +fn generate_send_sync_tokens(ast: &syn::DeriveInput) -> SendSyncTokens { + if !derive_type_is_send_sync(ast) { + return SendSyncTokens { serializer: quote! {}, struct_read_compatible: quote! {}, }; @@ -278,7 +278,7 @@ fn generate_threadsafe_tokens(ast: &syn::DeriveInput) -> ThreadsafeTokens { let struct_read_compatible = if matches!(ast.data, syn::Data::Struct(_)) { quote! { #[inline] - fn fory_read_compatible_send_sync( + fn fory_read_compatible_as_send_sync_any( context: &mut ::fory_core::ReadContext, type_info: ::std::rc::Rc<::fory_core::TypeInfo>, ) -> ::std::result::Result<::std::boxed::Box, ::fory_core::error::Error> { @@ -289,18 +289,10 @@ fn generate_threadsafe_tokens(ast: &syn::DeriveInput) -> ThreadsafeTokens { } else { quote! {} }; - ThreadsafeTokens { + SendSyncTokens { serializer: quote! { - #[inline(always)] - fn fory_is_threadsafe_type() -> bool - where - Self: Sized, - { - true - } - #[inline] - fn fory_read_data_send_sync( + fn fory_read_data_as_send_sync_any( context: &mut ::fory_core::ReadContext, ) -> ::std::result::Result<::std::boxed::Box, ::fory_core::error::Error> where @@ -314,14 +306,14 @@ fn generate_threadsafe_tokens(ast: &syn::DeriveInput) -> ThreadsafeTokens { } } -fn derive_type_is_threadsafe(ast: &syn::DeriveInput) -> bool { +fn derive_type_is_send_sync(ast: &syn::DeriveInput) -> bool { use crate::object::util::{ - all_type_params_send_sync, type_is_threadsafe, type_param_send_sync_bounds, + all_type_params_send_sync, type_is_send_sync, type_param_send_sync_bounds, }; - // This is a syntactic filter for generating the send-sync reader. The - // generated reader still boxes `Self`, so Rust enforces the final - // `Send + Sync` invariant for nested user-defined field types. + // This syntactic filter rejects field types that are known not to satisfy + // `Send + Sync`. Opaque custom field types are allowed through, and Rust + // validates the final `Self: Send + Sync` bound when compiling the reader. if !all_type_params_send_sync(&ast.generics) { return false; } @@ -330,12 +322,12 @@ fn derive_type_is_threadsafe(ast: &syn::DeriveInput) -> bool { syn::Data::Struct(data) => data .fields .iter() - .all(|field| type_is_threadsafe(&field.ty, &send_sync_params)), + .all(|field| type_is_send_sync(&field.ty, &send_sync_params)), syn::Data::Enum(data) => data.variants.iter().all(|variant| { variant .fields .iter() - .all(|field| type_is_threadsafe(&field.ty, &send_sync_params)) + .all(|field| type_is_send_sync(&field.ty, &send_sync_params)) }), syn::Data::Union(_) => false, } diff --git a/rust/fory-derive/src/object/util.rs b/rust/fory-derive/src/object/util.rs index 4fc38b8936..260159b826 100644 --- a/rust/fory-derive/src/object/util.rs +++ b/rust/fory-derive/src/object/util.rs @@ -963,13 +963,13 @@ pub(crate) fn all_type_params_send_sync(generics: &syn::Generics) -> bool { .all(|param| bounded.contains(¶m.ident.to_string())) } -pub(crate) fn type_is_threadsafe(ty: &Type, send_sync_params: &HashSet) -> bool { +pub(crate) fn type_is_send_sync(ty: &Type, send_sync_params: &HashSet) -> bool { match ty { - Type::Array(array) => type_is_threadsafe(array.elem.as_ref(), send_sync_params), + Type::Array(array) => type_is_send_sync(array.elem.as_ref(), send_sync_params), Type::Tuple(tuple) => tuple .elems .iter() - .all(|elem| type_is_threadsafe(elem, send_sync_params)), + .all(|elem| type_is_send_sync(elem, send_sync_params)), Type::Path(type_path) => { let Some(segment) = type_path.path.segments.last() else { return false; @@ -996,15 +996,15 @@ pub(crate) fn type_is_threadsafe(ty: &Type, send_sync_params: &HashSet) trait_object_is_any_send_sync(trait_obj) } (_, Type::TraitObject(_)) => false, - _ => type_is_threadsafe(inner, send_sync_params), + _ => type_is_send_sync(inner, send_sync_params), } } "HashMap" | "BTreeMap" => { let Some((key, value)) = two_path_type_args(&segment.arguments) else { return false; }; - type_is_threadsafe(key, send_sync_params) - && type_is_threadsafe(value, send_sync_params) + type_is_send_sync(key, send_sync_params) + && type_is_send_sync(value, send_sync_params) } _ => true, } diff --git a/rust/fory/src/lib.rs b/rust/fory/src/lib.rs index e190d0c8dd..70327c2a9a 100644 --- a/rust/fory/src/lib.rs +++ b/rust/fory/src/lib.rs @@ -340,7 +340,8 @@ //! - `Box` - Owned trait objects //! - `Rc` - Reference-counted trait objects //! - `Arc` - Thread-safe reference-counted trait objects -//! - `Rc` / `Arc` - Runtime type dispatch without custom traits +//! - `Box` / `Rc` / `Arc` - Runtime type +//! dispatch without custom traits for registered non-container payloads //! - Collections: `Vec>`, `HashMap>` //! //! #### Basic Trait Object Serialization @@ -401,7 +402,7 @@ //! //! #### Serializing `dyn Any` Trait Objects //! -//! **What it does:** Supports serializing `Rc` and +//! **What it does:** Supports serializing `Box`, `Rc`, and //! `Arc` for maximum runtime type flexibility without //! defining custom traits. //! @@ -410,7 +411,7 @@ //! //! **Key points:** //! -//! - Works with any type that implements `Serializer` +//! - Works with registered concrete non-container types that implement `Serializer` //! - Requires downcasting after deserialization to access the concrete type //! - Type information is preserved during serialization //! @@ -470,6 +471,18 @@ //! # } //! ``` //! +//! `Box`, `Rc`, and `Arc` are supported +//! erased `Any` carriers for registered concrete non-container payloads. +//! Use `Arc` when the erased payload must be shareable +//! across threads; the concrete payload type must also satisfy `Send + Sync`. +//! Registered structs, enums, and unions that satisfy those bounds can be used +//! as the erased payload. +//! Generic containers such as `Vec`, `HashMap`, `HashSet`, and +//! `LinkedList` are not supported directly as top-level erased `Any` +//! payloads behind any of those carriers. This also includes primitive vector +//! encodings such as `Vec`. Wrap the container in a registered derived type +//! when it needs to travel behind an erased `Any` carrier. +//! //! #### Rc/Arc-Based Trait Objects in Structs //! //! For struct fields containing `Rc` or `Arc`, Apache Fory™ @@ -536,8 +549,9 @@ //! `Serializer` directly. For standalone serialization (not inside struct fields), //! the `register_trait_type!` macro generates wrapper types. //! -//! **Note:** If you don't want to use wrapper types, you can serialize as `Rc` -//! or `Arc` instead (see the `dyn Any` section above). +//! **Note:** If you don't want to use wrapper types for concrete non-container payloads, +//! you can serialize as `Box`, `Rc`, or +//! `Arc` instead (see the `dyn Any` section above). //! //! The `register_trait_type!` macro generates `AnimalRc` and `AnimalArc` wrapper types: //! @@ -1005,8 +1019,11 @@ //! - `Box` - Owned trait objects //! - `Rc` - Reference-counted trait objects //! - `Arc` - Thread-safe reference-counted trait objects -//! - `Rc` - Runtime type dispatch without custom traits -//! - `Arc` - Thread-safe runtime type dispatch +//! - `Box` - Runtime type dispatch for registered non-container payloads +//! - `Rc` - Reference-counted runtime type dispatch for registered +//! non-container payloads +//! - `Arc` - Thread-safe runtime type dispatch for +//! registered non-container payloads //! //! ## Wire Modes And Schema Evolution //! diff --git a/rust/tests/tests/test_any.rs b/rust/tests/tests/test_any.rs index d7c4a7b574..8bc978c522 100644 --- a/rust/tests/tests/test_any.rs +++ b/rust/tests/tests/test_any.rs @@ -16,13 +16,113 @@ // under the License. use fory_core::fory::Fory; -use fory_derive::ForyStruct; +use fory_derive::{ForyEnum, ForyStruct, ForyUnion}; use std::any::Any; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet, LinkedList}; use std::rc::Rc; use std::sync::Arc; use std::vec; +fn assert_erased_container_error(message: String) { + assert!( + message.contains("top-level erased Any") + || message.contains("Erased Any payloads require") + || message.contains("not found in type_info registry"), + "unexpected error: {message}" + ); +} + +fn assert_box_any_unsupported(fory: &Fory, value: T) { + let wrapped: Box = Box::new(value); + match fory.serialize(&wrapped) { + Ok(bytes) => { + let result: Result, _> = fory.deserialize(&bytes); + let err = result.expect_err("expected Box container read to fail"); + assert_erased_container_error(err.to_string()); + } + Err(err) => assert_erased_container_error(err.to_string()), + } +} + +fn assert_rc_any_unsupported(fory: &Fory, value: T) { + let wrapped: Rc = Rc::new(value); + match fory.serialize(&wrapped) { + Ok(bytes) => { + let result: Result, _> = fory.deserialize(&bytes); + let err = result.expect_err("expected Rc container read to fail"); + assert_erased_container_error(err.to_string()); + } + Err(err) => assert_erased_container_error(err.to_string()), + } +} + +fn assert_arc_any_unsupported(fory: &Fory, value: T) { + let wrapped: Arc = Arc::new(value); + match fory.serialize(&wrapped) { + Ok(bytes) => { + let result: Result, _> = fory.deserialize(&bytes); + let err = result.expect_err("expected Arc container read to fail"); + assert_erased_container_error(err.to_string()); + } + Err(err) => assert_erased_container_error(err.to_string()), + } +} + +fn assert_box_any_values_unsupported(fory: &Fory, values: Vec>) { + let result = fory + .serialize(&values) + .and_then(|bytes| fory.deserialize::>>(&bytes).map(|_| ())); + let err = result.expect_err("expected Box container values to fail"); + assert_erased_container_error(err.to_string()); +} + +fn assert_rc_any_values_unsupported(fory: &Fory, values: Vec>) { + let result = fory + .serialize(&values) + .and_then(|bytes| fory.deserialize::>>(&bytes).map(|_| ())); + let err = result.expect_err("expected Rc container values to fail"); + assert_erased_container_error(err.to_string()); +} + +fn assert_arc_any_values_unsupported(fory: &Fory, values: Vec>) { + let result = fory.serialize(&values).and_then(|bytes| { + fory.deserialize::>>(&bytes) + .map(|_| ()) + }); + let err = result.expect_err("expected Arc container values to fail"); + assert_erased_container_error(err.to_string()); +} + +fn assert_box_any_map_unsupported(fory: &Fory, values: HashMap>) { + let result = fory.serialize(&values).and_then(|bytes| { + fory.deserialize::>>(&bytes) + .map(|_| ()) + }); + let err = result.expect_err("expected Box map values to fail"); + assert_erased_container_error(err.to_string()); +} + +fn assert_rc_any_map_unsupported(fory: &Fory, values: HashMap>) { + let result = fory.serialize(&values).and_then(|bytes| { + fory.deserialize::>>(&bytes) + .map(|_| ()) + }); + let err = result.expect_err("expected Rc map values to fail"); + assert_erased_container_error(err.to_string()); +} + +fn assert_arc_any_map_unsupported( + fory: &Fory, + values: HashMap>, +) { + let result = fory.serialize(&values).and_then(|bytes| { + fory.deserialize::>>(&bytes) + .map(|_| ()) + }); + let err = result.expect_err("expected Arc map values to fail"); + assert_erased_container_error(err.to_string()); +} + #[test] fn test_box_dyn_any() { let fory = Fory::builder().xlang(false).build(); @@ -90,13 +190,10 @@ fn test_arc_dyn_any() { let deserialized2: Arc = fory.deserialize(&bytes2).unwrap(); assert_eq!(deserialized2.downcast_ref::().unwrap(), &123i32); - let value3: Arc = Arc::new(vec![1, 2, 3]); + let value3: Arc = Arc::new(true); let bytes3 = fory.serialize(&value3).unwrap(); let deserialized3: Arc = fory.deserialize(&bytes3).unwrap(); - assert_eq!( - deserialized3.downcast_ref::>().unwrap(), - &vec![1, 2, 3] - ); + assert_eq!(deserialized3.downcast_ref::().unwrap(), &true); } #[test] @@ -122,17 +219,17 @@ fn test_rc_dyn_any_shared_reference() { fn test_arc_dyn_any_shared_reference() { let fory = Fory::builder().xlang(false).build(); - let shared_vec: Arc = Arc::new(vec![1, 2, 3]); + let shared_vec: Arc = Arc::new("shared".to_string()); let data = vec![shared_vec.clone(), shared_vec.clone()]; let bytes = fory.serialize(&data).unwrap(); let deserialized: Vec> = fory.deserialize(&bytes).unwrap(); - let first_vec = deserialized[0].downcast_ref::>().unwrap(); - let second_vec = deserialized[1].downcast_ref::>().unwrap(); - assert_eq!(first_vec, &vec![1, 2, 3]); - assert_eq!(second_vec, &vec![1, 2, 3]); + let first_vec = deserialized[0].downcast_ref::().unwrap(); + let second_vec = deserialized[1].downcast_ref::().unwrap(); + assert_eq!(first_vec, "shared"); + assert_eq!(second_vec, "shared"); assert_eq!(Arc::strong_count(&shared_vec), 3); } @@ -207,6 +304,197 @@ struct Container { items: Vec, } +#[derive(ForyStruct, PartialEq, Debug)] +struct RcRefPayload { + name: String, + shared: Rc, +} + +#[derive(ForyStruct, PartialEq, Debug)] +struct ArcRefPayload { + name: String, + shared: Arc, +} + +#[test] +fn wrapped_container_box_any() { + let mut fory = Fory::builder().xlang(false).build(); + fory.register_by_name::("", "Container").unwrap(); + + let value: Box = Box::new(Container { + id: 321, + items: vec!["wrapped".to_string(), "values".to_string()], + }); + let bytes = fory.serialize(&value).unwrap(); + let decoded: Box = fory.deserialize(&bytes).unwrap(); + let result = decoded.downcast_ref::().unwrap(); + + assert_eq!(result.id, 321); + assert_eq!(result.items, vec!["wrapped", "values"]); +} + +#[test] +fn rc_any_refvalue_keeps_outer_ref() { + let mut fory = Fory::builder().xlang(false).build(); + fory.register_by_name::("", "RcRefPayload") + .unwrap(); + + let payload: Rc = Rc::new(RcRefPayload { + name: "outer".to_string(), + shared: Rc::new("nested".to_string()), + }); + let values = vec![payload.clone(), payload.clone()]; + + let bytes = fory.serialize(&values).unwrap(); + let decoded: Vec> = fory.deserialize(&bytes).unwrap(); + + assert!(Rc::ptr_eq(&decoded[0], &decoded[1])); + let payload = decoded[0].downcast_ref::().unwrap(); + assert_eq!(payload.name, "outer"); + assert_eq!(payload.shared.as_str(), "nested"); +} + +#[test] +fn arc_any_refvalue_keeps_outer_ref() { + let mut fory = Fory::builder().xlang(false).build(); + fory.register_by_name::("", "ArcRefPayload") + .unwrap(); + + let payload: Arc = Arc::new(ArcRefPayload { + name: "outer".to_string(), + shared: Arc::new("nested".to_string()), + }); + let values = vec![payload.clone(), payload.clone()]; + + let bytes = fory.serialize(&values).unwrap(); + let decoded: Vec> = fory.deserialize(&bytes).unwrap(); + + assert!(Arc::ptr_eq(&decoded[0], &decoded[1])); + let payload = decoded[0].downcast_ref::().unwrap(); + assert_eq!(payload.name, "outer"); + assert_eq!(payload.shared.as_str(), "nested"); +} + +#[test] +fn generic_containers_rejected_in_any() { + let fory = Fory::builder().xlang(false).build(); + + assert_box_any_unsupported(&fory, vec![1_i32, 2, 3]); + assert_rc_any_unsupported(&fory, vec![1_i32, 2, 3]); + assert_arc_any_unsupported(&fory, vec![1_i32, 2, 3]); + + assert_box_any_unsupported(&fory, LinkedList::from([1_i32, 2, 3])); + assert_rc_any_unsupported(&fory, HashSet::from([1_i32, 2, 3])); + assert_arc_any_unsupported( + &fory, + HashMap::from([("one".to_string(), 1_i32), ("two".to_string(), 2)]), + ); +} + +#[test] +fn any_collection_rejects_containers() { + let fory = Fory::builder().xlang(false).build(); + + assert_box_any_values_unsupported(&fory, vec![Box::new(vec![1_i32, 2, 3]) as Box]); + assert_box_any_values_unsupported( + &fory, + vec![ + Box::new(vec![1_i32, 2, 3]) as Box, + Box::new(vec![4_i32, 5, 6]) as Box, + ], + ); + + assert_rc_any_values_unsupported(&fory, vec![Rc::new(vec![1_i32, 2, 3]) as Rc]); + assert_rc_any_values_unsupported( + &fory, + vec![ + Rc::new(vec![1_i32, 2, 3]) as Rc, + Rc::new(vec![4_i32, 5, 6]) as Rc, + ], + ); + + assert_arc_any_values_unsupported( + &fory, + vec![Arc::new(vec![1_i32, 2, 3]) as Arc], + ); + assert_arc_any_values_unsupported( + &fory, + vec![ + Arc::new(vec![1_i32, 2, 3]) as Arc, + Arc::new(vec![4_i32, 5, 6]) as Arc, + ], + ); +} + +#[test] +fn any_map_values_reject_containers() { + let fory = Fory::builder().xlang(false).build(); + + assert_box_any_map_unsupported( + &fory, + HashMap::from([( + "list".to_string(), + Box::new(vec![1_i32, 2, 3]) as Box, + )]), + ); + assert_rc_any_map_unsupported( + &fory, + HashMap::from([( + "map".to_string(), + Rc::new(HashMap::from([("one".to_string(), 1_i32)])) as Rc, + )]), + ); + assert_arc_any_map_unsupported( + &fory, + HashMap::from([( + "list".to_string(), + Arc::new(vec![1_i32, 2, 3]) as Arc, + )]), + ); +} + +#[test] +fn compatible_enum_box_any_read() { + #[derive(ForyEnum, Debug, Default, PartialEq)] + enum Status { + #[default] + Active, + Inactive, + } + + let mut fory = Fory::builder().xlang(false).compatible(true).build(); + fory.register::(710).unwrap(); + + let value: Box = Box::new(Status::Inactive); + let bytes = fory.serialize(&value).unwrap(); + let decoded: Box = fory.deserialize(&bytes).unwrap(); + + assert_eq!(decoded.downcast_ref::().unwrap(), &Status::Inactive); +} + +#[test] +fn compatible_union_rc_any_read() { + #[derive(ForyUnion, Debug, PartialEq)] + enum Event { + #[fory(unknown)] + Unknown(fory_core::UnknownCase), + #[fory(id = 0, default)] + Value(String), + } + + let mut fory = Fory::builder().xlang(false).compatible(true).build(); + fory.register_union::(711).unwrap(); + + let value: Rc = Rc::new(Event::Value("compatible".to_string())); + let bytes = fory.serialize(&value).unwrap(); + let decoded: Rc = fory.deserialize(&bytes).unwrap(); + + assert_eq!( + decoded.downcast_ref::().unwrap(), + &Event::Value("compatible".to_string()) + ); +} + #[derive(ForyStruct)] struct ArcAnyHolder { value: Arc, @@ -348,36 +636,18 @@ struct StructB { i: i32, } -/// Test that serializing different Vec types in Box returns a clear error. +/// Test that different Vec types in Box return a clear error. /// -/// This tests for a known limitation of the xlang serialization protocol: -/// different generic container types (Vec, Vec) share the same -/// type ID (LIST=21), so they cannot be distinguished during polymorphic deserialization. +/// Different generic container instantiations share broad container metadata, so +/// they cannot be used as unambiguous top-level erased Any payloads. /// /// Previously this caused non-deterministic failures. Now it returns a clear error message. #[test] -fn test_vec_of_different_struct_types_in_box_any_returns_error() { +fn generic_vecs_rejected_in_box_any() { let mut fory = Fory::builder().xlang(false).build(); - // Register both struct types fory.register_by_name::("", "StructA").unwrap(); - fory.register_generic_trait::>().unwrap(); fory.register_by_name::("", "StructB").unwrap(); - fory.register_generic_trait::>().unwrap(); - - // Create Box wrappers for Vec and Vec - let a_vec: Box = Box::new(vec![StructA { a: 11 }; 5]); - let b_vec: Box = Box::new(vec![StructB { i: 1 }; 5]); - let any_vec: Vec> = vec![a_vec, b_vec]; - let bytes = fory.serialize(&any_vec).unwrap(); - - // Deserialization should fail with a clear error about generic containers - let result: Result>, _> = fory.deserialize(&bytes); - assert!(result.is_err()); - let err_msg = result.unwrap_err().to_string(); - assert!( - err_msg.contains("generic container types"), - "Expected error about generic containers, got: {}", - err_msg - ); + assert_box_any_unsupported(&fory, vec![StructA { a: 11 }; 5]); + assert_box_any_unsupported(&fory, vec![StructB { i: 1 }; 5]); } diff --git a/rust/tests/tests/test_collection.rs b/rust/tests/tests/test_collection.rs index 9271e39ae4..c71e4af20e 100644 --- a/rust/tests/tests/test_collection.rs +++ b/rust/tests/tests/test_collection.rs @@ -15,23 +15,20 @@ // specific language governing permissions and limitations // under the License. -use fory_core::{Fory, Serializer}; +use fory_core::Fory; use fory_derive::ForyStruct; use std::collections::{BTreeSet, BinaryHeap, HashSet}; #[test] fn test_btreeset_roundtrip() { - let mut fory: Fory = Fory::builder().xlang(false).build(); - fory.register_generic_trait::>().unwrap(); + let fory: Fory = Fory::builder().xlang(false).build(); let mut original = BTreeSet::new(); original.insert(1); original.insert(2); original.insert(3); - let trait_obj: Box = Box::new(original.clone()); - let serialized = fory.serialize(&trait_obj).unwrap(); - + let serialized = fory.serialize(&original).unwrap(); let deserialized_concrete: BTreeSet = fory.deserialize(&serialized).unwrap(); assert_eq!(deserialized_concrete.len(), 3); diff --git a/rust/tests/tests/test_enum.rs b/rust/tests/tests/test_enum.rs index 566baff9d8..5398d68a78 100644 --- a/rust/tests/tests/test_enum.rs +++ b/rust/tests/tests/test_enum.rs @@ -223,7 +223,7 @@ fn union_compatible_enum_xlang_format() { } #[test] -fn unknown_case_reads_threadsafe_generated_payload() { +fn unknown_case_reads_send_sync_payload() { use fory_core::ArcWeak; use std::sync::{Arc, Mutex}; diff --git a/rust/tests/tests/test_lifecycle_guard.rs b/rust/tests/tests/test_lifecycle_guard.rs index 56e8eb64bf..7c3c9f80a8 100644 --- a/rust/tests/tests/test_lifecycle_guard.rs +++ b/rust/tests/tests/test_lifecycle_guard.rs @@ -195,19 +195,6 @@ fn test_register_serializer_by_name_with_namespace_after_serialize_fails() { assert!(matches!(err, Error::NotAllowed(_))); } -/// Ensures `register_generic_trait()` is forbidden after snapshot init. -#[test] -fn test_register_generic_trait_after_serialize_fails() { - let mut fory = Fory::builder().xlang(false).build(); - fory.register::(100).unwrap(); - let _bytes = fory.serialize(&Point { x: 0, y: 0 }).unwrap(); - - let err = fory - .register_generic_trait::>() - .expect_err("register_generic_trait after serialize should fail"); - assert!(matches!(err, Error::NotAllowed(_))); -} - /// Ensures `register_union()` is forbidden after snapshot init. #[test] fn test_register_union_after_serialize_fails() { diff --git a/rust/tests/tests/test_send_sync.rs b/rust/tests/tests/test_send_sync.rs new file mode 100644 index 0000000000..d7b5a77731 --- /dev/null +++ b/rust/tests/tests/test_send_sync.rs @@ -0,0 +1,365 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#![allow(dead_code)] + +use fory_core::fory::Fory; +use fory_core::{ + read_data, write_data, Config, Error, ForyDefault, ReadContext, Serializer, TypeResolver, + WriteContext, +}; +use fory_derive::{ForyEnum, ForyStruct, ForyUnion}; +use std::{ + any::Any, + collections::{HashMap, HashSet, LinkedList}, + fmt::Debug, + rc::Rc, + sync::Arc, +}; + +fn assert_arc_any_roundtrip(fory: &Fory, value: T) +where + T: 'static + Clone + Debug + PartialEq + Send + Sync, +{ + let wrapped: Arc = Arc::new(value.clone()); + let bytes = fory.serialize(&wrapped).unwrap(); + let decoded: Arc = fory.deserialize(&bytes).unwrap(); + assert_eq!(decoded.downcast_ref::().unwrap(), &value); +} + +fn assert_arc_any_unsupported(fory: &Fory, value: T) +where + T: 'static + Send + Sync, +{ + let wrapped: Arc = Arc::new(value); + let err = match fory.serialize(&wrapped) { + Ok(bytes) => { + let result: Result, _> = fory.deserialize(&bytes); + match result { + Ok(_) => panic!("expected direct generic container payload to be unsupported"), + Err(err) => err, + } + } + Err(err) => err, + }; + let message = err.to_string(); + assert!( + message.contains("top-level erased Any") + || message.contains("Erased Any payloads require") + || message.contains("cannot be represented as Arc"), + "unexpected error: {err}" + ); +} + +fn assert_send_sync_reader_unsupported() +where + T: Serializer + ForyDefault, +{ + let mut context = ReadContext::new(TypeResolver::default(), Config::default()); + let result = T::fory_read_data_as_send_sync_any(&mut context); + let err = match result { + Ok(_) => panic!("expected send-sync Any reader to be unsupported"), + Err(err) => err, + }; + let message = err.to_string(); + assert!( + message.contains("cannot be represented as Arc"), + "unexpected error: {err}" + ); +} + +#[test] +fn test_builtin_send_sync_arc_any_reads() { + let fory = Fory::builder().xlang(false).build(); + + assert_arc_any_roundtrip(&fory, 42_i32); + assert_arc_any_roundtrip(&fory, true); + assert_arc_any_roundtrip(&fory, "thread-safe".to_string()); +} + +#[test] +fn test_derived_send_sync_arc_any_read() { + #[derive(ForyStruct, Clone, Debug, PartialEq)] + struct Value { + name: String, + count: i32, + } + + let mut fory = Fory::builder().xlang(false).build(); + fory.register::(900).unwrap(); + + assert_arc_any_roundtrip( + &fory, + Value { + name: "derived".to_string(), + count: 7, + }, + ); +} + +#[test] +fn compatible_struct_arc_any_read() { + #[derive(ForyStruct, Clone, Debug, PartialEq)] + struct ValueV1 { + #[fory(id = 0)] + name: String, + #[fory(id = 1)] + count: i32, + } + + #[derive(ForyStruct, Clone, Debug, PartialEq)] + struct ValueV2 { + #[fory(id = 0)] + name: String, + #[fory(id = 1)] + count: i32, + #[fory(id = 2)] + label: String, + } + + let mut writer = Fory::builder().xlang(false).compatible(true).build(); + writer.register::(909).unwrap(); + + let mut reader = Fory::builder().xlang(false).compatible(true).build(); + reader.register::(909).unwrap(); + + let value: Arc = Arc::new(ValueV1 { + name: "compatible".to_string(), + count: 11, + }); + let bytes = writer.serialize(&value).unwrap(); + let decoded: Arc = reader.deserialize(&bytes).unwrap(); + let value = decoded.downcast_ref::().unwrap(); + + assert_eq!(value.name, "compatible"); + assert_eq!(value.count, 11); + assert_eq!(value.label, String::default()); +} + +#[test] +fn wrapped_container_arc_any_read() { + #[derive(ForyStruct, Clone, Debug, PartialEq)] + struct IntList { + values: Vec, + } + + let mut fory = Fory::builder().xlang(false).build(); + fory.register::(901).unwrap(); + + assert_arc_any_roundtrip( + &fory, + IntList { + values: vec![1, 2, 3], + }, + ); +} + +#[test] +fn generic_containers_rejected_arc_any() { + let fory = Fory::builder().xlang(false).build(); + + assert_arc_any_unsupported(&fory, vec![1_i32, 2, 3]); + assert_arc_any_unsupported(&fory, LinkedList::from([1_i32, 2, 3])); + assert_arc_any_unsupported(&fory, HashSet::from([1_i32, 2, 3])); + assert_arc_any_unsupported( + &fory, + HashMap::from([("one".to_string(), 1_i32), ("two".to_string(), 2)]), + ); +} + +#[test] +fn test_auto_send_sync_struct() { + #[derive(ForyStruct, Clone, Debug, PartialEq)] + struct Value { + name: String, + } + + let mut fory = Fory::builder().xlang(false).build(); + fory.register::(902).unwrap(); + + assert_arc_any_roundtrip( + &fory, + Value { + name: "auto".to_string(), + }, + ); +} + +#[test] +fn non_send_sync_carrier_reader_unsupported() { + assert_send_sync_reader_unsupported::>(); +} + +#[test] +fn manual_serializer_arc_any_read() { + #[derive(Clone, Debug, PartialEq)] + struct ManualValue { + id: i32, + name: String, + } + + impl ForyDefault for ManualValue { + fn fory_default() -> Self { + Self { + id: 0, + name: String::new(), + } + } + } + + impl Serializer for ManualValue { + fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error> { + write_data(&self.id, context)?; + write_data(&self.name, context) + } + + fn fory_read_data(context: &mut ReadContext) -> Result { + Ok(Self { + id: read_data(context)?, + name: read_data(context)?, + }) + } + + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> { + Ok(fory_core::serializer::box_send_sync(Self::fory_read_data( + context, + )?)) + } + + fn fory_type_id_dyn( + &self, + type_resolver: &TypeResolver, + ) -> Result { + Self::fory_get_type_id(type_resolver) + } + + fn as_any(&self) -> &dyn Any { + self + } + } + + let mut fory = Fory::builder().xlang(false).build(); + fory.register_serializer::(910).unwrap(); + + assert_arc_any_roundtrip( + &fory, + ManualValue { + id: 7, + name: "manual".to_string(), + }, + ); +} + +#[test] +fn test_nested_custom_default() { + #[derive(ForyStruct, Clone, Debug, PartialEq)] + struct Leaf { + name: String, + } + + #[derive(ForyStruct, Clone, Debug, PartialEq)] + struct Value { + leaf: Leaf, + } + + let mut fory = Fory::builder().xlang(false).build(); + fory.register::(902).unwrap(); + fory.register::(903).unwrap(); + + assert_arc_any_roundtrip( + &fory, + Value { + leaf: Leaf { + name: "nested".to_string(), + }, + }, + ); +} + +#[test] +fn test_known_non_send_sync_struct() { + #[derive(ForyStruct)] + struct Value { + name: Rc, + } + + assert_send_sync_reader_unsupported::(); +} + +#[test] +fn test_send_sync_union() { + #[derive(ForyUnion, Clone, Debug, PartialEq)] + enum Event { + #[fory(unknown)] + Unknown(fory_core::UnknownCase), + #[fory(id = 0, default)] + Value(String), + } + + let mut fory = Fory::builder().xlang(false).build(); + fory.register_union::(905).unwrap(); + + assert_arc_any_roundtrip(&fory, Event::Value("union".to_string())); +} + +#[test] +fn test_send_sync_enum() { + #[derive(ForyEnum, Clone, Debug, Default, PartialEq)] + enum Status { + #[default] + Active, + Inactive, + } + + let mut fory = Fory::builder().xlang(false).build(); + fory.register::(906).unwrap(); + + assert_arc_any_roundtrip(&fory, Status::Inactive); +} + +#[test] +fn compatible_union_arc_any_read() { + #[derive(ForyUnion, Clone, Debug, PartialEq)] + enum Event { + #[fory(unknown)] + Unknown(fory_core::UnknownCase), + #[fory(id = 0, default)] + Value(String), + } + + let mut fory = Fory::builder().xlang(false).compatible(true).build(); + fory.register_union::(907).unwrap(); + + assert_arc_any_roundtrip(&fory, Event::Value("compatible".to_string())); +} + +#[test] +fn compatible_enum_arc_any_read() { + #[derive(ForyEnum, Clone, Debug, Default, PartialEq)] + enum Status { + #[default] + Active, + Inactive, + } + + let mut fory = Fory::builder().xlang(false).compatible(true).build(); + fory.register::(908).unwrap(); + + assert_arc_any_roundtrip(&fory, Status::Inactive); +} diff --git a/rust/tests/tests/test_trait_object.rs b/rust/tests/tests/test_trait_object.rs index 9544c4bdb9..923ae62665 100644 --- a/rust/tests/tests/test_trait_object.rs +++ b/rust/tests/tests/test_trait_object.rs @@ -17,7 +17,9 @@ use fory_core::fory::Fory; use fory_core::register_trait_type; +use fory_core::resolver::RefFlag; use fory_core::serializer::Serializer; +use fory_core::TypeId; use fory_derive::ForyStruct; use std::collections::{HashMap, HashSet}; @@ -25,6 +27,16 @@ fn fory_compatible() -> Fory { Fory::builder().xlang(false).compatible(true).build() } +fn assert_erased_serializer_container_error(err: fory_core::Error) { + let message = err.to_string(); + assert!( + message.contains("Type info for internal type not found") + || message.contains("not found in type_info registry") + || message.contains("ID harness not found"), + "unexpected error: {message}" + ); +} + #[test] fn test_multiple_types_in_sequence() { let fory = fory_compatible(); @@ -74,41 +86,64 @@ fn test_option_some_roundtrip() { } #[test] -fn test_hashmap_roundtrip() { +fn trait_object_map_set_payloads_rejected() { let fory = fory_compatible(); - let mut original = HashMap::new(); - original.insert(String::from("one"), 1); - original.insert(String::from("two"), 2); - original.insert(String::from("three"), 3); - let trait_obj: Box = Box::new(original.clone()); - let serialized = fory.serialize(&trait_obj).unwrap(); - - let deserialized_concrete: HashMap = fory.deserialize(&serialized).unwrap(); - - assert_eq!(deserialized_concrete.len(), 3); - assert_eq!(deserialized_concrete.get("one"), Some(&1)); - assert_eq!(deserialized_concrete.get("two"), Some(&2)); - assert_eq!(deserialized_concrete.get("three"), Some(&3)); + let list: Box = Box::new(vec!["one".to_string(), "two".to_string()]); + assert_erased_serializer_container_error(fory.serialize(&list).unwrap_err()); + + let representative_map: Box = Box::new(HashMap::from([ + ("one".to_string(), 1_i32), + ("two".to_string(), 2), + ])); + assert_erased_serializer_container_error(fory.serialize(&representative_map).unwrap_err()); + + let other_map: Box = Box::new(HashMap::from([ + ("one".to_string(), "first".to_string()), + ("two".to_string(), "second".to_string()), + ])); + assert_erased_serializer_container_error(fory.serialize(&other_map).unwrap_err()); + + let representative_set: Box = Box::new(HashSet::from([1_i32, 2, 3])); + assert_erased_serializer_container_error(fory.serialize(&representative_set).unwrap_err()); + + let other_set: Box = Box::new(HashSet::from([ + "one".to_string(), + "two".to_string(), + "three".to_string(), + ])); + assert_erased_serializer_container_error(fory.serialize(&other_set).unwrap_err()); } #[test] -fn test_hashset_roundtrip() { +fn trait_object_map_values_reject_containers() { let fory = fory_compatible(); - let mut original = HashSet::new(); - original.insert(1); - original.insert(2); - original.insert(3); - let trait_obj: Box = Box::new(original.clone()); - let serialized = fory.serialize(&trait_obj).unwrap(); + let mut values: HashMap> = HashMap::new(); + values.insert( + "map".to_string(), + Box::new(HashMap::from([("one".to_string(), 1_i32)])), + ); + assert_erased_serializer_container_error(fory.serialize(&values).unwrap_err()); + + let mut values: HashMap> = HashMap::new(); + values.insert("set".to_string(), Box::new(HashSet::from([1_i32, 2, 3]))); + assert_erased_serializer_container_error(fory.serialize(&values).unwrap_err()); +} - let deserialized_concrete: HashSet = fory.deserialize(&serialized).unwrap(); +#[test] +fn trait_object_container_type_ids_rejected_on_read() { + let fory = fory_compatible(); - assert_eq!(deserialized_concrete.len(), 3); - assert!(deserialized_concrete.contains(&1)); - assert!(deserialized_concrete.contains(&2)); - assert!(deserialized_concrete.contains(&3)); + for type_id in [TypeId::LIST, TypeId::SET, TypeId::MAP] { + let bytes = vec![0, RefFlag::NotNullValue as i8 as u8, type_id as u8]; + let result: Result, _> = fory.deserialize(&bytes); + let err = match result { + Ok(_) => panic!("expected erased container type id to fail"), + Err(err) => err, + }; + assert_erased_serializer_container_error(err); + } } #[test] diff --git a/rust/tests/tests/test_unsigned.rs b/rust/tests/tests/test_unsigned.rs index 4f4be267a5..dd75467b51 100644 --- a/rust/tests/tests/test_unsigned.rs +++ b/rust/tests/tests/test_unsigned.rs @@ -19,8 +19,65 @@ mod test_helpers; use fory_core::fory::Fory; use fory_derive::ForyStruct; +use std::any::Any; +use std::rc::Rc; +use std::sync::Arc; use test_helpers::{test_arc_any, test_box_any, test_rc_any, test_roundtrip}; +fn assert_erased_container_error(message: String) { + assert!( + message.contains("top-level erased Any") + || message.contains("Erased Any payloads require") + || message.contains("cannot be represented as Arc"), + "unexpected error: {message}" + ); +} + +fn test_box_any_unsupported(fory: &Fory, value: T) +where + T: 'static, +{ + let wrapped: Box = Box::new(value); + match fory.serialize(&wrapped) { + Ok(bytes) => { + let result: Result, _> = fory.deserialize(&bytes); + let err = result.expect_err("expected Box container read to fail"); + assert_erased_container_error(err.to_string()); + } + Err(err) => assert_erased_container_error(err.to_string()), + } +} + +fn test_rc_any_unsupported(fory: &Fory, value: T) +where + T: 'static, +{ + let wrapped: Rc = Rc::new(value); + match fory.serialize(&wrapped) { + Ok(bytes) => { + let result: Result, _> = fory.deserialize(&bytes); + let err = result.expect_err("expected Rc container read to fail"); + assert_erased_container_error(err.to_string()); + } + Err(err) => assert_erased_container_error(err.to_string()), + } +} + +fn test_arc_any_unsupported(fory: &Fory, value: T) +where + T: 'static + Send + Sync, +{ + let wrapped: Arc = Arc::new(value); + match fory.serialize(&wrapped) { + Ok(bytes) => { + let result: Result, _> = fory.deserialize(&bytes); + let err = result.expect_err("expected Arc container read to fail"); + assert_erased_container_error(err.to_string()); + } + Err(err) => assert_erased_container_error(err.to_string()), + }; +} + #[test] fn test_unsigned_numbers() { let fory = Fory::builder().xlang(false).build(); @@ -443,27 +500,25 @@ fn test_unsigned_with_smart_pointers() { test_arc_any(&fory, usize::MAX); test_arc_any(&fory, u128::MAX); - // Test Box with unsigned arrays - test_box_any(&fory, vec![0u8, 127, u8::MAX]); - test_box_any(&fory, vec![0u16, 1000, u16::MAX]); - test_box_any(&fory, vec![0u32, 1000000, u32::MAX]); - test_box_any(&fory, vec![0u64, 1000000000000, u64::MAX]); - test_box_any(&fory, vec![0usize, 1000000000000, usize::MAX]); - test_box_any(&fory, vec![0u128, 1000000000000, u128::MAX]); - - // Test Rc with unsigned arrays - test_rc_any(&fory, vec![0u8, 127, u8::MAX]); - test_rc_any(&fory, vec![100u16, 200, 300, u16::MAX]); - test_rc_any(&fory, vec![1000u32, 2000, 3000, u32::MAX]); - test_rc_any(&fory, vec![0u64, 1000000000000, u64::MAX]); - test_rc_any(&fory, vec![0usize, 1000000000000, usize::MAX]); - test_rc_any(&fory, vec![0u128, 1000000000000, u128::MAX]); - - // Test Arc with unsigned arrays - test_arc_any(&fory, vec![0u8, 127, u8::MAX]); - test_arc_any(&fory, vec![100u16, 200, 300, u16::MAX]); - test_arc_any(&fory, vec![999u32, 888, 777, u32::MAX]); - test_arc_any(&fory, vec![123u64, 456789, 987654321, u64::MAX]); - test_arc_any(&fory, vec![123usize, 456789, 987654321, usize::MAX]); - test_arc_any(&fory, vec![0u128, 1000000000000, u128::MAX]); + // Direct vectors are not supported as top-level erased Any payloads. + test_box_any_unsupported(&fory, vec![0u8, 127, u8::MAX]); + test_box_any_unsupported(&fory, vec![0u16, 1000, u16::MAX]); + test_box_any_unsupported(&fory, vec![0u32, 1000000, u32::MAX]); + test_box_any_unsupported(&fory, vec![0u64, 1000000000000, u64::MAX]); + test_box_any_unsupported(&fory, vec![0usize, 1000000000000, usize::MAX]); + test_box_any_unsupported(&fory, vec![0u128, 1000000000000, u128::MAX]); + + test_rc_any_unsupported(&fory, vec![0u8, 127, u8::MAX]); + test_rc_any_unsupported(&fory, vec![100u16, 200, 300, u16::MAX]); + test_rc_any_unsupported(&fory, vec![1000u32, 2000, 3000, u32::MAX]); + test_rc_any_unsupported(&fory, vec![0u64, 1000000000000, u64::MAX]); + test_rc_any_unsupported(&fory, vec![0usize, 1000000000000, usize::MAX]); + test_rc_any_unsupported(&fory, vec![0u128, 1000000000000, u128::MAX]); + + test_arc_any_unsupported(&fory, vec![0u8, 127, u8::MAX]); + test_arc_any_unsupported(&fory, vec![100u16, 200, 300, u16::MAX]); + test_arc_any_unsupported(&fory, vec![999u32, 888, 777, u32::MAX]); + test_arc_any_unsupported(&fory, vec![123u64, 456789, 987654321, u64::MAX]); + test_arc_any_unsupported(&fory, vec![123usize, 456789, 987654321, usize::MAX]); + test_arc_any_unsupported(&fory, vec![0u128, 1000000000000, u128::MAX]); }