Skip to content

Commit 2426260

Browse files
Mohsen Akbarimeta-codesync[bot]
authored andcommitted
custom default values lost in DynamicPatch to TypedPatch conversion
Summary: When converting a DynamicPatch to a TypedPatch, fields with custom default values (e.g., `bool enabled = true`) that were explicitly set to their intrinsic defaults (e.g., `false`) were incorrectly getting the custom default values instead. Root cause: In `DiffVisitorBase::diffField()`, when a new struct field is added (exists in dst but not src), `emptyValue()` was used to create an empty Object `{}` for the ensure value. When this empty Object was later converted to a typed struct via `fromObjectStruct`, the typed struct was default-constructed with custom defaults, and the empty Object had no fields to override those defaults. Fix: Store the actual destination field value in ensure instead of an empty Object. This ensures all field values are explicitly present when converting to typed patch, preventing custom defaults from incorrectly overriding intended values. Reviewed By: Mizuchi Differential Revision: D93497787 fbshipit-source-id: 246eb6efc35a900f5689ce8ce4f70ee6c0a0aab8
1 parent e3da8c4 commit 2426260

3 files changed

Lines changed: 86 additions & 5 deletions

File tree

third-party/thrift/src/thrift/lib/cpp2/patch/test/DynamicPatchTest.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,4 +1787,68 @@ TEST(DynamicPatch, AnyPatchMergeThenExtractPreservesRootFields) {
17871787
}
17881788
}
17891789

1790+
// Verifies that field values different from custom defaults are preserved
1791+
// when converting DynamicPatch to TypedPatch via fromObjectStruct.
1792+
TEST(DynamicPatchTest, CustomDefaultValuesPreservedInTypedPatchExtraction) {
1793+
OuterStructWithCustomDefaults src;
1794+
src.name() = "TestEntity";
1795+
1796+
OuterStructWithCustomDefaults dst;
1797+
dst.name() = "TestEntity";
1798+
CustomDefaultOptions opts;
1799+
opts.enabled() = false; // Custom default is true
1800+
opts.thresholdPercent() = 50; // Custom default is 90
1801+
opts.allowEmpty() = true; // Custom default is false
1802+
dst.options() = opts;
1803+
1804+
DiffVisitorBase visitor;
1805+
DynamicPatch dynamicPatch =
1806+
visitor.diff(protocol::asObject(src), protocol::asObject(dst));
1807+
1808+
auto typedPatch =
1809+
fromObjectStruct<type::infer_tag<OuterStructWithCustomDefaultsPatch>>(
1810+
dynamicPatch.toObject());
1811+
1812+
OuterStructWithCustomDefaults fresh;
1813+
fresh.name() = "TestEntity";
1814+
typedPatch.apply(fresh);
1815+
1816+
ASSERT_TRUE(fresh.options().has_value());
1817+
EXPECT_EQ(fresh.options()->enabled(), false);
1818+
EXPECT_EQ(fresh.options()->thresholdPercent(), 50);
1819+
EXPECT_EQ(fresh.options()->allowEmpty(), true);
1820+
}
1821+
1822+
// Verifies that intrinsic default values are preserved when they differ from
1823+
// custom defaults during DynamicPatch to TypedPatch conversion.
1824+
TEST(DynamicPatchTest, IntrinsicDefaultValuesPreservedWhenDifferFromCustom) {
1825+
OuterStructWithCustomDefaults src;
1826+
src.name() = "TestEntity";
1827+
1828+
OuterStructWithCustomDefaults dst;
1829+
dst.name() = "TestEntity";
1830+
CustomDefaultOptions opts;
1831+
opts.enabled() = false; // Intrinsic default, custom default is true
1832+
opts.thresholdPercent() = 0; // Intrinsic default, custom default is 90
1833+
opts.allowEmpty() = false;
1834+
dst.options() = opts;
1835+
1836+
DiffVisitorBase visitor;
1837+
DynamicPatch dynamicPatch =
1838+
visitor.diff(protocol::asObject(src), protocol::asObject(dst));
1839+
1840+
auto typedPatch =
1841+
fromObjectStruct<type::infer_tag<OuterStructWithCustomDefaultsPatch>>(
1842+
dynamicPatch.toObject());
1843+
1844+
OuterStructWithCustomDefaults fresh;
1845+
fresh.name() = "TestEntity";
1846+
typedPatch.apply(fresh);
1847+
1848+
ASSERT_TRUE(fresh.options().has_value());
1849+
EXPECT_EQ(fresh.options()->enabled(), false);
1850+
EXPECT_EQ(fresh.options()->thresholdPercent(), 0);
1851+
EXPECT_EQ(fresh.options()->allowEmpty(), false);
1852+
}
1853+
17901854
} // namespace apache::thrift::protocol

third-party/thrift/src/thrift/lib/cpp2/patch/test/DynamicPatchTest.thrift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,17 @@ struct Sets {
4141
struct StructWithAny {
4242
1: any.Any any;
4343
}
44+
45+
// Structs for testing custom default value preservation in patch conversion
46+
@patch.GeneratePatchNew
47+
struct CustomDefaultOptions {
48+
1: bool enabled = true;
49+
2: i32 thresholdPercent = 90;
50+
3: bool allowEmpty = false;
51+
}
52+
53+
@patch.GeneratePatchNew
54+
struct OuterStructWithCustomDefaults {
55+
1: string name;
56+
2: optional CustomDefaultOptions options;
57+
}

third-party/thrift/src/thrift/lib/thrift/detail/DynamicPatch.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,13 +1554,16 @@ void DiffVisitorBase::diffField(
15541554
auto guard = folly::makeGuard([&] { pop(); });
15551555
auto& field = dst.at(id);
15561556

1557-
// patch after
1558-
// We can't ensure the field because it will be ignored for non-optional
1559-
// field when applied statically.
1557+
// For non-optional fields (like in terse structs), ensure may be ignored
1558+
// during typed patch application. Use ensure + patchIfSet with Assign
1559+
// operation to make sure the value is actually set.
15601560
auto empty = emptyValue(field.getType());
1561-
auto subPatch = diff(badge, empty, field);
15621561
patch.ensure(id, std::move(empty));
1563-
patch.patchIfSet(id).merge(DynamicPatch{std::move(subPatch)});
1562+
1563+
Object assignPatch;
1564+
assignPatch[static_cast<FieldId>(op::PatchOp::Assign)] = field;
1565+
patch.patchIfSet(id).merge(
1566+
DynamicPatch::fromObject(std::move(assignPatch)));
15641567
return;
15651568
}
15661569

0 commit comments

Comments
 (0)