This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to tag v0.14.1-rc1 in repository https://gitbox.apache.org/repos/asf/fory.git
commit 6b104501e4c7580b3ac648511efa7ee98bc73ed7 Author: Damon Zhao <[email protected]> AuthorDate: Mon Dec 22 18:22:55 2025 +0800 feat(rust): add generate_default attr, no longer generate Default by default (#3074) …default ## Why? When using `#[derive(ForyObject)]` on a type that already has a manual `impl Default`, a compilation error occurs due to conflicting `Default` implementations. The previous behavior of auto-generating `Default` was problematic for integrating with existing codebases where types have custom `Default` implementations. After discussion with reviewers, we decided to **invert the default behavior**: `ForyObject` should NOT generate `impl Default` by default, and users who want it can opt-in with `#[fory(generate_default)]`. ## What does this PR do? ### Breaking Change `#[derive(ForyObject)]` **no longer generates `impl Default`** by default. Scenario | ForyDefault | Default -- | -- | -- Default (no attribute) | ✅ Generated (standalone impl) | ❌ Not generated #[fory(generate_default)] | ✅ Generated | ✅ Generated (delegates to ForyDefault) User has existing Default | ✅ Generated (delegates to Default) | User's own impl ### Migration Guide ```rust // Before this PR (v0.14.0) #[derive(ForyObject)] struct MyStruct { ... } // Auto-generates Default // After this PR (v0.15.0) - Option 1: Add derive(Default) explicitly #[derive(ForyObject, Default)] struct MyStruct { ... } // After this PR (v0.15.0) - Option 2: Use generate_default attribute #[derive(ForyObject)] #[fory(generate_default)] struct MyStruct { ... } ``` ### Changes 1. Added `#[fory(generate_default)]` attribute to opt-in for `Default` generation 2. Changed all internal code generation to use `ForyDefault::fory_default()` instead of `Default::default()` 3. Updated `register_trait_type!` macro to use `ForyDefault` instead of `Default` 4. Updated documentation and tests 5. Used workspace dependencies for internal crate versions ## Related issues None ## Does this PR introduce any user-facing change? - [x] Does this PR introduce any public API change? - **Breaking**: `#[derive(ForyObject)]` no longer generates `impl Default` - **New**: `#[fory(generate_default)]` attribute to opt-in for `Default` generation - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark This change only affects compile-time code generation. No runtime performance impact. --- docs/guide/rust/custom-serializers.md | 8 +- rust/Cargo.toml | 6 +- rust/README.md | 4 +- rust/fory-core/src/serializer/trait_object.rs | 18 +-- rust/fory-derive/Cargo.toml | 2 +- rust/fory-derive/src/lib.rs | 36 ++++- rust/fory-derive/src/object/derive_enum.rs | 26 ++-- rust/fory-derive/src/object/read.rs | 3 +- rust/fory-derive/src/object/serializer.rs | 113 ++++++++++------ rust/fory/Cargo.toml | 4 +- rust/tests/tests/compatible/test_struct.rs | 6 +- rust/tests/tests/compatible/test_struct_enum.rs | 6 +- rust/tests/tests/test_cross_language.rs | 2 +- rust/tests/tests/test_generate_default.rs | 171 ++++++++++++++++++++++++ rust/tests/tests/test_rc_arc_trait_object.rs | 11 +- rust/tests/tests/test_skip_fields.rs | 12 +- 16 files changed, 334 insertions(+), 94 deletions(-) diff --git a/docs/guide/rust/custom-serializers.md b/docs/guide/rust/custom-serializers.md index 833b20147..a7f403a29 100644 --- a/docs/guide/rust/custom-serializers.md +++ b/docs/guide/rust/custom-serializers.md @@ -34,7 +34,7 @@ For types that don't support `#[derive(ForyObject)]`, implement the `Serializer` use fory::{Fory, ReadContext, WriteContext, Serializer, ForyDefault, Error}; use std::any::Any; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Default)] struct CustomType { value: i32, name: String, @@ -63,6 +63,7 @@ impl Serializer for CustomType { } } +// ForyDefault delegates to Default impl ForyDefault for CustomType { fn fory_default() -> Self { Self::default() @@ -70,6 +71,11 @@ impl ForyDefault for CustomType { } ``` +> **Note**: When implementing `ForyDefault` manually, ensure your type also implements `Default` if you use `Self::default()`. +> Alternatively, you can construct a default instance directly in `fory_default()`. +> +> **Tip**: If your type supports `#[derive(ForyObject)]`, you can use `#[fory(generate_default)]` to automatically generate both `ForyDefault` and `Default` implementations. + ## Registering Custom Serializers ```rust diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5579ab3c6..25b4588c3 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -30,7 +30,7 @@ exclude = [ resolver = "2" [workspace.package] -version = "0.14.0" +version = "0.15.0" rust-version = "1.70" description = "Apache Fory: Blazingly fast multi-language serialization framework with trait objects and reference support." license = "Apache-2.0" @@ -40,3 +40,7 @@ repository = "https://github.com/apache/fory" edition = "2021" keywords = ["serialization", "serde", "trait-object", "zero-copy", "schema-evolution"] categories = ["encoding"] + +[workspace.dependencies] +fory-core = { path = "fory-core", version = "0.15.0" } +fory-derive = { path = "fory-derive", version = "0.15.0" } diff --git a/rust/README.md b/rust/README.md index d84bfcebe..c16651f65 100644 --- a/rust/README.md +++ b/rust/README.md @@ -32,7 +32,7 @@ Add Apache Fory™ to your `Cargo.toml`: ```toml [dependencies] -fory = "0.13" +fory = "0.14" ``` ### Basic Example @@ -459,7 +459,7 @@ For types that don't support `#[derive(ForyObject)]`, implement the `Serializer` use fory::{Fory, ReadContext, WriteContext, Serializer, ForyDefault, Error}; use std::any::Any; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Default)] struct CustomType { value: i32, name: String, diff --git a/rust/fory-core/src/serializer/trait_object.rs b/rust/fory-core/src/serializer/trait_object.rs index 7a70e2345..54e639aa3 100644 --- a/rust/fory-core/src/serializer/trait_object.rs +++ b/rust/fory-core/src/serializer/trait_object.rs @@ -120,16 +120,10 @@ macro_rules! resolve_and_deserialize { macro_rules! register_trait_type { ($trait_name:ident, $($impl_type:ty),+ $(,)?) => { // 1. Generate Box<dyn Trait> serializer (existing functionality) - // Default implementation using first registered type - impl std::default::Default for Box<dyn $trait_name> { - fn default() -> Self { - Box::new(<register_trait_type!(@first_type $($impl_type),+) as std::default::Default>::default()) - } - } - + // ForyDefault implementation using first registered type impl $crate::serializer::ForyDefault for Box<dyn $trait_name> { fn fory_default() -> Self { - Box::new(<register_trait_type!(@first_type $($impl_type),+) as std::default::Default>::default()) + Box::new(<register_trait_type!(@first_type $($impl_type),+) as $crate::serializer::ForyDefault>::fory_default()) } } @@ -328,15 +322,9 @@ macro_rules! generate_smart_pointer_wrapper { } } - impl std::default::Default for [<$trait_name $ptr_name>] { - fn default() -> Self { - Self($ptr_path::new(<$crate::register_trait_type!(@first_type $($impl_type),+) as std::default::Default>::default())) - } - } - impl $crate::serializer::ForyDefault for [<$trait_name $ptr_name>] { fn fory_default() -> Self { - Self($ptr_path::new(<$crate::register_trait_type!(@first_type $($impl_type),+) as std::default::Default>::default())) + Self($ptr_path::new(<$crate::register_trait_type!(@first_type $($impl_type),+) as $crate::serializer::ForyDefault>::fory_default())) } } diff --git a/rust/fory-derive/Cargo.toml b/rust/fory-derive/Cargo.toml index 8e354cc21..1c0c9067e 100644 --- a/rust/fory-derive/Cargo.toml +++ b/rust/fory-derive/Cargo.toml @@ -32,7 +32,7 @@ proc-macro = true [dependencies] proc-macro2 = { default-features = false, version = "1.0" } -fory-core = { path = "../fory-core", version = "0.14.0"} +fory-core.workspace = true syn = { default-features = false, version = "2.0", features = [ "parsing", "proc-macro", diff --git a/rust/fory-derive/src/lib.rs b/rust/fory-derive/src/lib.rs index 8ecb4cdbc..69a335ba7 100644 --- a/rust/fory-derive/src/lib.rs +++ b/rust/fory-derive/src/lib.rs @@ -113,6 +113,10 @@ //! `fory_core::serializer::struct_`. //! - **`#[fory(skip)]`**: Marks an individual field (or enum variant) to be ignored by the //! generated serializer, retaining compatibility with previous releases. +//! - **`#[fory(generate_default)]`**: Enables the macro to generate `Default` implementation. +//! By default, `ForyObject` does NOT generate `impl Default` to avoid conflicts with existing +//! `Default` implementations. Use this attribute when you want the macro to generate both +//! `ForyDefault` and `Default` for you. //! //! ## Field Types //! @@ -212,12 +216,12 @@ pub fn proc_macro_derive_fory_object(input: proc_macro::TokenStream) -> TokenStr // Check if this is being applied to a trait (which is not possible with derive macros) // Derive macros can only be applied to structs, enums, and unions - let debug_enabled = match parse_debug_flag(&input.attrs) { - Ok(flag) => flag, + let (debug_enabled, generate_default) = match parse_fory_attrs(&input.attrs) { + Ok(flags) => flags, Err(err) => return err.into_compile_error().into(), }; - object::derive_serializer(&input, debug_enabled) + object::derive_serializer(&input, debug_enabled, generate_default) } /// Derive macro for row-based serialization. @@ -245,8 +249,10 @@ pub fn proc_macro_derive_fory_row(input: proc_macro::TokenStream) -> TokenStream derive_row(&input) } -fn parse_debug_flag(attrs: &[Attribute]) -> syn::Result<bool> { +/// Parse fory attributes and return (debug_enabled, generate_default) +fn parse_fory_attrs(attrs: &[Attribute]) -> syn::Result<(bool, bool)> { let mut debug_flag: Option<bool> = None; + let mut generate_default_flag: Option<bool> = None; for attr in attrs { if attr.path().is_ident("fory") { @@ -268,11 +274,31 @@ fn parse_debug_flag(attrs: &[Attribute]) -> syn::Result<bool> { Some(_) => debug_flag, None => Some(value), }; + } else if meta.path.is_ident("generate_default") { + let value = if meta.input.is_empty() { + true + } else { + let lit: LitBool = meta.value()?.parse()?; + lit.value + }; + generate_default_flag = match generate_default_flag { + Some(existing) if existing != value => { + return Err(syn::Error::new( + meta.path.span(), + "conflicting `generate_default` attribute values", + )); + } + Some(_) => generate_default_flag, + None => Some(value), + }; } Ok(()) })?; } } - Ok(debug_flag.unwrap_or(false)) + Ok(( + debug_flag.unwrap_or(false), + generate_default_flag.unwrap_or(false), + )) } diff --git a/rust/fory-derive/src/object/derive_enum.rs b/rust/fory-derive/src/object/derive_enum.rs index efb67a0e0..e32fae431 100644 --- a/rust/fory-derive/src/object/derive_enum.rs +++ b/rust/fory-derive/src/object/derive_enum.rs @@ -429,8 +429,9 @@ fn xlang_variant_read_branches( let default_fields: Vec<TokenStream> = fields_unnamed .unnamed .iter() - .map(|_| { - quote! { Default::default() } + .map(|f| { + let ty = &f.ty; + quote! { <#ty as fory_core::ForyDefault>::fory_default() } }) .collect(); @@ -444,7 +445,8 @@ fn xlang_variant_read_branches( .iter() .map(|f| { let field_ident = f.ident.as_ref().unwrap(); - quote! { #field_ident: Default::default() } + let ty = &f.ty; + quote! { #field_ident: <#ty as fory_core::ForyDefault>::fory_default() } }) .collect(); @@ -577,7 +579,7 @@ fn rust_compatible_variant_read_branches( use fory_core::serializer::Serializer; <#field_ty>::fory_read(context, true, true)? } else { - Default::default() + <#field_ty as fory_core::ForyDefault>::fory_default() } } }) @@ -587,7 +589,10 @@ fn rust_compatible_variant_read_branches( let default_fields: Vec<TokenStream> = fields_unnamed .unnamed .iter() - .map(|_| quote! { Default::default() }) + .map(|f| { + let ty = &f.ty; + quote! { <#ty as fory_core::ForyDefault>::fory_default() } + }) .collect(); let default_value = quote! { Self::#ident( #(#default_fields),* ) }; @@ -636,7 +641,8 @@ fn rust_compatible_variant_read_branches( .iter() .map(|f| { let field_ident = f.ident.as_ref().unwrap(); - quote! { #field_ident: Default::default() } + let ty = &f.ty; + quote! { #field_ident: <#ty as fory_core::ForyDefault>::fory_default() } }) .collect(); let default_value = quote! { Self::#ident { #(#default_fields),* } }; @@ -695,7 +701,10 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream { let default_fields: Vec<TokenStream> = fields_unnamed .unnamed .iter() - .map(|_| quote! { Default::default() }) + .map(|f| { + let ty = &f.ty; + quote! { <#ty as fory_core::ForyDefault>::fory_default() } + }) .collect(); quote! { Self::#default_variant_ident( #(#default_fields),* ) } } @@ -705,7 +714,8 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream { .iter() .map(|f| { let field_ident = f.ident.as_ref().unwrap(); - quote! { #field_ident: Default::default() } + let ty = &f.ty; + quote! { #field_ident: <#ty as fory_core::ForyDefault>::fory_default() } }) .collect(); quote! { Self::#default_variant_ident { #(#default_fields),* } } diff --git a/rust/fory-derive/src/object/read.rs b/rust/fory-derive/src/object/read.rs index add028568..e53a32789 100644 --- a/rust/fory-derive/src/object/read.rs +++ b/rust/fory-derive/src/object/read.rs @@ -88,8 +88,9 @@ pub(crate) fn assign_value(fields: &[&Field]) -> Vec<TokenStream> { } _ => { if need_declared_by_option(field) { + let ty = &field.ty; quote! { - #name: #var_name.unwrap_or_default() + #name: #var_name.unwrap_or_else(|| <#ty as fory_core::ForyDefault>::fory_default()) } } else { quote! { diff --git a/rust/fory-derive/src/object/serializer.rs b/rust/fory-derive/src/object/serializer.rs index ee5fb7201..374745392 100644 --- a/rust/fory-derive/src/object/serializer.rs +++ b/rust/fory-derive/src/object/serializer.rs @@ -36,7 +36,11 @@ fn has_existing_default(ast: &syn::DeriveInput, trait_name: &str) -> bool { }) } -pub fn derive_serializer(ast: &syn::DeriveInput, debug_enabled: bool) -> TokenStream { +pub fn derive_serializer( + ast: &syn::DeriveInput, + debug_enabled: bool, + generate_default: bool, +) -> TokenStream { let name = &ast.ident; use crate::object::util::{clear_struct_context, set_struct_context}; set_struct_context(&name.to_string(), debug_enabled); @@ -44,7 +48,7 @@ pub fn derive_serializer(ast: &syn::DeriveInput, debug_enabled: bool) -> TokenSt // Check if ForyDefault is already derived/implemented let has_existing_default = has_existing_default(ast, "ForyDefault"); let default_impl = if !has_existing_default { - generate_default_impl(ast) + generate_default_impl(ast, generate_default) } else { quote! {} }; @@ -242,9 +246,14 @@ pub fn derive_serializer(ast: &syn::DeriveInput, debug_enabled: bool) -> TokenSt code } -fn generate_default_impl(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { +fn generate_default_impl( + ast: &syn::DeriveInput, + generate_default: bool, +) -> proc_macro2::TokenStream { let name = &ast.ident; - let has_existing_default = has_existing_default(ast, "Default"); + // By default, we don't generate Default impl to avoid conflicts. + // Only generate if generate_default is true AND there's no existing Default. + let should_generate_default = generate_default && !has_existing_default(ast, "Default"); match &ast.data { Data::Struct(s) => { @@ -295,15 +304,8 @@ fn generate_default_impl(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { } }); - if has_existing_default { - quote! { - impl fory_core::ForyDefault for #name { - fn fory_default() -> Self { - Self::default() - } - } - } - } else { + if should_generate_default { + // User requested Default generation via #[fory(generate_default)] quote! { impl fory_core::ForyDefault for #name { fn fory_default() -> Self { @@ -318,6 +320,18 @@ fn generate_default_impl(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { } } } + } else { + // Default case: only generate ForyDefault, not Default + // This avoids conflicts with existing Default implementations + quote! { + impl fory_core::ForyDefault for #name { + fn fory_default() -> Self { + Self { + #(#field_inits),* + } + } + } + } } } Data::Enum(e) => { @@ -327,25 +341,42 @@ fn generate_default_impl(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { .iter() .any(|v| v.attrs.iter().any(|attr| attr.path().is_ident("default"))); - // For C-like enums, implement Default by returning the first variant - // Only if there's no #[default] attribute (which means Default is being derived) - if !has_default_variant { - if let Some(first_variant) = e.variants.first() { - let variant_ident = &first_variant.ident; - let field_defaults = match &first_variant.fields { - syn::Fields::Unit => quote! {}, - syn::Fields::Unnamed(fields) => { - let defaults = - (0..fields.unnamed.len()).map(|_| quote! { Default::default() }); - quote! { (#(#defaults),*) } - } - syn::Fields::Named(fields) => { - let field_idents = fields.named.iter().map(|f| &f.ident); - let defaults = - fields.named.iter().map(|_| quote! { Default::default() }); - quote! { { #(#field_idents: #defaults),* } } + // Check if user has #[derive(Default)] on the enum + let has_derived_default = has_existing_default(ast, "Default"); + + if let Some(first_variant) = e.variants.first() { + let variant_ident = &first_variant.ident; + let field_defaults = match &first_variant.fields { + syn::Fields::Unit => quote! {}, + syn::Fields::Unnamed(fields) => { + let defaults = fields.unnamed.iter().map(|f| { + let ty = &f.ty; + quote! { <#ty as fory_core::ForyDefault>::fory_default() } + }); + quote! { (#(#defaults),*) } + } + syn::Fields::Named(fields) => { + let field_inits = fields.named.iter().map(|f| { + let ident = &f.ident; + let ty = &f.ty; + quote! { #ident: <#ty as fory_core::ForyDefault>::fory_default() } + }); + quote! { { #(#field_inits),* } } + } + }; + + if has_derived_default || has_default_variant { + // User has #[derive(Default)] or #[default] attribute + // Only generate ForyDefault that delegates to Default + quote! { + impl fory_core::ForyDefault for #name { + fn fory_default() -> Self { + Self::default() + } } - }; + } + } else if should_generate_default { + // User requested Default generation via #[fory(generate_default)] quote! { impl fory_core::ForyDefault for #name { fn fory_default() -> Self { @@ -360,21 +391,17 @@ fn generate_default_impl(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { } } } else { - // impl fory_core::ForyDefault for #name { - // fn fory_default() -> Self { - // panic!("No unit-like variants found in enum {}", stringify!(#name)); - // } - // } - quote! {} - } - } else { - quote! { - impl fory_core::ForyDefault for #name { - fn fory_default() -> Self { - Self::default() + // Default case: only generate ForyDefault, not Default + quote! { + impl fory_core::ForyDefault for #name { + fn fory_default() -> Self { + Self::#variant_ident #field_defaults + } } } } + } else { + quote! {} } } Data::Union(_) => quote! {}, diff --git a/rust/fory/Cargo.toml b/rust/fory/Cargo.toml index a7e3d7290..a8c2645b9 100644 --- a/rust/fory/Cargo.toml +++ b/rust/fory/Cargo.toml @@ -29,5 +29,5 @@ categories.workspace = true description = "Apache Fory: Blazingly fast multi-language serialization framework with trait objects and reference support." [dependencies] -fory-core = { path = "../fory-core", version = "0.14.0"} -fory-derive = { path = "../fory-derive", version = "0.14.0"} +fory-core.workspace = true +fory-derive.workspace = true diff --git a/rust/tests/tests/compatible/test_struct.rs b/rust/tests/tests/compatible/test_struct.rs index 44c32726b..cc5c1655b 100644 --- a/rust/tests/tests/compatible/test_struct.rs +++ b/rust/tests/tests/compatible/test_struct.rs @@ -136,7 +136,8 @@ fn nonexistent_struct() { }; let bin = fory1.serialize(&person).unwrap(); let obj: Person2 = fory2.deserialize(&bin).unwrap(); - assert_eq!(obj.f2, Item2::default()); + use fory_core::ForyDefault; + assert_eq!(obj.f2, Item2::fory_default()); assert_eq!(obj.f3, i64::default()); assert_eq!(obj.last, person.last); } @@ -376,7 +377,8 @@ fn nullable_struct() { let person2: Person2 = fory2.deserialize(&bin).unwrap(); assert_eq!(person2.f1.unwrap(), person1.f1); - assert_eq!(person2.f2, Item::default()); + use fory_core::ForyDefault; + assert_eq!(person2.f2, Item::fory_default()); assert_eq!(person2.f3, person1.f3.unwrap()); assert_eq!(person2.last, person1.last); } diff --git a/rust/tests/tests/compatible/test_struct_enum.rs b/rust/tests/tests/compatible/test_struct_enum.rs index 052c7439f..0952b6833 100644 --- a/rust/tests/tests/compatible/test_struct_enum.rs +++ b/rust/tests/tests/compatible/test_struct_enum.rs @@ -180,7 +180,8 @@ fn nonexistent_struct() { }; let bin = fory1.serialize(&person).unwrap(); let obj: Person2 = fory2.deserialize(&bin).unwrap(); - assert_eq!(obj.f2, Item2::default()); + use fory_core::ForyDefault; + assert_eq!(obj.f2, Item2::fory_default()); assert_eq!(obj.f3, i64::default()); assert_eq!(obj.last, person.last); } @@ -420,7 +421,8 @@ fn nullable_struct() { let person2: Person2 = fory2.deserialize(&bin).unwrap(); assert_eq!(person2.f1.unwrap(), person1.f1); - assert_eq!(person2.f2, Item::default()); + use fory_core::ForyDefault; + assert_eq!(person2.f2, Item::fory_default()); assert_eq!(person2.f3, person1.f3.unwrap()); assert_eq!(person2.last, person1.last); } diff --git a/rust/tests/tests/test_cross_language.rs b/rust/tests/tests/test_cross_language.rs index 2afc3f470..e415534e8 100644 --- a/rust/tests/tests/test_cross_language.rs +++ b/rust/tests/tests/test_cross_language.rs @@ -32,7 +32,7 @@ fn get_data_file() -> String { std::env::var("DATA_FILE").expect("DATA_FILE not set") } -#[derive(ForyObject, Debug, PartialEq)] +#[derive(ForyObject, Debug, PartialEq, Default)] struct Empty {} #[derive(ForyObject, Debug, PartialEq, Default)] diff --git a/rust/tests/tests/test_generate_default.rs b/rust/tests/tests/test_generate_default.rs new file mode 100644 index 000000000..b96d9af30 --- /dev/null +++ b/rust/tests/tests/test_generate_default.rs @@ -0,0 +1,171 @@ +// 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. + +//! Tests for the `#[fory(generate_default)]` attribute. +//! +//! By default, `ForyObject` does NOT generate `impl Default` to avoid conflicts +//! with existing `Default` implementations. Use `#[fory(generate_default)]` when +//! you want the macro to generate both `ForyDefault` and `Default` for you. + +use fory_core::fory::Fory; +use fory_derive::ForyObject; + +/// Test struct with manual Default implementation. +/// ForyObject does NOT generate Default by default, so there's no conflict. +#[derive(ForyObject, Debug, PartialEq)] +struct NodeWithCustomDefault { + addr: String, +} + +impl Default for NodeWithCustomDefault { + fn default() -> Self { + Self { + addr: "localhost:8080".to_string(), + } + } +} + +#[test] +fn test_no_default_conflict() { + let mut fory = Fory::default(); + fory.register::<NodeWithCustomDefault>(1).unwrap(); + + let node = NodeWithCustomDefault { + addr: "192.168.1.1:3000".to_string(), + }; + + let bytes = fory.serialize(&node).unwrap(); + let decoded: NodeWithCustomDefault = fory.deserialize(&bytes).unwrap(); + assert_eq!(node, decoded); +} + +#[test] +fn test_custom_default_is_preserved() { + // Verify that our custom Default implementation is used + let node = NodeWithCustomDefault::default(); + assert_eq!(node.addr, "localhost:8080"); +} + +/// Test enum with manual Default implementation. +/// ForyObject does NOT generate Default by default. +#[derive(ForyObject, Debug, PartialEq)] +enum StatusWithCustomDefault { + Active, + Inactive, + Pending, +} + +// Allow derivable_impls because we're specifically testing manual Default impl +#[allow(clippy::derivable_impls)] +impl Default for StatusWithCustomDefault { + fn default() -> Self { + // Custom default: Pending instead of Active (first variant) + StatusWithCustomDefault::Pending + } +} + +#[test] +fn test_enum_no_default_conflict() { + let mut fory = Fory::default(); + fory.register::<StatusWithCustomDefault>(2).unwrap(); + + let status = StatusWithCustomDefault::Active; + let bytes = fory.serialize(&status).unwrap(); + let decoded: StatusWithCustomDefault = fory.deserialize(&bytes).unwrap(); + assert_eq!(status, decoded); +} + +#[test] +fn test_enum_custom_default_is_preserved() { + // Verify that our custom Default implementation is used + let status = StatusWithCustomDefault::default(); + assert_eq!(status, StatusWithCustomDefault::Pending); +} + +/// Test that generate_default = true generates Default impl +#[derive(ForyObject, Debug, PartialEq)] +#[fory(generate_default)] +struct StructWithGeneratedDefault { + value: i32, +} + +#[test] +fn test_generate_default_struct() { + let mut fory = Fory::default(); + fory.register::<StructWithGeneratedDefault>(3).unwrap(); + + let data = StructWithGeneratedDefault { value: 42 }; + let bytes = fory.serialize(&data).unwrap(); + let decoded: StructWithGeneratedDefault = fory.deserialize(&bytes).unwrap(); + assert_eq!(data, decoded); + + // Default should work (generated by ForyObject) + let default_data = StructWithGeneratedDefault::default(); + assert_eq!(default_data.value, 0); +} + +/// Test that generate_default = true generates Default impl for enums +#[derive(ForyObject, Debug, PartialEq)] +#[fory(generate_default)] +enum EnumWithGeneratedDefault { + First, + Second, + Third, +} + +#[test] +fn test_generate_default_enum() { + let mut fory = Fory::default(); + fory.register::<EnumWithGeneratedDefault>(4).unwrap(); + + let data = EnumWithGeneratedDefault::Second; + let bytes = fory.serialize(&data).unwrap(); + let decoded: EnumWithGeneratedDefault = fory.deserialize(&bytes).unwrap(); + assert_eq!(data, decoded); + + // Default should be First variant (generated by ForyObject) + let default_data = EnumWithGeneratedDefault::default(); + assert_eq!(default_data, EnumWithGeneratedDefault::First); +} + +/// Test that generate_default = false works (same as not specifying) +#[derive(ForyObject, Debug, PartialEq)] +#[fory(generate_default = false)] +struct StructWithoutGeneratedDefault { + value: i32, +} + +impl Default for StructWithoutGeneratedDefault { + fn default() -> Self { + Self { value: 100 } + } +} + +#[test] +fn test_generate_default_false() { + let mut fory = Fory::default(); + fory.register::<StructWithoutGeneratedDefault>(5).unwrap(); + + let data = StructWithoutGeneratedDefault { value: 42 }; + let bytes = fory.serialize(&data).unwrap(); + let decoded: StructWithoutGeneratedDefault = fory.deserialize(&bytes).unwrap(); + assert_eq!(data, decoded); + + // Custom Default should be used + let default_data = StructWithoutGeneratedDefault::default(); + assert_eq!(default_data.value, 100); +} diff --git a/rust/tests/tests/test_rc_arc_trait_object.rs b/rust/tests/tests/test_rc_arc_trait_object.rs index 68ccaa872..2bbed8a1a 100644 --- a/rust/tests/tests/test_rc_arc_trait_object.rs +++ b/rust/tests/tests/test_rc_arc_trait_object.rs @@ -160,13 +160,14 @@ fn test_wrapper_polymorphism() { #[test] fn test_wrapper_default_implementations() { - // Test that wrapper types have proper Default implementations - let default_rc = AnimalRc::default(); - // Dog::default() should have empty name + use fory_core::ForyDefault; + // Test that wrapper types have proper ForyDefault implementations + let default_rc = AnimalRc::fory_default(); + // Dog::fory_default() should have empty name assert_eq!(default_rc.as_ref().name(), ""); - let default_arc = AnimalArc::default(); - // Dog::default() should have empty name + let default_arc = AnimalArc::fory_default(); + // Dog::fory_default() should have empty name assert_eq!(default_arc.as_ref().name(), ""); } diff --git a/rust/tests/tests/test_skip_fields.rs b/rust/tests/tests/test_skip_fields.rs index 9a9c43059..3f9cabd3e 100644 --- a/rust/tests/tests/test_skip_fields.rs +++ b/rust/tests/tests/test_skip_fields.rs @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -use fory_core::{Fory, ForyDefault, Reader}; +use fory_core::{Fory, Reader}; use fory_derive::ForyObject; #[derive(ForyObject, Debug, PartialEq)] @@ -123,7 +123,8 @@ fn test_nested_skip_functionality() { assert_eq!(original.normal_field, decoded.normal_field); assert_eq!(original.nested, decoded.nested); - assert_eq!(decoded.skipped_nested, NestedStruct::default()); + use fory_core::ForyDefault; + assert_eq!(decoded.skipped_nested, NestedStruct::fory_default()); } #[test] @@ -197,9 +198,10 @@ fn test_complex_nested_skip() { original.nested.serialized_field, decoded.nested.serialized_field ); + use fory_core::ForyDefault; assert_eq!(decoded.nested.skipped_field, String::default()); assert_eq!(decoded.skipped_field, String::default()); - assert_eq!(decoded.skipped_nested, TestSkipFields::default()); + assert_eq!(decoded.skipped_nested, TestSkipFields::fory_default()); } #[test] @@ -216,7 +218,8 @@ fn test_enum_skip() { let original_skip = TestEnumSkip::Deleted; let bytes = fory.serialize(&original_skip).unwrap(); let decoded: TestEnumSkip = fory.deserialize(&bytes).unwrap(); - assert_eq!(decoded, TestEnumSkip::default()); + use fory_core::ForyDefault; + assert_eq!(decoded, TestEnumSkip::fory_default()); } #[test] @@ -298,7 +301,6 @@ fn test_skip_with_different_types() { #[test] fn test_trait_object_serialization() { - use fory_core::ForyDefault; use fory_core::Serializer; use fory_core::{register_trait_type, Fory}; use fory_derive::ForyObject; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
