From 2a47cc2ca9ee433e1387c73e41b80815a25e518f Mon Sep 17 00:00:00 2001 From: Zack Puhl Date: Sat, 2 Aug 2025 17:11:27 -0400 Subject: [PATCH] Pair and Triple Compare w/ Unit Tests (#2359) * Pair and Triple Compare w/ Unit Tests * scope creep myself by adding date-time eq op * make Pair and Triple printable * Update releasenotes. Restrict equals on tuples to when underlying type supports `==`. Remove unnecessary Time.eq. --------- Co-authored-by: Christoffer Lerno --- lib/std/collections/tuple.c3 | 30 +++++++++-- lib/std/core/types.c3 | 2 + lib/std/time/datetime.c3 | 7 +++ releasenotes.md | 1 + test/unit/stdlib/collections/tuple.c3 | 71 +++++++++++++++++++++++++++ test/unit/stdlib/time/datetime.c3 | 38 ++++++++++++++ test/unit/stdlib/time/time.c3 | 14 ++++++ 7 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 test/unit/stdlib/collections/tuple.c3 diff --git a/lib/std/collections/tuple.c3 b/lib/std/collections/tuple.c3 index 15764f420..d077df9dc 100644 --- a/lib/std/collections/tuple.c3 +++ b/lib/std/collections/tuple.c3 @@ -1,11 +1,17 @@ module std::collections::pair{Type1, Type2}; +import std::io; -struct Pair +struct Pair (Printable) { Type1 first; Type2 second; } +fn usz? Pair.to_format(&self, Formatter* f) @dynamic +{ + return f.printf("{ %s, %s }", self.first, self.second); +} + <* @param [&out] a @param [&out] b @@ -18,15 +24,27 @@ macro void Pair.unpack(&self, a, b) *b = self.second; } -module std::collections::triple{Type1, Type2, Type3}; +fn bool Pair.equal(self, Pair other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2)) +{ + return self.first == other.first && self.second == other.second; +} -struct Triple + + +module std::collections::triple{Type1, Type2, Type3}; +import std::io; + +struct Triple (Printable) { Type1 first; Type2 second; Type3 third; } +fn usz? Triple.to_format(&self, Formatter* f) @dynamic +{ + return f.printf("{ %s, %s, %s }", self.first, self.second, self.third); +} <* @param [&out] a @param [&out] b @@ -42,6 +60,12 @@ macro void Triple.unpack(&self, a, b, c) *c = self.third; } +fn bool Triple.equal(self, Triple other) @operator(==) @if (types::has_equals(Type1) &&& types::has_equals(Type2) &&& types::has_equals(Type3)) +{ + return self.first == other.first && self.second == other.second && self.third == other.third; +} + + module std::collections::tuple{Type1, Type2}; struct Tuple @deprecated("Use 'Pair' instead") diff --git a/lib/std/core/types.c3 b/lib/std/core/types.c3 index 2067fb3a9..4fa7d595e 100644 --- a/lib/std/core/types.c3 +++ b/lib/std/core/types.c3 @@ -339,6 +339,8 @@ macro bool is_same_vector_type($Type1, $Type2) @const $endif } +macro bool has_equals($Type) @const => $defined(($Type){} == ($Type){}); + macro bool is_equatable_type($Type) @const { $if $defined($Type.less) || $defined($Type.compare_to) || $defined($Type.equals): diff --git a/lib/std/time/datetime.c3 b/lib/std/time/datetime.c3 index 4a7defe39..e3b644337 100644 --- a/lib/std/time/datetime.c3 +++ b/lib/std/time/datetime.c3 @@ -112,6 +112,11 @@ fn TzDateTime TzDateTime.to_gmt_offset(self, int gmt_offset) { return { dt, gmt_offset }; } +fn bool TzDateTime.eq(self, TzDateTime other) @operator(==) @inline +{ + return self.to_gmt_offset(0).time == other.to_gmt_offset(0).time; +} + <* @require day >= 1 && day < 32 @require hour >= 0 && hour < 24 @@ -257,3 +262,5 @@ fn Duration DateTime.diff_us(self, DateTime from) @operator(-) { return self.time.diff_us(from.time); } + +fn bool DateTime.eq(self, DateTime other) @operator(==) @inline => self.time == other.time; diff --git a/releasenotes.md b/releasenotes.md index 04dd5645c..7bed9da4a 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -11,6 +11,7 @@ - With avx512, passing a 512 bit vector in a union would be lowered incorrectly, causing an assert. #2362 ### Stdlib changes +- Add `==` to `Pair`, `Triple` and TzDateTime. Add print to `Pair` and `Triple`. ## 0.7.4 Change list diff --git a/test/unit/stdlib/collections/tuple.c3 b/test/unit/stdlib/collections/tuple.c3 new file mode 100644 index 000000000..2ca165ade --- /dev/null +++ b/test/unit/stdlib/collections/tuple.c3 @@ -0,0 +1,71 @@ +module tuple_test; + +struct TestStruct +{ + String a; + int b; +} + +fn bool TestStruct.eq(self, TestStruct other) @operator(==) + => self.a == other.a && self.b == other.b; + + +module tuple_test @test; + +import std::collections::pair; +import std::collections::triple; + + +fn void pair_make_and_unpack() +{ + Pair { ulong, String } x = { 0x8034534, "some string" }; + + assert(x.first == 0x8034534); + assert(x.second == "some string"); + + ulong a; + String b; + x.unpack(&a, &b); + + assert(a == 0x8034534); + assert(b == "some string"); +} + +fn void pair_compare() +{ + Pair { TestStruct, String } x = { {"left", -123}, "right" }; + Pair { TestStruct, String } y = { {"left", -123}, "right" }; + Pair { TestStruct, String } z = { {"left", 4096}, "right" }; + + assert(x == y); + assert(x != z); +} + +fn void triple_make_and_unpack() +{ + int myval = 7; + Triple { ulong, String, int* } x = { 0xA_DEAD_C0DE, "yet another string", &myval }; + + assert(x.first == 0xA_DEAD_C0DE); + assert(x.second == "yet another string"); + assert(x.third == &myval); + + ulong a; + String b; + int* c; + x.unpack(&a, &b, &c); + + assert(a == 0xA_DEAD_C0DE); + assert(b == "yet another string"); + assert(c == &myval); +} + +fn void triple_compare() +{ + Triple { TestStruct, String, String } x = { {"in", 42}, "left", "right" }; + Triple { TestStruct, String, String } y = { {"in", 42}, "left", "right" }; + Triple { TestStruct, String, String } z = { {"in", 42}, "up", "down" }; + + assert(x == y); + assert(x != z); +} diff --git a/test/unit/stdlib/time/datetime.c3 b/test/unit/stdlib/time/datetime.c3 index e154d3562..165792e2e 100644 --- a/test/unit/stdlib/time/datetime.c3 +++ b/test/unit/stdlib/time/datetime.c3 @@ -49,6 +49,44 @@ fn void test_parse_and_add() test::eq(x.day, 4); } +fn void datetime_eq() +{ + DateTime d = datetime::from_date(1973, APRIL, 27, 12, 23, 55, 34); + DateTime d2 = d; + + assert(d == d2); + + d2 = d2.add_days(6); + assert(d != d2); + + d2 = d2.add_days(-5); + assert(d != d2); + + d2 = d2.add_days(-1); + assert(d == d2); +} + +fn void tzdatetime_eq() +{ + int offset1_hours = 7; + int offset2_hours = -1; + + TzDateTime d1 = datetime::from_date_tz(2022, OCTOBER, 15, 9, 07, 45, gmt_offset: offset1_hours * 3600); + TzDateTime d2 = datetime::from_date_tz(2022, OCTOBER, 15, 1, 07, 45, gmt_offset: offset2_hours * 3600); + + assert(d1 == d2); + + d2 = d2.add_years(30); + assert(d1 != d2); + + offset1_hours = 12; + offset2_hours = -10; + + d1 = datetime::from_date_tz(2022, OCTOBER, 15, 20, 15, 31, gmt_offset: offset1_hours * 3600); + d2 = datetime::from_date_tz(2022, OCTOBER, 14, 22, 15, 31, gmt_offset: offset2_hours * 3600); + assert(d1 == d2); +} + fn void test_timezone() { int offset_hours = 7; diff --git a/test/unit/stdlib/time/time.c3 b/test/unit/stdlib/time/time.c3 index d447f5461..7b6c72880 100644 --- a/test/unit/stdlib/time/time.c3 +++ b/test/unit/stdlib/time/time.c3 @@ -13,6 +13,20 @@ fn void time_diff() test::eq(t, t2); } +fn void time_eq() +{ + Time t = time::now(); + Time t2 = t; + + assert(t == t2); + + t2 += time::US; + assert(t != t2); + + t2 -= time::US; + assert(t == t2); +} + fn void clock_diff() { Clock c = clock::now();