///| Performs a checked cast from a `Value` to a `Self?` type.
///
/// This trait abstracts a function that ensures the cast is valid and returns `null` if the cast fails.
/// It is useful for safely handling dynamic types in MoonBit.
pub(open) trait Cast {
into(Value) -> Self?
from(Self) -> Value
}
// NOTE: Do we need a default implementation for the `from` method in `Cast` trait?
//
// If the type `Self` can be cast from a `Value`, then it must can be cast back to a `Value` safely,
// only when the `Self` type is a abstract type that represents a JavaScript type.
//
// ```
// impl Cast with from(self) { Value::cast_from(self) }
// ```
//
// But if some one implements the `Cast` trait for a type that not an abstract type,
// then the default implementation is not type-safe.
///|
extern "js" fn checked_cast_string_ffi(value : Value) -> Nullable[String] =
#| (x) => typeof x === 'string' ? x : null
///|
extern "js" fn checked_cast_int_ffi(value : Value) -> Nullable[Int] =
#| (x) => typeof x === 'number' && Number.isInteger(x) ? x : null
///|
extern "js" fn checked_cast_double_ffi(value : Value) -> Nullable[Double] =
#| (x) => typeof x === 'number' ? x : null
///|
extern "js" fn checked_cast_bool_ffi(value : Value) -> Nullable[Bool] =
#| (x) => typeof x === 'boolean' ? x : null
///|
extern "js" fn checked_cast_array_ffi(value : Value) -> Nullable[Array[Value]] =
#| (x) => Array.isArray(x) ? x : null
///|
pub impl Cast for String with into(value) {
checked_cast_string_ffi(value).to_option()
}
///|
pub impl Cast for String with from(value) {
Value::cast_from(value)
}
///|
pub impl Cast for Int with into(value) {
checked_cast_int_ffi(value).to_option()
}
///|
pub impl Cast for Int with from(value) {
Value::cast_from(value)
}
///|
pub impl Cast for Double with into(value) {
checked_cast_double_ffi(value).to_option()
}
///|
pub impl Cast for Double with from(value) {
Value::cast_from(value)
}
///|
pub impl Cast for Bool with into(value) {
checked_cast_bool_ffi(value).to_option()
}
///|
pub impl Cast for Bool with from(value) {
Value::cast_from(value)
}
///|
pub impl[A : Cast] Cast for Array[A] with into(value) {
checked_cast_array_ffi(value)
.to_option()
.bind(fn(arr) {
let is_type_a = fn(elem) { not((Cast::into(elem) : A?).is_empty()) }
if arr.iter().all(is_type_a) {
Some(Value::cast_from(arr).cast())
} else {
None
}
})
}
///|
pub impl[A : Cast] Cast for Array[A] with from(value) {
Value::cast_from(value)
}