// Copyright 2026 International Digital Economy Academy
//
// 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.
///|
/// Trait for types that can be converted to human-readable debugging info.
pub(open) trait Debug {
#as_free_fn(deprecated="Use `Repr(x)` instead")
fn to_repr(Self) -> Repr
}
///|
pub impl Debug for Int with fn to_repr(self) {
Repr::integer(self.to_string())
}
///|
pub impl Debug for Int16 with fn to_repr(self) {
Repr::integer(self.to_string())
}
///|
pub impl Debug for Int64 with fn to_repr(self) {
Repr::integer(self.to_string())
}
///|
pub impl Debug for UInt16 with fn to_repr(self) {
Repr::integer(self.to_string())
}
///|
pub impl Debug for UInt with fn to_repr(self) {
Repr::integer(self.to_string())
}
///|
pub impl Debug for UInt64 with fn to_repr(self) {
Repr::integer(self.to_string())
}
///|
pub impl Debug for Double with fn to_repr(self) {
Repr::double(self)
}
///|
pub impl Debug for Float with fn to_repr(self) {
Repr::float(self)
}
///|
pub impl Debug for Bool with fn to_repr(self) {
Repr::bool(self)
}
///|
pub impl Debug for Byte with fn to_repr(self) {
// Use hex to make byte sequences easier to read.
Repr::literal("0x" + self.to_hex())
}
///|
pub impl Debug for Char with fn to_repr(self) {
Repr::char(self)
}
///|
pub impl Debug for String with fn to_repr(self) {
Repr::string(self)
}
///|
pub impl Debug for StringView with fn to_repr(self) {
Repr::opaque_("StringView", Repr::string(self.to_owned()))
}
///|
pub impl Debug for Bytes with fn to_repr(self) {
Repr::opaque_("Bytes", Repr::array(self.to_array().map(x => Repr(x))))
}
///|
pub impl Debug for BytesView with fn to_repr(self) {
Repr::opaque_("BytesView", Repr::array(self.to_array().map(x => Repr(x))))
}
///|
pub impl Debug for Unit with fn to_repr(_) {
Repr::unit()
}
///|
pub impl[T : Debug] Debug for Array[T] with fn to_repr(self) {
Repr::array(self.map(x => Repr(x)))
}
///|
pub impl[T : Debug] Debug for ArrayView[T] with fn to_repr(self) {
Repr::opaque_("ArrayView", Repr::array(self.map(x => Repr(x))))
}
///|
pub impl[T : Debug] Debug for FixedArray[T] with fn to_repr(self) {
// `FixedArray` can be viewed as `ArrayView` via slicing.
let view : ArrayView[T] = self
Repr::opaque_("FixedArray", Repr::array(view.map(x => Repr(x))))
}
///|
pub impl[T : Debug] Debug for ReadOnlyArray[T] with fn to_repr(self) {
// `ReadOnlyArray` can be viewed as `ArrayView` via slicing.
let view : ArrayView[T] = self
Repr::opaque_("ReadOnlyArray", Repr::array(view.map(x => Repr(x))))
}
///|
pub impl[T : Debug] Debug for T? with fn to_repr(self) {
match self {
None => Repr::ctor("None", [])
Some(x) => Repr::ctor("Some", [(None, Repr(x))])
}
}
///|
pub impl[T : Debug, E : Debug] Debug for Result[T, E] with fn to_repr(self) {
match self {
Ok(x) => Repr::ctor("Ok", [(None, Repr(x))])
Err(e) => Repr::ctor("Err", [(None, Repr(e))])
}
}
///|
pub impl[A] Debug for Iter[A] with fn to_repr(_) {
Repr::opaque_("Iter", Omitted)
}
///|
pub impl[A, B] Debug for Iter2[A, B] with fn to_repr(_) {
Repr::opaque_("Iter2", Omitted)
}
///|
pub impl[T : Debug] Debug for MutArrayView[T] with fn to_repr(self) {
Repr::opaque_("MutArrayView", Repr::array(self[:].map(x => Repr(x))))
}
///|
pub impl Debug for StringBuilder with fn to_repr(self) {
Repr::string(self.to_string())
}
///|
pub impl[K : Debug, V : Debug] Debug for Map[K, V] with fn to_repr(self) {
Repr::map([ for k, v in self => (Repr(k), Repr(v)) ])
}
///|
pub impl Debug for Json with fn to_repr(self) {
match self {
Null => Repr::ctor("Null", [])
True => Repr::ctor("True", [])
False => Repr::ctor("False", [])
Number(number, repr~) =>
match repr {
None => Repr::ctor("Number", [(None, Repr(number))])
Some(_) =>
Repr::ctor("Number", [
(None, Repr(number)),
(Some("repr"), Repr(repr)),
])
}
String(string) => Repr::ctor("String", [(None, Repr(string))])
Array(array) => Repr::ctor("Array", [(None, Repr(array))])
Object(object) => Repr::ctor("Object", [(None, Repr(object))])
}
}
///|
pub impl Debug for SourceLoc with fn to_repr(self) {
Repr::opaque_("SourceLoc", Repr::string(self.to_string()))
}
///|
/// Checks that the structural representation (`Repr`) of an object matches the
/// expected content. Used in test blocks to ensure API results are as expected,
/// and stores a pretty-printed string for comparison.
///
/// Parameters:
/// - `obj`: The object to inspect. Must implement the `Debug` trait.
/// - `content`: The expected string representation of the object. Defaults to an empty string if not provided.
/// - `loc`: Source code location information for error reporting. Automatically provided by the compiler.
/// - `args_loc`: Location information for function arguments in the source code. Automatically provided by the compiler.
///
/// Raises an `InspectError` if the actual `Repr` does not match the expected content.
///
/// Example:
///
/// ```mbt check
/// test {
/// @debug.debug_inspect(42, content="42")
/// @debug.debug_inspect('c', content="'c'")
/// @debug.debug_inspect(
/// "hello",
/// content=(
/// #|"hello"
/// ),
/// )
/// @debug.debug_inspect(
/// ([1, 2, 3, 4], "string", Some(3.14)),
/// content=(
/// #|([1, 2, 3, 4], "string", Some(3.14))
/// ),
/// )
/// }
/// ```
#callsite(autofill(args_loc, loc))
#alias(inspect, deprecated="use `debug_inspect` without package name instead")
pub fn debug_inspect(
obj : &Debug,
content? : String,
loc~ : SourceLoc,
args_loc~ : ArgsLoc,
) -> Unit raise InspectError {
let loc = loc.to_json_string()
let args_loc = args_loc.to_json()
let actual = render(obj.to_repr())
let want = match content {
None => ""
Some(x) => x
}
if actual != want {
raise InspectError(
(
$|@EXPECT_FAILED {"loc": \{loc}, "args_loc": \{args_loc}, "expect": \{want.escape()}, "actual": \{actual.escape()} }
),
)
}
}
///|
/// Assert two values are equal for debugging/tests.
///
/// This is the preferred test assertion for new code, especially for types
/// that implement `Debug` but not `Show`.
#callsite(autofill(loc))
pub fn[T : Eq + Debug] assert_eq(
a : T,
b : T,
msg? : String,
loc~ : SourceLoc,
) -> Unit raise {
if a != b {
let fail_msg = match msg {
Some(msg) => msg
None => {
let repr_a = Repr(a)
let repr_b = Repr(b)
let diff = pretty_print_delta(diff_repr(repr_a, repr_b), use_ansi=false)
(
$|`\{render(repr_a)} != \{render(repr_b)}`
$|diff:
$|\{diff}
)
}
}
fail(fail_msg, loc~)
}
}
///|
test "core builtin Debug implementations" {
debug_inspect((1 : Int16), content="1")
debug_inspect((1 : UInt16), content="1")
debug_inspect((1 : UInt64), content="1")
debug_inspect((1.0 : Float), content="1")
let b : Bytes = b"ab"
debug_inspect(b, content="")
let bv : BytesView = b
debug_inspect(bv, content="")
let sv : StringView = "abc"
debug_inspect(
sv,
content=(
#|
),
)
let av : ArrayView[Int] = [1, 2, 3][1:3]
debug_inspect(av, content="")
let fa : FixedArray[Int] = [1, 2, 3]
debug_inspect(fa, content="")
let ro : ReadOnlyArray[Int] = [1, 2, 3]
debug_inspect(ro, content="")
let mv : MutArrayView[Int] = [1, 2, 3].mut_view(start=1, end=3)
debug_inspect(mv, content="")
debug_inspect((Ok(1) : Result[Int, String]), content="Ok(1)")
debug_inspect((Err("e") : Result[Int, String]), content="Err(\"e\")")
}
///|
/// Prints and returns the value of a given expression for quick and dirty debugging.
/// This could also be useful to print some logs to trace the progress.
/// For example, you can put `dump(())` in each line, the execution will print the line number
/// when it reaches that line.
#callsite(autofill(loc))
#deprecated("This function is for debugging only and should not be used in production", skip_current_package=true)
pub fn[T : Debug] dump(t : T, name? : String, loc~ : SourceLoc) -> T {
let name = match name {
Some(name) => name
None => ""
}
println("dump(\{name}@\{loc}) = \{to_string(t)}")
t
}
///|
test "dump" {
let x = 42
if false {
dump(()) // never reached here
assert_eq(dump(x) + x, 84)
}
}
///|
pub impl Debug for InspectError with fn to_repr(self) {
match self {
InspectError(msg) => Repr::ctor("InspectError", [(None, Repr::string(msg))])
}
}
///|
pub impl Debug for Failure with fn to_repr(self) {
match self {
Failure(msg) => Repr::ctor("Failure", [(None, Repr::string(msg))])
}
}
///|
pub impl Debug for SnapshotError with fn to_repr(self) {
match self {
SnapshotError(msg) =>
Repr::ctor("SnapshotError", [(None, Repr::string(msg))])
}
}
///|
#warnings("-deprecated")
pub impl Debug for BenchError with fn to_repr(self) {
match self {
BenchError(msg) => Repr::ctor("BenchError", [(None, Repr::string(msg))])
}
}
///|
pub impl Debug for ArgsLoc with fn to_repr(self) {
let ArgsLoc(arr) = self
Repr::opaque_("ArgsLoc", Repr(arr))
}
///|
pub impl Debug for Hasher with fn to_repr(_) {
Repr::opaque_("Hasher", Repr::omitted())
}
///|
pub impl[T] Debug for UninitializedArray[T] with fn to_repr(_) {
Repr::opaque_("UninitializedArray", Repr::omitted())
}
///|
test "Debug for builtin errors" {
debug_inspect(
InspectError::InspectError("inspect msg"),
content="InspectError(\"inspect msg\")",
)
debug_inspect(
Failure::Failure("failure msg"),
content="Failure(\"failure msg\")",
)
debug_inspect(
SnapshotError::SnapshotError("snapshot msg"),
content="SnapshotError(\"snapshot msg\")",
)
}