ChaiScript/unittests/strong_typedef.chai
leftibot a5f89326f1 Address review: register all operators unconditionally and add compound assignment operators
Remove conditional operator registration (op_exists_for_base_type check)
since users could add underlying operators later, and the runtime check was
expensive. Operators that fail on the underlying type now error at call time
instead of being absent. Add compound assignment operators (*=, +=, -=, /=,
%=, <<=, >>=, &=, |=, ^=) via Strong_Typedef_Compound_Assign_Op which
computes the base operation and stores the result back in __value.

Requested by @lefticus in PR #680 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:00:15 -06:00

201 lines
4.8 KiB
ChaiScript

// Strong typedef: using Type = int creates a distinct type
using Meters = int
def measure(Meters m) {
return m
}
// Constructing a strong typedef value should work
var m = Meters(42)
// Calling with the typedef'd value should succeed
measure(m)
// Calling with a plain int should fail (strong typedef)
try {
measure(42)
assert_equal(true, false)
} catch(e) {
// Expected: type mismatch because int is not Meters
}
// Multiple strong typedefs from the same base type should be distinct
using Seconds = int
def wait(Seconds s) {
return s
}
var s = Seconds(10)
wait(s)
// Meters and Seconds should not be interchangeable
try {
wait(m)
assert_equal(true, false)
} catch(e) {
// Expected: Meters is not Seconds
}
try {
measure(s)
assert_equal(true, false)
} catch(e) {
// Expected: Seconds is not Meters
}
// to_underlying should return the base value
assert_equal(to_underlying(m), 42)
assert_equal(to_underlying(s), 10)
// to_underlying result should be a plain value, not a strong typedef
def takes_int(int i) {
return i
}
assert_equal(takes_int(to_underlying(m)), 42)
// --- Arithmetic operators: strongly typed ---
var m2 = Meters(8)
var m_sum = m + m2
assert_equal(to_underlying(m_sum), 50)
measure(m_sum)
var m_diff = m - m2
assert_equal(to_underlying(m_diff), 34)
var m_prod = Meters(3) * Meters(4)
assert_equal(to_underlying(m_prod), 12)
var m_quot = Meters(20) / Meters(5)
assert_equal(to_underlying(m_quot), 4)
var m_rem = Meters(17) % Meters(5)
assert_equal(to_underlying(m_rem), 2)
// Arithmetic result is strongly typed, not plain int
try {
takes_int(m_sum)
assert_equal(true, false)
} catch(e) {
// Expected: m_sum is Meters, not int
}
// --- Comparison operators ---
assert_equal(Meters(5) == Meters(5), true)
assert_equal(Meters(5) != Meters(3), true)
assert_equal(Meters(3) < Meters(5), true)
assert_equal(Meters(5) > Meters(3), true)
assert_equal(Meters(5) <= Meters(5), true)
assert_equal(Meters(3) >= Meters(3), true)
assert_equal(Meters(3) >= Meters(5), false)
// --- Bitwise and shift operators ---
assert_equal(to_underlying(Meters(6) & Meters(3)), 2)
assert_equal(to_underlying(Meters(6) | Meters(3)), 7)
assert_equal(to_underlying(Meters(6) ^ Meters(3)), 5)
assert_equal(to_underlying(Meters(5) << Meters(2)), 20)
assert_equal(to_underlying(Meters(12) >> Meters(1)), 6)
// Bitwise results are strongly typed
try {
takes_int(Meters(6) & Meters(3))
assert_equal(true, false)
} catch(e) {
// Expected: result is Meters, not int
}
// --- Strong typedef over string ---
using StrongString = string
var ss1 = StrongString("hello")
var ss2 = StrongString(" world")
var ss_cat = ss1 + ss2
assert_equal(to_underlying(ss_cat), "hello world")
// StrongString + StrongString -> StrongString (strongly typed)
def takes_strong_string(StrongString ss) {
return ss
}
takes_strong_string(ss_cat)
// Operators not supported by the underlying type error at call time
try {
var bad = ss1 * ss2
assert_equal(true, false)
} catch(e) {
// Expected: underlying string has no * operator
}
try {
var bad = ss1 - ss2
assert_equal(true, false)
} catch(e) {
// Expected: underlying string has no - operator
}
try {
var bad = ss1 / ss2
assert_equal(true, false)
} catch(e) {
// Expected: underlying string has no / operator
}
try {
var bad = ss1 % ss2
assert_equal(true, false)
} catch(e) {
// Expected: underlying string has no % operator
}
// Comparison on StrongString
assert_equal(StrongString("abc") < StrongString("def"), true)
assert_equal(StrongString("abc") == StrongString("abc"), true)
assert_equal(StrongString("abc") != StrongString("def"), true)
assert_equal(StrongString("def") > StrongString("abc"), true)
assert_equal(StrongString("abc") <= StrongString("abc"), true)
assert_equal(StrongString("def") >= StrongString("abc"), true)
// --- User-defined extensions on strong typedefs ---
def first_char(StrongString ss) {
return to_string(to_underlying(ss)[0])
}
assert_equal(first_char(StrongString("hello")), "h")
def double_meters(Meters m) {
return Meters(to_underlying(m) * 2)
}
assert_equal(to_underlying(double_meters(Meters(21))), 42)
// User-defined operator extension
def `[]`(StrongString ss, int offset) {
return to_string(to_underlying(ss)[offset])
}
assert_equal(StrongString("hello")[1], "e")
// --- Compound assignment operators ---
var m3 = Meters(10)
m3 += Meters(5)
assert_equal(to_underlying(m3), 15)
measure(m3)
m3 -= Meters(3)
assert_equal(to_underlying(m3), 12)
m3 *= Meters(2)
assert_equal(to_underlying(m3), 24)
m3 /= Meters(4)
assert_equal(to_underlying(m3), 6)
m3 %= Meters(4)
assert_equal(to_underlying(m3), 2)
// Compound assignment result is still the strong typedef
var m4 = Meters(10)
m4 += Meters(5)
assert_equal(to_underlying(m4), 15)
measure(m4)
// Compound assignment on StrongString
var ss3 = StrongString("hello")
ss3 += StrongString(" world")
assert_equal(to_underlying(ss3), "hello world")
takes_strong_string(ss3)