Add IU64 and Span classes, to compute spans over all integers (#9021)
IU64 is a single numeric representation for both signed and unsigned
integers: it can contain as many negative values as a signed number can,
but also as many unsigned as well (so it needs more than 64 bits).
The Span class is a simple representation of contiguous Spans of
numbers.
diff --git a/src/support/iu64.h b/src/support/iu64.h
new file mode 100644
index 0000000..b916665
--- /dev/null
+++ b/src/support/iu64.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2026 WebAssembly Community Group participants
+ *
+ * Licensed 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.
+ */
+
+#ifndef wasm_support_i65_h
+#define wasm_support_i65_h
+
+#include <cstdint>
+#include <iostream>
+#include <limits>
+
+namespace wasm {
+
+// An integer capable of representing numbers in the combined range of 32 and
+// 64-bit integers, both signed and unsigned. That is, in the range
+//
+// std::numeric_limits<int64_t>::min() .. std::numeric_limits<uint64_t>::max()
+//
+// This is basically an i64 combined with a u64 in terms of range, hence "IU64".
+struct IU64 {
+ // A 64-bit payload with an extra 65th sign bit.
+ uint64_t value = 0;
+ bool negative = false;
+
+ constexpr IU64() = default;
+
+ // Unsigned values are simple.
+ constexpr IU64(uint32_t x) : value(x) {}
+ constexpr IU64(uint64_t x) : value(x) {}
+
+ // Signed values need to be checked for being negative.
+ constexpr IU64(int32_t x) {
+ if (x >= 0) {
+ value = x;
+ } else {
+ negative = true;
+ value = -int64_t(x);
+ }
+ }
+ constexpr IU64(int64_t x) {
+ if (x >= 0) {
+ value = x;
+ } else {
+ negative = true;
+
+ // One does not simply negate MIN_INT64.
+ if (x == std::numeric_limits<int64_t>::min()) {
+ value = uint64_t(1) << 63;
+ } else {
+ value = -int64_t(x);
+ }
+ }
+ }
+
+ constexpr bool operator==(const IU64& other) const {
+ return value == other.value && negative == other.negative;
+ }
+ constexpr bool operator!=(const IU64& other) const {
+ return !(*this == other);
+ }
+
+ constexpr bool operator<(const IU64& other) const {
+ if (negative) {
+ if (other.negative) {
+ // Both negative; we are smaller if absolute value is larger.
+ return value > other.value;
+ } else {
+ // Only we are negative, so we are smaller.
+ return true;
+ }
+ } else {
+ if (other.negative) {
+ // Only the other is negative, so we are larger.
+ return false;
+ } else {
+ // Both positive; we are smaller if absolute value is smaller.
+ return value < other.value;
+ }
+ }
+ }
+ constexpr bool operator<=(const IU64& other) const {
+ return *this < other || *this == other;
+ }
+ constexpr bool operator>(const IU64& other) const {
+ return !(*this <= other);
+ }
+ constexpr bool operator>=(const IU64& other) const {
+ return !(*this < other);
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& os, const IU64& x) {
+ if (x.negative) {
+ os << '-';
+ }
+ return os << x.value;
+}
+
+} // namespace wasm
+
+namespace std {
+
+template<> class numeric_limits<wasm::IU64> {
+public:
+ static constexpr bool is_specialized = true;
+ static constexpr bool is_signed = true;
+ static constexpr bool is_integer = true;
+ static constexpr bool is_exact = true;
+ static constexpr bool has_infinity = false;
+ static constexpr bool has_quiet_NaN = false;
+ static constexpr bool has_signaling_NaN = false;
+ static constexpr float_denorm_style has_denorm = denorm_absent;
+ static constexpr bool has_denorm_loss = false;
+ static constexpr float_round_style round_style = round_toward_zero;
+ static constexpr bool is_iec559 = false;
+ static constexpr bool is_bounded = true;
+ static constexpr bool is_modulo = false;
+ static constexpr int digits = 65;
+ static constexpr int digits10 = 19;
+ static constexpr int max_digits10 = 0;
+ static constexpr int radix = 2;
+ static constexpr int min_exponent = 0;
+ static constexpr int min_exponent10 = 0;
+ static constexpr int max_exponent = 0;
+ static constexpr int max_exponent10 = 0;
+ static constexpr bool traps = false;
+ static constexpr bool tinyness_before = false;
+
+ static constexpr wasm::IU64 min() noexcept {
+ return wasm::IU64(std::numeric_limits<int64_t>::min());
+ }
+ static constexpr wasm::IU64 lowest() noexcept { return min(); }
+ static constexpr wasm::IU64 max() noexcept {
+ return wasm::IU64(std::numeric_limits<uint64_t>::max());
+ }
+};
+
+} // namespace std
+
+#endif // wasm_support_i65_h
diff --git a/src/support/span.h b/src/support/span.h
new file mode 100644
index 0000000..05cdbce
--- /dev/null
+++ b/src/support/span.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2026 WebAssembly Community Group participants
+ *
+ * Licensed 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.
+ */
+
+#ifndef wasm_support_span_h
+#define wasm_support_span_h
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <limits>
+
+namespace wasm {
+
+// A span of values.
+//
+// Span{min, max} means [min, max], inclusive of both sides. To represent an
+// empty span, we use min > max.
+template<typename T> struct Span {
+ static constexpr T Min = std::numeric_limits<T>::lowest();
+ static constexpr T Max = std::numeric_limits<T>::max();
+
+ T min = Min;
+ T max = Max;
+
+ constexpr Span() = default;
+ constexpr Span(T min, T max) : min(min), max(max) {}
+
+ // Set a single value as possible.
+ void set(T value) { min = max = value; }
+
+ // To represent an empty span, we use min > max, an impossible span.
+ void setEmpty() {
+ *this = empty();
+ assert(isEmpty());
+ }
+
+ bool isEmpty() const { return min > max; }
+
+ static Span<T> empty() { return Span{Max, Min}; }
+
+ void setFull() {
+ *this = Span();
+ assert(isFull());
+ }
+
+ bool isFull() const { return min == Min && max == Max; }
+
+ static Span<T> full() { return Span{}; }
+
+ // Intersect this with another span, returning a (possibly empty) span.
+ Span<T> intersection(const Span& other) const {
+ if (isEmpty() || other.isEmpty()) {
+ return empty();
+ }
+ return Span<T>{std::max(min, other.min), std::min(max, other.max)};
+ }
+
+ // Checks whether two spans have any overlap at all.
+ bool hasOverlap(const Span& other) const {
+ return !intersection(other).isEmpty();
+ }
+
+ // Check whether we contain another span (possibly being equal).
+ bool contains(const Span& other) const {
+ return intersection(other) == other;
+ }
+
+ bool operator==(const Span& other) const {
+ if (isEmpty()) {
+ return other.isEmpty();
+ }
+ return !other.isEmpty() && min == other.min && max == other.max;
+ }
+ bool operator!=(const Span& other) const { return !(*this == other); }
+};
+
+template<typename T>
+inline std::ostream& operator<<(std::ostream& os, const Span<T>& span) {
+ if (span.isEmpty()) {
+ return os << "[empty]";
+ }
+ return os << '[' << span.min << ", " << span.max << ']';
+}
+
+} // namespace wasm
+
+#endif // wasm_support_span_h
diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt
index ab49c49..925880b 100644
--- a/test/gtest/CMakeLists.txt
+++ b/test/gtest/CMakeLists.txt
@@ -17,6 +17,7 @@
disjoint_sets.cpp
graph.cpp
int128.cpp
+ iu64.cpp
leaves.cpp
glbs.cpp
inplace_vector.cpp
@@ -31,6 +32,7 @@
printing.cpp
public-type-validator.cpp
scc.cpp
+ span.cpp
stringify.cpp
subtype-exprs.cpp
suffix_tree.cpp
diff --git a/test/gtest/iu64.cpp b/test/gtest/iu64.cpp
new file mode 100644
index 0000000..1f812e9
--- /dev/null
+++ b/test/gtest/iu64.cpp
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2026 WebAssembly Community Group participants
+ *
+ * Licensed 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.
+ */
+
+#include <cstdint>
+#include <limits>
+#include <sstream>
+#include <vector>
+
+#include "support/iu64.h"
+#include "gtest/gtest.h"
+
+using namespace wasm;
+
+TEST(IU64Test, DefaultConstruct) {
+ IU64 x;
+ EXPECT_EQ(x.value, 0u);
+ EXPECT_FALSE(x.negative);
+ EXPECT_EQ(x, IU64(0));
+}
+
+TEST(IU64Test, ConstructFromUnsigned32) {
+ uint32_t zero = 0;
+ uint32_t one = 1;
+ uint32_t mid = 12345678;
+ uint32_t maxU32 = std::numeric_limits<uint32_t>::max();
+
+ IU64 iZero(zero);
+ EXPECT_EQ(iZero.value, 0u);
+ EXPECT_FALSE(iZero.negative);
+
+ IU64 iOne(one);
+ EXPECT_EQ(iOne.value, 1u);
+ EXPECT_FALSE(iOne.negative);
+
+ IU64 iMid(mid);
+ EXPECT_EQ(iMid.value, mid);
+ EXPECT_FALSE(iMid.negative);
+
+ IU64 iMax(maxU32);
+ EXPECT_EQ(iMax.value, uint64_t(maxU32));
+ EXPECT_FALSE(iMax.negative);
+}
+
+TEST(IU64Test, ConstructFromSigned32) {
+ int32_t zero = 0;
+ int32_t one = 1;
+ int32_t maxI32 = std::numeric_limits<int32_t>::max();
+ int32_t negOne = -1;
+ int32_t negMid = -12345678;
+ int32_t minI32 = std::numeric_limits<int32_t>::min();
+
+ IU64 iZero(zero);
+ EXPECT_EQ(iZero.value, 0u);
+ EXPECT_FALSE(iZero.negative);
+
+ IU64 iOne(one);
+ EXPECT_EQ(iOne.value, 1u);
+ EXPECT_FALSE(iOne.negative);
+
+ IU64 iMax(maxI32);
+ EXPECT_EQ(iMax.value, uint64_t(maxI32));
+ EXPECT_FALSE(iMax.negative);
+
+ IU64 iNegOne(negOne);
+ EXPECT_EQ(iNegOne.value, 1u);
+ EXPECT_TRUE(iNegOne.negative);
+
+ IU64 iNegMid(negMid);
+ EXPECT_EQ(iNegMid.value, 12345678u);
+ EXPECT_TRUE(iNegMid.negative);
+
+ IU64 iMin(minI32);
+ EXPECT_EQ(iMin.value, 2147483648ULL);
+ EXPECT_TRUE(iMin.negative);
+}
+
+TEST(IU64Test, ConstructFromUnsigned64) {
+ uint64_t zero = 0;
+ uint64_t one = 1;
+ uint64_t maxU32 = std::numeric_limits<uint32_t>::max();
+ uint64_t maxI64 = std::numeric_limits<int64_t>::max();
+ uint64_t highBitOnly = uint64_t(1) << 63;
+ uint64_t maxU64 = std::numeric_limits<uint64_t>::max();
+
+ IU64 iZero(zero);
+ EXPECT_EQ(iZero.value, 0u);
+ EXPECT_FALSE(iZero.negative);
+
+ IU64 iOne(one);
+ EXPECT_EQ(iOne.value, 1u);
+ EXPECT_FALSE(iOne.negative);
+
+ IU64 iMaxU32(maxU32);
+ EXPECT_EQ(iMaxU32.value, maxU32);
+ EXPECT_FALSE(iMaxU32.negative);
+
+ IU64 iMaxI64(maxI64);
+ EXPECT_EQ(iMaxI64.value, maxI64);
+ EXPECT_FALSE(iMaxI64.negative);
+
+ IU64 iHighBit(highBitOnly);
+ EXPECT_EQ(iHighBit.value, highBitOnly);
+ EXPECT_FALSE(iHighBit.negative);
+
+ IU64 iMaxU64(maxU64);
+ EXPECT_EQ(iMaxU64.value, maxU64);
+ EXPECT_FALSE(iMaxU64.negative);
+}
+
+TEST(IU64Test, ConstructFromSigned64) {
+ int64_t zero = 0;
+ int64_t one = 1;
+ int64_t maxI64 = std::numeric_limits<int64_t>::max();
+ int64_t negOne = -1;
+ int64_t minI64 = std::numeric_limits<int64_t>::min();
+ int64_t minI64PlusOne = std::numeric_limits<int64_t>::min() + 1;
+
+ IU64 iZero(zero);
+ EXPECT_EQ(iZero.value, 0u);
+ EXPECT_FALSE(iZero.negative);
+
+ IU64 iOne(one);
+ EXPECT_EQ(iOne.value, 1u);
+ EXPECT_FALSE(iOne.negative);
+
+ IU64 iMax(maxI64);
+ EXPECT_EQ(iMax.value, uint64_t(maxI64));
+ EXPECT_FALSE(iMax.negative);
+
+ IU64 iNegOne(negOne);
+ EXPECT_EQ(iNegOne.value, 1u);
+ EXPECT_TRUE(iNegOne.negative);
+
+ IU64 iMin(minI64);
+ EXPECT_EQ(iMin.value, uint64_t(1) << 63);
+ EXPECT_TRUE(iMin.negative);
+
+ IU64 iMinPlusOne(minI64PlusOne);
+ EXPECT_EQ(iMinPlusOne.value, uint64_t(std::numeric_limits<int64_t>::max()));
+ EXPECT_TRUE(iMinPlusOne.negative);
+}
+
+TEST(IU64Test, EqualityAndInequality) {
+ EXPECT_EQ(IU64(int32_t(0)), IU64(uint32_t(0)));
+ EXPECT_EQ(IU64(int32_t(0)), IU64(int64_t(0)));
+ EXPECT_EQ(IU64(int32_t(0)), IU64(uint64_t(0)));
+
+ EXPECT_EQ(IU64(int32_t(42)), IU64(uint32_t(42)));
+ EXPECT_EQ(IU64(int32_t(42)), IU64(int64_t(42)));
+ EXPECT_EQ(IU64(int32_t(42)), IU64(uint64_t(42)));
+
+ EXPECT_EQ(IU64(int32_t(-42)), IU64(int64_t(-42)));
+ EXPECT_EQ(IU64(std::numeric_limits<int32_t>::min()),
+ IU64(int64_t(std::numeric_limits<int32_t>::min())));
+
+ EXPECT_NE(IU64(int32_t(1)), IU64(int32_t(-1)));
+ EXPECT_NE(IU64(uint64_t(0xffffffffffffffffULL)), IU64(int64_t(-1)));
+ EXPECT_NE(IU64(std::numeric_limits<int64_t>::min()), IU64(uint64_t(1) << 63));
+}
+
+TEST(IU64Test, TotalOrdering) {
+ std::vector<IU64> sortedValues = {
+ IU64(std::numeric_limits<int64_t>::min()),
+ IU64(std::numeric_limits<int64_t>::min() + 1),
+ IU64(int64_t(-0x100000000LL)),
+ IU64(std::numeric_limits<int32_t>::min()),
+ IU64(int32_t(-12345)),
+ IU64(int64_t(-2)),
+ IU64(int64_t(-1)),
+ IU64(0),
+ IU64(1),
+ IU64(2),
+ IU64(int32_t(12345)),
+ IU64(std::numeric_limits<int32_t>::max()),
+ IU64(uint64_t(std::numeric_limits<int32_t>::max()) + 1),
+ IU64(std::numeric_limits<uint32_t>::max()),
+ IU64(uint64_t(std::numeric_limits<uint32_t>::max()) + 1),
+ IU64(std::numeric_limits<int64_t>::max() - 1),
+ IU64(std::numeric_limits<int64_t>::max()),
+ IU64(uint64_t(std::numeric_limits<int64_t>::max()) + 1),
+ IU64(std::numeric_limits<uint64_t>::max() - 1),
+ IU64(std::numeric_limits<uint64_t>::max()),
+ };
+
+ for (size_t i = 0; i < sortedValues.size(); ++i) {
+ for (size_t j = 0; j < sortedValues.size(); ++j) {
+ const auto& a = sortedValues[i];
+ const auto& b = sortedValues[j];
+
+ if (i < j) {
+ EXPECT_LT(a, b);
+ EXPECT_LE(a, b);
+ EXPECT_GT(b, a);
+ EXPECT_GE(b, a);
+ EXPECT_NE(a, b);
+ EXPECT_FALSE(a == b);
+ EXPECT_FALSE(b < a);
+ } else if (i == j) {
+ EXPECT_EQ(a, b);
+ EXPECT_LE(a, b);
+ EXPECT_GE(a, b);
+ EXPECT_FALSE(a < b);
+ EXPECT_FALSE(a > b);
+ EXPECT_FALSE(a != b);
+ } else {
+ EXPECT_GT(a, b);
+ EXPECT_GE(a, b);
+ EXPECT_LT(b, a);
+ EXPECT_LE(b, a);
+ EXPECT_NE(a, b);
+ EXPECT_FALSE(a == b);
+ EXPECT_FALSE(a < b);
+ }
+ }
+ }
+}
+
+TEST(IU64Test, NumericLimits) {
+ EXPECT_TRUE(std::numeric_limits<IU64>::is_specialized);
+ EXPECT_TRUE(std::numeric_limits<IU64>::is_signed);
+ EXPECT_TRUE(std::numeric_limits<IU64>::is_integer);
+
+ EXPECT_EQ(std::numeric_limits<IU64>::min(),
+ IU64(std::numeric_limits<int64_t>::min()));
+ EXPECT_EQ(std::numeric_limits<IU64>::lowest(),
+ IU64(std::numeric_limits<int64_t>::min()));
+ EXPECT_EQ(std::numeric_limits<IU64>::max(),
+ IU64(std::numeric_limits<uint64_t>::max()));
+}
+
+TEST(IU64Test, StreamOutput) {
+ auto toString = [](const IU64& x) {
+ std::ostringstream ss;
+ ss << x;
+ return ss.str();
+ };
+
+ EXPECT_EQ(toString(IU64(0)), "0");
+ EXPECT_EQ(toString(IU64(42)), "42");
+ EXPECT_EQ(toString(IU64(-42)), "-42");
+ EXPECT_EQ(toString(IU64(std::numeric_limits<int64_t>::min())),
+ "-9223372036854775808");
+ EXPECT_EQ(toString(IU64(std::numeric_limits<uint64_t>::max())),
+ "18446744073709551615");
+}
diff --git a/test/gtest/span.cpp b/test/gtest/span.cpp
new file mode 100644
index 0000000..f05fd80
--- /dev/null
+++ b/test/gtest/span.cpp
@@ -0,0 +1,362 @@
+/*
+ * Copyright 2026 WebAssembly Community Group participants
+ *
+ * Licensed 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.
+ */
+
+#include <cstdint>
+#include <limits>
+#include <sstream>
+
+#include "support/iu64.h"
+#include "support/span.h"
+#include "gtest/gtest.h"
+
+using namespace wasm;
+
+// ============================================================================
+// Generic Span<T> tests
+// ============================================================================
+
+TEST(SpanTest, EmptySpanInt) {
+ Span<int32_t> empty = Span<int32_t>::empty();
+ EXPECT_TRUE(empty.isEmpty());
+ EXPECT_FALSE(empty.isFull());
+
+ Span<int32_t> invalid(10, 5);
+ EXPECT_TRUE(invalid.isEmpty());
+ EXPECT_FALSE(invalid.isFull());
+
+ EXPECT_EQ(empty, invalid);
+
+ Span<int32_t> s;
+ EXPECT_FALSE(s.isEmpty());
+ s.setEmpty();
+ EXPECT_TRUE(s.isEmpty());
+ EXPECT_EQ(s, empty);
+}
+
+TEST(SpanTest, FullSpanIntTypes) {
+ // Signed 32-bit
+ Span<int32_t> fullI32 = Span<int32_t>::full();
+ EXPECT_TRUE(fullI32.isFull());
+ EXPECT_FALSE(fullI32.isEmpty());
+ EXPECT_EQ(fullI32.min, std::numeric_limits<int32_t>::min());
+ EXPECT_EQ(fullI32.max, std::numeric_limits<int32_t>::max());
+
+ Span<int32_t> defI32;
+ EXPECT_TRUE(defI32.isFull());
+ EXPECT_EQ(defI32, fullI32);
+
+ // Unsigned 32-bit
+ Span<uint32_t> fullU32 = Span<uint32_t>::full();
+ EXPECT_TRUE(fullU32.isFull());
+ EXPECT_FALSE(fullU32.isEmpty());
+ EXPECT_EQ(fullU32.min, 0u);
+ EXPECT_EQ(fullU32.max, std::numeric_limits<uint32_t>::max());
+
+ // Signed 64-bit
+ Span<int64_t> fullI64 = Span<int64_t>::full();
+ EXPECT_TRUE(fullI64.isFull());
+ EXPECT_FALSE(fullI64.isEmpty());
+ EXPECT_EQ(fullI64.min, std::numeric_limits<int64_t>::min());
+ EXPECT_EQ(fullI64.max, std::numeric_limits<int64_t>::max());
+
+ // Unsigned 64-bit
+ Span<uint64_t> fullU64 = Span<uint64_t>::full();
+ EXPECT_TRUE(fullU64.isFull());
+ EXPECT_FALSE(fullU64.isEmpty());
+ EXPECT_EQ(fullU64.min, 0ull);
+ EXPECT_EQ(fullU64.max, std::numeric_limits<uint64_t>::max());
+}
+
+TEST(SpanTest, SetSingleValue) {
+ Span<int32_t> s;
+ s.set(42);
+ EXPECT_EQ(s.min, 42);
+ EXPECT_EQ(s.max, 42);
+ EXPECT_FALSE(s.isEmpty());
+ EXPECT_FALSE(s.isFull());
+ EXPECT_EQ(s, Span<int32_t>(42, 42));
+}
+
+TEST(SpanTest, SetFull) {
+ Span<int32_t> s(10, 20);
+ EXPECT_FALSE(s.isFull());
+ s.setFull();
+ EXPECT_TRUE(s.isFull());
+ EXPECT_EQ(s.min, std::numeric_limits<int32_t>::min());
+ EXPECT_EQ(s.max, std::numeric_limits<int32_t>::max());
+}
+
+TEST(SpanTest, IntersectionInt) {
+ Span<int32_t> a(1, 10);
+ Span<int32_t> b(5, 15);
+ Span<int32_t> ab = a.intersection(b);
+ EXPECT_EQ(ab, Span<int32_t>(5, 10));
+
+ // Commutativity
+ EXPECT_EQ(b.intersection(a), Span<int32_t>(5, 10));
+
+ // Touching at a single point
+ Span<int32_t> c(10, 20);
+ EXPECT_EQ(a.intersection(c), Span<int32_t>(10, 10));
+
+ // Disjoint
+ Span<int32_t> d(11, 20);
+ EXPECT_TRUE(a.intersection(d).isEmpty());
+ EXPECT_EQ(a.intersection(d), Span<int32_t>::empty());
+
+ // Contained
+ Span<int32_t> e(3, 7);
+ EXPECT_EQ(a.intersection(e), Span<int32_t>(3, 7));
+
+ // Identical
+ EXPECT_EQ(a.intersection(a), a);
+
+ // With empty
+ EXPECT_TRUE(a.intersection(Span<int32_t>::empty()).isEmpty());
+ EXPECT_TRUE(Span<int32_t>::empty().intersection(a).isEmpty());
+
+ // With full
+ EXPECT_EQ(a.intersection(Span<int32_t>::full()), a);
+ EXPECT_EQ(Span<int32_t>::full().intersection(a), a);
+}
+
+TEST(SpanTest, HasOverlapInt) {
+ Span<int32_t> a(1, 10);
+ Span<int32_t> b(5, 15);
+ Span<int32_t> c(10, 20);
+ Span<int32_t> d(11, 20);
+
+ EXPECT_TRUE(a.hasOverlap(b));
+ EXPECT_TRUE(b.hasOverlap(a));
+ EXPECT_TRUE(a.hasOverlap(c));
+ EXPECT_FALSE(a.hasOverlap(d));
+ EXPECT_FALSE(d.hasOverlap(a));
+
+ EXPECT_FALSE(a.hasOverlap(Span<int32_t>::empty()));
+ EXPECT_TRUE(a.hasOverlap(Span<int32_t>::full()));
+ EXPECT_FALSE(Span<int32_t>::empty().hasOverlap(Span<int32_t>::full()));
+}
+
+TEST(SpanTest, ContainsInt) {
+ Span<int32_t> a(1, 10);
+ Span<int32_t> b(3, 7);
+ Span<int32_t> c(5, 15);
+ Span<int32_t> d(11, 20);
+
+ EXPECT_TRUE(a.contains(b));
+ EXPECT_FALSE(b.contains(a));
+
+ EXPECT_TRUE(a.contains(a));
+ EXPECT_FALSE(a.contains(c));
+ EXPECT_FALSE(a.contains(d));
+
+ EXPECT_TRUE(a.contains(Span<int32_t>::empty()));
+ EXPECT_TRUE(Span<int32_t>::empty().contains(Span<int32_t>::empty()));
+ EXPECT_FALSE(Span<int32_t>::empty().contains(a));
+
+ EXPECT_TRUE(Span<int32_t>::full().contains(a));
+ EXPECT_TRUE(Span<int32_t>::full().contains(Span<int32_t>::empty()));
+ EXPECT_FALSE(a.contains(Span<int32_t>::full()));
+}
+
+TEST(SpanTest, StreamOutput) {
+ auto toString = [](const auto& span) {
+ std::ostringstream ss;
+ ss << span;
+ return ss.str();
+ };
+
+ EXPECT_EQ(toString(Span<int32_t>(1, 10)), "[1, 10]");
+ EXPECT_EQ(toString(Span<int32_t>::empty()), "[empty]");
+ EXPECT_EQ(toString(Span<int32_t>(10, 5)), "[empty]");
+}
+
+// ============================================================================
+// Span<IU64> tests (corner cases, sign mixing, large range)
+// ============================================================================
+
+TEST(SpanIU64Test, FullAndLimits) {
+ EXPECT_EQ(Span<IU64>::Min, IU64(std::numeric_limits<int64_t>::min()));
+ EXPECT_EQ(Span<IU64>::Max, IU64(std::numeric_limits<uint64_t>::max()));
+
+ Span<IU64> full = Span<IU64>::full();
+ EXPECT_TRUE(full.isFull());
+ EXPECT_FALSE(full.isEmpty());
+ EXPECT_EQ(full.min, IU64(std::numeric_limits<int64_t>::min()));
+ EXPECT_EQ(full.max, IU64(std::numeric_limits<uint64_t>::max()));
+
+ // Default constructed span is full
+ Span<IU64> def;
+ EXPECT_TRUE(def.isFull());
+ EXPECT_FALSE(def.isEmpty());
+ EXPECT_EQ(def, full);
+}
+
+TEST(SpanIU64Test, Empty) {
+ Span<IU64> empty = Span<IU64>::empty();
+ EXPECT_TRUE(empty.isEmpty());
+ EXPECT_FALSE(empty.isFull());
+
+ Span<IU64> empty2{IU64(100), IU64(-100)};
+ EXPECT_TRUE(empty2.isEmpty());
+ EXPECT_FALSE(empty2.isFull());
+ EXPECT_EQ(empty, empty2);
+
+ Span<IU64> empty3{IU64(uint64_t(1)), IU64(int64_t(-1))};
+ EXPECT_TRUE(empty3.isEmpty());
+ EXPECT_EQ(empty, empty3);
+}
+
+TEST(SpanIU64Test, SingletonsAtExtremes) {
+ // Min int64 singleton
+ Span<IU64> minI64{IU64(std::numeric_limits<int64_t>::min()),
+ IU64(std::numeric_limits<int64_t>::min())};
+ EXPECT_FALSE(minI64.isEmpty());
+ EXPECT_FALSE(minI64.isFull());
+ EXPECT_EQ(minI64.min, IU64(std::numeric_limits<int64_t>::min()));
+ EXPECT_EQ(minI64.max, IU64(std::numeric_limits<int64_t>::min()));
+
+ // -1 singleton
+ Span<IU64> negOne{IU64(-1), IU64(-1)};
+ EXPECT_FALSE(negOne.isEmpty());
+
+ // 0 singleton
+ Span<IU64> zero{IU64(0), IU64(0)};
+ EXPECT_FALSE(zero.isEmpty());
+
+ // 1 singleton
+ Span<IU64> one{IU64(1), IU64(1)};
+ EXPECT_FALSE(one.isEmpty());
+
+ // Max int64 singleton
+ Span<IU64> maxI64{IU64(std::numeric_limits<int64_t>::max()),
+ IU64(std::numeric_limits<int64_t>::max())};
+ EXPECT_FALSE(maxI64.isEmpty());
+
+ // 2^63 singleton (above int64_t max, into uint64_t territory)
+ Span<IU64> highBit{IU64(uint64_t(1) << 63), IU64(uint64_t(1) << 63)};
+ EXPECT_FALSE(highBit.isEmpty());
+
+ // Max uint64 singleton
+ Span<IU64> maxU64{IU64(std::numeric_limits<uint64_t>::max()),
+ IU64(std::numeric_limits<uint64_t>::max())};
+ EXPECT_FALSE(maxU64.isEmpty());
+}
+
+TEST(SpanIU64Test, CrossingZero) {
+ Span<IU64> span{IU64(-10), IU64(10)};
+ EXPECT_FALSE(span.isEmpty());
+ EXPECT_FALSE(span.isFull());
+
+ // Contains points inside
+ EXPECT_TRUE(span.contains(Span<IU64>(IU64(-10), IU64(-10))));
+ EXPECT_TRUE(span.contains(Span<IU64>(IU64(-5), IU64(5))));
+ EXPECT_TRUE(span.contains(Span<IU64>(IU64(0), IU64(0))));
+ EXPECT_TRUE(span.contains(Span<IU64>(IU64(10), IU64(10))));
+
+ // Does not contain points outside
+ EXPECT_FALSE(span.contains(Span<IU64>(IU64(-11), IU64(-11))));
+ EXPECT_FALSE(span.contains(Span<IU64>(IU64(11), IU64(11))));
+ EXPECT_FALSE(span.contains(Span<IU64>(IU64(-15), IU64(5))));
+ EXPECT_FALSE(span.contains(Span<IU64>(IU64(-5), IU64(15))));
+}
+
+TEST(SpanIU64Test, NegativeAndPositiveIntersections) {
+ Span<IU64> neg{IU64(-100), IU64(-10)};
+ Span<IU64> pos{IU64(10), IU64(100)};
+
+ EXPECT_FALSE(neg.hasOverlap(pos));
+ EXPECT_FALSE(pos.hasOverlap(neg));
+ EXPECT_TRUE(neg.intersection(pos).isEmpty());
+ EXPECT_TRUE(pos.intersection(neg).isEmpty());
+
+ Span<IU64> touchNegZero{IU64(-10), IU64(0)};
+ Span<IU64> touchZeroPos{IU64(0), IU64(10)};
+ EXPECT_TRUE(touchNegZero.hasOverlap(touchZeroPos));
+ EXPECT_EQ(touchNegZero.intersection(touchZeroPos),
+ Span<IU64>(IU64(0), IU64(0)));
+
+ Span<IU64> overlap{IU64(-50), IU64(50)};
+ EXPECT_EQ(neg.intersection(overlap), Span<IU64>(IU64(-50), IU64(-10)));
+ EXPECT_EQ(pos.intersection(overlap), Span<IU64>(IU64(10), IU64(50)));
+}
+
+TEST(SpanIU64Test, SignedUnsignedBoundary) {
+ // Test around INT64_MAX and 2^63
+ int64_t maxI64 = std::numeric_limits<int64_t>::max();
+ uint64_t highBit = uint64_t(maxI64) + 1; // 0x8000000000000000ULL
+
+ Span<IU64> s1(IU64(maxI64 - 100), IU64(highBit + 50));
+ Span<IU64> s2(IU64(highBit), IU64(highBit + 100));
+
+ EXPECT_TRUE(s1.hasOverlap(s2));
+ EXPECT_EQ(s1.intersection(s2), Span<IU64>(IU64(highBit), IU64(highBit + 50)));
+
+ // Disjoint near 2^63 boundary
+ Span<IU64> s3{IU64(maxI64 - 200), IU64(maxI64)};
+ Span<IU64> s4{IU64(highBit + 1), IU64(highBit + 100)};
+ EXPECT_FALSE(s3.hasOverlap(s4));
+ EXPECT_TRUE(s3.intersection(s4).isEmpty());
+
+ // Adjacent touching at 2^63
+ Span<IU64> s5{IU64(maxI64), IU64(highBit)};
+ Span<IU64> s6{IU64(highBit), IU64(highBit + 10)};
+ EXPECT_TRUE(s5.hasOverlap(s6));
+ EXPECT_EQ(s5.intersection(s6), Span<IU64>(IU64(highBit), IU64(highBit)));
+}
+
+TEST(SpanIU64Test, ExtremeBoundaries) {
+ Span<IU64> minPart(IU64(std::numeric_limits<int64_t>::min()),
+ IU64(std::numeric_limits<int64_t>::min() + 100));
+ Span<IU64> maxPart(IU64(std::numeric_limits<uint64_t>::max() - 100),
+ IU64(std::numeric_limits<uint64_t>::max()));
+
+ EXPECT_FALSE(minPart.hasOverlap(maxPart));
+ EXPECT_TRUE(minPart.intersection(maxPart).isEmpty());
+
+ Span<IU64> full = Span<IU64>::full();
+ EXPECT_TRUE(full.contains(minPart));
+ EXPECT_TRUE(full.contains(maxPart));
+ EXPECT_EQ(full.intersection(minPart), minPart);
+ EXPECT_EQ(full.intersection(maxPart), maxPart);
+ EXPECT_TRUE(full.hasOverlap(minPart));
+ EXPECT_TRUE(full.hasOverlap(maxPart));
+
+ Span<IU64> allNeg(IU64(std::numeric_limits<int64_t>::min()), IU64(-1));
+ Span<IU64> allNonNeg(IU64(0), IU64(std::numeric_limits<uint64_t>::max()));
+
+ EXPECT_FALSE(allNeg.hasOverlap(allNonNeg));
+ EXPECT_TRUE(allNeg.intersection(allNonNeg).isEmpty());
+ EXPECT_TRUE(full.contains(allNeg));
+ EXPECT_TRUE(full.contains(allNonNeg));
+}
+
+TEST(SpanIU64Test, SetAndMutate) {
+ Span<IU64> s;
+ EXPECT_TRUE(s.isFull());
+
+ s.set(IU64(-12345));
+ EXPECT_FALSE(s.isFull());
+ EXPECT_FALSE(s.isEmpty());
+ EXPECT_EQ(s.min, IU64(-12345));
+ EXPECT_EQ(s.max, IU64(-12345));
+
+ s.setEmpty();
+ EXPECT_TRUE(s.isEmpty());
+
+ s.setFull();
+ EXPECT_TRUE(s.isFull());
+}