// Derived from tonyfettes/any 0.1.5, licensed under Apache-2.0.
// Changes: migrated to current MoonBit syntax and explicit trait extensions.
///|
priv type Object
///|
fn[T] Object::of(value : T) -> Object = "%identity"
///|
fn[T] Object::to(self : Object) -> T = "%identity"
///|
priv trait ToAny {
fn id() -> Unit = _
fn get_id(() -> Self) -> TypeId = _
}
///|
impl ToAny with fn id() -> Unit {
}
///|
impl ToAny with fn get_id(_ : () -> Self) -> TypeId {
Self::id
}
///|
impl[T] ToAny for Ref[T]
///|
fn[T] any_to_string(value : T) -> String = "%any.to_string"
///|
fn[T] type_name(value : () -> T) -> String {
let string = any_to_string(value)
guard string is [.. " ", .. name, '>'] else {
abort("any.type_name: unexpected any.to_string() format: \{string}")
}
name.to_owned()
}
///|
struct Any(TypeInfo, Object)
///|
/// Returns the complete type information for the value stored in this `Any`.
pub fn Any::type_info(self : Any) -> TypeInfo {
self.0
}
///|
/// Returns the runtime type identifier for the value stored in this `Any`.
pub fn Any::type_id(self : Any) -> TypeId {
self.0.0
}
///|
/// Returns the human-readable name of the type stored in this `Any`.
pub fn Any::type_name(self : Any) -> String {
self.0.1
}
///|
#warnings("-unused_constructor")
struct TypeId(() -> Unit)
///|
struct TypeInfo(TypeId, String)
///|
/// Extracts the runtime type identifier from this `TypeInfo`.
pub fn TypeInfo::id(self : TypeInfo) -> TypeId {
self.0
}
///|
/// Extracts the human-readable type name from this `TypeInfo`.
pub fn TypeInfo::name(self : TypeInfo) -> String {
self.1
}
///|
let any_id : TypeId = ToAny::get_id((panic : () -> Ref[Any]))
///|
/// Wrap a value of type `T` in an `Any` container.
#as_free_fn
pub fn[T] Any::of(value : T) -> Any {
let r : Ref[T] = Ref(value)
let id : TypeId = ToAny::get_id((panic : () -> Ref[T]))
if physical_equal(id, any_id) {
let r : Ref[Any] = Object::of(r).to()
return r.val
}
let name : String = type_name((panic : () -> T))
let info : TypeInfo = TypeInfo(id, name)
Any(info, Object::of(r))
}
///|
/// Unsafely coerce the underlying value to type `T` without checking.
pub fn[T] Any::unsafe_coerce(self : Any) -> T {
let r : Ref[T] = self.1.to()
r.val
}
///|
/// Error raised when attempting to extract a value as an incompatible type.
pub suberror TypeMismatch {
TypeMismatch(expect~ : TypeInfo, actual~ : TypeInfo)
}
///|
pub impl Show for TypeMismatch with fn output(
self : TypeMismatch,
logger : &Logger,
) -> Unit {
let TypeMismatch(expect~, actual~) = self
logger.write_string(
"TypeMismatch: expected type '\{expect.name()}' but found type '\{actual.name()}'",
)
}
///|
pub extend TypeMismatch with Show::{to_string, output}
///|
pub impl ToJson for TypeMismatch with fn to_json(self : TypeMismatch) -> Json {
let TypeMismatch(expect~, actual~) = self
{ "TypeMismatch": { "expect": expect.name(), "actual": actual.name() } }
}
///|
pub extend TypeMismatch with ToJson::{to_json}
///|
/// Extract the stored value as type `T` performing a runtime type id check.
pub fn[T] Any::to(self : Any) -> T raise TypeMismatch {
let id : TypeId = ToAny::get_id((panic : () -> Ref[T]))
if physical_equal(id, any_id) {
let r : Ref[T] = Object::of(Ref(self)).to()
return r.val
}
guard physical_equal(self.type_id(), id) else {
let name = type_name((panic : () -> T))
raise TypeMismatch::TypeMismatch(expect=TypeInfo(id, name), actual=self.0)
}
let r : Ref[T] = self.1.to()
r.val
}
///|
/// Attempt to extract the stored value as type `T`, returning `None` on mismatch.
pub fn[T] Any::try_to(self : Any) -> T? {
let id : TypeId = ToAny::get_id((panic : () -> Ref[T]))
if physical_equal(id, any_id) {
let r : Ref[T] = Object::of(Ref(self)).to()
return Some(r.val)
}
guard physical_equal(self.type_id(), id) else { return None }
let r : Ref[T] = self.1.to()
Some(r.val)
}
///|
/// Conditionally apply `f` if the stored value has type `T`.
pub fn[T, R] Any::map(self : Any, f : (T) -> R raise?) -> Any raise? {
let id : TypeId = ToAny::get_id((panic : () -> Ref[T]))
if physical_equal(self.type_id(), id) {
let r : Ref[T] = self.1.to()
Any::of(f(r.val))
} else {
self
}
}