///|
priv type Object
///|
fn[T] Object::of(value : T) -> Object = "%identity"
///|
fn[T] Object::to(self : Object) -> T = "%identity"
///|
priv trait ToAny {
id() -> Unit = _
get_id(() -> Self) -> TypeId = _
}
///|
impl ToAny with id() -> Unit {
}
///|
impl ToAny with 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(
"@tonyfettes/any.type_name: unexpected any.to_string() format: \{string}",
)
}
name.to_string()
}
///|
struct Any(TypeInfo, Object)
///|
/// Returns the complete type information for the value stored in this `Any`.
///
/// The `TypeInfo` contains both the runtime type id and the human-readable
/// type name. This is useful when you need both pieces of information or want
/// to pass the complete type metadata around.
pub fn Any::type_info(self : Any) -> TypeInfo {
self.0
}
///|
/// Returns the runtime type identifier for the value stored in this `Any`.
///
/// The `TypeId` is used internally to perform type checks during extraction.
/// Two `Any` values will have physically equal type ids if and only if they
/// contain values of the same type.
pub fn Any::type_id(self : Any) -> TypeId {
self.0.0
}
///|
/// Returns the human-readable name of the type stored in this `Any`.
///
/// This is particularly useful for debugging, logging, and error messages.
/// The name is captured when the value is first wrapped with `Any::of`.
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`.
///
/// Returns the `TypeId` component which is used for physical equality checks
/// during type matching operations.
pub fn TypeInfo::id(self : TypeInfo) -> TypeId {
self.0
}
///|
/// Extracts the human-readable type name from this `TypeInfo`.
///
/// Returns the string representation of the type, useful for debugging,
/// logging, and generating user-friendly error messages.
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.
///
/// This records the runtime type id and stores the value in a type-erased box.
/// If the provided value is already an `Any`, the original `Any` is returned
/// (idempotent double-wrap elimination).
///
/// The first time a type id is encountered its human-readable name is cached.
#as_free_fn
pub fn[T] Any::of(value : T) -> Any {
let r : Ref[T] = Ref::new(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.
///
/// Only use when you are certain of the actual stored type. Prefer `to()` or
/// `try_to()` for safety. Incorrect use leads to undefined behavior.
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.
/// Carries both expected and actual runtime type ids so they can be converted
/// to human-readable names for diagnostics.
pub suberror TypeMismatch {
TypeMismatch(expect~ : TypeInfo, actual~ : TypeInfo)
}
///|
pub impl Show for TypeMismatch with 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 impl ToJson for TypeMismatch with to_json(self : TypeMismatch) -> Json {
let TypeMismatch(expect~, actual~) = self
{ "TypeMismatch": { "expect": expect.name(), "actual": actual.name() } }
}
///|
/// Extract the stored value as type `T` performing a runtime type id check.
///
/// Raises `TypeMismatch` if the ids differ. When `T` itself is `Any`, this will
/// unwrap one level (special-case id) to avoid double wrapping.
///
/// On first mismatch for a type, its name is lazily registered for error
/// output.
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::new(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` if the
/// runtime type id does not match. This is the non-raising variant of `to()`.
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::new(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` to the stored value if its runtime type matches `T`.
///
/// When it matches, the result of `f` is rewrapped with `Any::of`; otherwise
/// the original `Any` is returned unchanged. Enables chaining to build
/// exhaustive type-dispatch pipelines. Propagates any error (`raise?`) from
/// `f`.
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
}
}