Skip to content

Commit 7857f92

Browse files
committed
fix(c++): make temporal types hashable
1 parent 6c6ba3f commit 7857f92

10 files changed

Lines changed: 599 additions & 177 deletions

File tree

compiler/fory_compiler/tests/test_generated_code.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,32 @@ def test_cpp_nested_container_ref_uses_correct_pointer_type():
11691169
)
11701170

11711171

1172+
def test_cpp_temporal_map_keys_use_fory_owned_wrappers():
1173+
schema = parse_fdl(
1174+
dedent(
1175+
"""
1176+
package demo;
1177+
1178+
message Holder {
1179+
map<duration, string> durations = 1;
1180+
map<timestamp, string> timestamps = 2;
1181+
map<date, string> dates = 3;
1182+
}
1183+
"""
1184+
)
1185+
)
1186+
1187+
cpp_output = render_files(generate_files(schema, CppGenerator))
1188+
assert (
1189+
"std::unordered_map<fory::serialization::Duration, std::string>" in cpp_output
1190+
)
1191+
assert (
1192+
"std::unordered_map<fory::serialization::Timestamp, std::string>" in cpp_output
1193+
)
1194+
assert "std::unordered_map<fory::serialization::Date, std::string>" in cpp_output
1195+
assert "std::map<" not in cpp_output
1196+
1197+
11721198
def test_java_enum_generation_uses_fory_enum_ids():
11731199
schema = parse_fdl(
11741200
dedent(

cpp/fory/serialization/serialization_test.cc

Lines changed: 145 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "fory/serialization/skip.h"
2323
#include "fory/thirdparty/MurmurHash3.h"
2424
#include "gtest/gtest.h"
25+
#include <any>
2526
#include <array>
2627
#include <atomic>
2728
#include <chrono>
@@ -31,6 +32,8 @@
3132
#include <map>
3233
#include <string>
3334
#include <thread>
35+
#include <typeinfo>
36+
#include <unordered_map>
3437
#include <variant>
3538
#include <vector>
3639

@@ -193,6 +196,24 @@ void test_roundtrip(const T &original, bool should_equal = true) {
193196
}
194197
}
195198

199+
template <typename T> void expect_any_roundtrip(Fory &fory, const T &value) {
200+
std::any original = value;
201+
auto serialize_result = fory.serialize(original);
202+
ASSERT_TRUE(serialize_result.ok())
203+
<< "Serialization failed: " << serialize_result.error().to_string();
204+
205+
auto deserialize_result =
206+
fory.deserialize<std::any>(serialize_result.value());
207+
ASSERT_TRUE(deserialize_result.ok())
208+
<< "Deserialization failed: " << deserialize_result.error().to_string();
209+
210+
std::any decoded = std::move(deserialize_result).value();
211+
EXPECT_EQ(decoded.type(), typeid(T));
212+
const auto *decoded_value = std::any_cast<T>(&decoded);
213+
ASSERT_NE(decoded_value, nullptr);
214+
EXPECT_EQ(*decoded_value, value);
215+
}
216+
196217
// ============================================================================
197218
// Primitive Type Tests
198219
// ============================================================================
@@ -370,10 +391,11 @@ TEST(SerializationTest, DurationRoundtrip) {
370391
auto fory =
371392
Fory::builder().xlang(true).compatible(false).track_ref(false).build();
372393
std::vector<Duration> values = {
373-
Duration(0),
374-
std::chrono::seconds(12) + Duration(345678901),
375-
-std::chrono::seconds(7) - std::chrono::milliseconds(45) - Duration(67),
376-
Duration(-1),
394+
Duration(std::chrono::nanoseconds(0)),
395+
Duration(std::chrono::seconds(12) + std::chrono::nanoseconds(345678901)),
396+
Duration(-std::chrono::seconds(7) - std::chrono::milliseconds(45) -
397+
std::chrono::nanoseconds(67)),
398+
Duration(std::chrono::nanoseconds(-1)),
377399
};
378400

379401
for (const Duration &original : values) {
@@ -550,9 +572,9 @@ TEST(SerializationTest, DurationUsesSecondsAndNanosecondsPayload) {
550572
auto fory =
551573
Fory::builder().xlang(true).compatible(false).track_ref(false).build();
552574
std::vector<TestCase> cases = {
553-
{Duration(1234567890), 1, 234567890},
554-
{Duration(-1234567890), -1, -234567890},
555-
{Duration(-1), 0, -1},
575+
{Duration(std::chrono::nanoseconds(1234567890)), 1, 234567890},
576+
{Duration(std::chrono::nanoseconds(-1234567890)), -1, -234567890},
577+
{Duration(std::chrono::nanoseconds(-1)), 0, -1},
556578
};
557579

558580
for (const TestCase &test_case : cases) {
@@ -579,7 +601,8 @@ TEST(SerializationTest, DurationSkipConsumesSecondsAndNanosecondsPayload) {
579601
auto fory =
580602
Fory::builder().xlang(true).compatible(false).track_ref(false).build();
581603
WriteContext write_ctx(fory.config(), fory.type_resolver().clone());
582-
Serializer<Duration>::write_data(Duration(-1), write_ctx);
604+
Serializer<Duration>::write_data(Duration(std::chrono::nanoseconds(-1)),
605+
write_ctx);
583606
ASSERT_FALSE(write_ctx.has_error()) << write_ctx.error().to_string();
584607

585608
ReadContext read_ctx(fory.config(), fory.type_resolver().clone());
@@ -805,6 +828,13 @@ TEST(SerializationTest, MapStringIntRoundtrip) {
805828
std::map<std::string, int32_t>{{"one", 1}, {"two", 2}, {"three", 3}});
806829
}
807830

831+
TEST(SerializationTest, UnorderedMapStringIntRoundtrip) {
832+
test_roundtrip(std::unordered_map<std::string, int32_t>{});
833+
test_roundtrip(std::unordered_map<std::string, int32_t>{{"one", 1}});
834+
test_roundtrip(std::unordered_map<std::string, int32_t>{
835+
{"one", 1}, {"two", 2}, {"three", 3}});
836+
}
837+
808838
TEST(SerializationTest, NestedVectorRoundtrip) {
809839
test_roundtrip(std::vector<std::vector<int32_t>>{});
810840
test_roundtrip(std::vector<std::vector<int32_t>>{{1, 2}, {3, 4}, {5}});
@@ -1489,6 +1519,113 @@ TEST(SerializationTest, ThreadSafeForyRejectsRegistrationAfterFirstSerialize) {
14891519
std::string::npos);
14901520
}
14911521

1522+
TEST(SerializationTest, TemporalCarriersAreHashable) {
1523+
std::unordered_map<Date, std::string> date_map;
1524+
date_map[Date(0)] = "epoch";
1525+
date_map[Date(18954)] = "future";
1526+
date_map[Date(-1)] = "past";
1527+
EXPECT_EQ(date_map.size(), 3u);
1528+
EXPECT_EQ(date_map[Date(0)], "epoch");
1529+
1530+
std::unordered_map<Duration, std::string> dur_map;
1531+
dur_map[Duration(std::chrono::nanoseconds(0))] = "zero";
1532+
dur_map[Duration(std::chrono::seconds(1))] = "one_sec";
1533+
dur_map[Duration(std::chrono::nanoseconds(-1))] = "neg";
1534+
EXPECT_EQ(dur_map.size(), 3u);
1535+
EXPECT_EQ(dur_map[Duration(std::chrono::nanoseconds(0))], "zero");
1536+
1537+
std::unordered_map<Timestamp, std::string> ts_map;
1538+
ts_map[Timestamp()] = "epoch";
1539+
ts_map[Timestamp(std::chrono::nanoseconds(1000000000LL))] = "one_sec";
1540+
EXPECT_EQ(ts_map.size(), 2u);
1541+
EXPECT_EQ(ts_map[Timestamp()], "epoch");
1542+
}
1543+
1544+
TEST(SerializationTest, DurationChronoConversion) {
1545+
auto ns = std::chrono::seconds(5) + std::chrono::nanoseconds(123);
1546+
Duration d(ns);
1547+
EXPECT_EQ(d.to_chrono(), ns);
1548+
EXPECT_EQ(d.count(), ns.count());
1549+
1550+
Duration zero;
1551+
EXPECT_EQ(zero.count(), 0);
1552+
EXPECT_EQ(zero.to_chrono(), std::chrono::nanoseconds(0));
1553+
}
1554+
1555+
TEST(SerializationTest, TimestampChronoConversion) {
1556+
auto ns = std::chrono::nanoseconds(1700000000000000000LL);
1557+
Timestamp ts(ns);
1558+
EXPECT_EQ(ts.time_since_epoch(), ns);
1559+
EXPECT_EQ(ts.to_chrono().time_since_epoch(), ns);
1560+
1561+
Timestamp epoch;
1562+
EXPECT_EQ(epoch.time_since_epoch().count(), 0);
1563+
}
1564+
1565+
TEST(SerializationTest, TemporalAnyUsesCanonicalCarriers) {
1566+
auto fory =
1567+
Fory::builder().xlang(true).compatible(false).track_ref(false).build();
1568+
ASSERT_TRUE(register_any_type<Duration>(fory.type_resolver()).ok());
1569+
ASSERT_TRUE(register_any_type<Timestamp>(fory.type_resolver()).ok());
1570+
1571+
expect_any_roundtrip(
1572+
fory, Duration(std::chrono::seconds(12) + std::chrono::nanoseconds(34)));
1573+
expect_any_roundtrip(fory,
1574+
Timestamp(std::chrono::nanoseconds(1234567890123LL)));
1575+
}
1576+
1577+
TEST(SerializationTest, ChronoTemporalAnyRegistrationIsRejected) {
1578+
using ChronoTimestamp = std::chrono::time_point<std::chrono::system_clock,
1579+
std::chrono::nanoseconds>;
1580+
auto fory =
1581+
Fory::builder().xlang(true).compatible(false).track_ref(false).build();
1582+
1583+
auto chrono_duration_registration =
1584+
register_any_type<std::chrono::nanoseconds>(fory.type_resolver());
1585+
ASSERT_FALSE(chrono_duration_registration.ok());
1586+
EXPECT_NE(chrono_duration_registration.error().to_string().find(
1587+
"explicit static targets"),
1588+
std::string::npos);
1589+
1590+
ASSERT_TRUE(register_any_type<Duration>(fory.type_resolver()).ok());
1591+
ASSERT_TRUE(register_any_type<Timestamp>(fory.type_resolver()).ok());
1592+
1593+
auto chrono_timestamp_registration =
1594+
register_any_type<ChronoTimestamp>(fory.type_resolver());
1595+
ASSERT_FALSE(chrono_timestamp_registration.ok());
1596+
EXPECT_NE(chrono_timestamp_registration.error().to_string().find(
1597+
"explicit static targets"),
1598+
std::string::npos);
1599+
1600+
expect_any_roundtrip(fory, Duration(std::chrono::nanoseconds(-1)));
1601+
expect_any_roundtrip(fory, Timestamp(std::chrono::nanoseconds(1000000000LL)));
1602+
}
1603+
1604+
TEST(SerializationTest, ForyOwnedAndChronoShareWireEncoding) {
1605+
auto fory =
1606+
Fory::builder().xlang(true).compatible(false).track_ref(false).build();
1607+
1608+
auto ns = std::chrono::seconds(7) + std::chrono::nanoseconds(654321);
1609+
Duration fory_dur(ns);
1610+
std::chrono::nanoseconds chrono_dur = ns;
1611+
1612+
auto fory_bytes = fory.serialize(fory_dur);
1613+
auto chrono_bytes = fory.serialize(chrono_dur);
1614+
ASSERT_TRUE(fory_bytes.ok());
1615+
ASSERT_TRUE(chrono_bytes.ok());
1616+
EXPECT_EQ(fory_bytes.value(), chrono_bytes.value());
1617+
1618+
auto decoded_from_fory = fory.deserialize<std::chrono::nanoseconds>(
1619+
fory_bytes.value().data(), fory_bytes.value().size());
1620+
ASSERT_TRUE(decoded_from_fory.ok());
1621+
EXPECT_EQ(decoded_from_fory.value(), ns);
1622+
1623+
auto decoded_from_chrono = fory.deserialize<Duration>(
1624+
chrono_bytes.value().data(), chrono_bytes.value().size());
1625+
ASSERT_TRUE(decoded_from_chrono.ok());
1626+
EXPECT_EQ(decoded_from_chrono.value(), fory_dur);
1627+
}
1628+
14921629
} // namespace test
14931630
} // namespace serialization
14941631
} // namespace fory

0 commit comments

Comments
 (0)