pub use typst_macros::{cast, Cast}; use std::fmt::Write; use std::ops::Add; use ecow::EcoString; use super::Value; use crate::diag::{At, SourceResult, StrResult}; use crate::syntax::{Span, Spanned}; use crate::util::separated_list; /// Determine details of a type. /// /// Type casting works as follows: /// - [`Reflect for T`](Reflect) describes the possible Typst values for `T` /// (for documentation and autocomplete). /// - [`IntoValue for T`](IntoValue) is for conversion from `T -> Value` /// (infallible) /// - [`FromValue for T`](FromValue) is for conversion from `Value -> T` /// (fallible). /// /// We can't use `TryFrom` due to conflicting impls. We could use /// `From for Value`, but that inverses the impl and leads to tons of /// `.into()` all over the place that become hard to decipher. pub trait Reflect { /// Describe the acceptable values for this type. fn describe() -> CastInfo; /// Whether the given value can be converted to `T`. /// /// This exists for performance. The check could also be done through the /// [`CastInfo`], but it would be much more expensive (heap allocation + /// dynamic checks instead of optimized machine code for each type). fn castable(value: &Value) -> bool; /// Produce an error message for an inacceptable value. /// /// ``` /// # use typst::eval::{Int, Reflect, Value}; /// assert_eq!( /// ::error(Value::None), /// "expected integer, found none", /// ); /// ``` fn error(found: &Value) -> EcoString { Self::describe().error(found) } } impl Reflect for Value { fn describe() -> CastInfo { CastInfo::Any } fn castable(_: &Value) -> bool { true } } impl Reflect for Spanned { fn describe() -> CastInfo { T::describe() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for StrResult { fn describe() -> CastInfo { T::describe() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for SourceResult { fn describe() -> CastInfo { T::describe() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for &T { fn describe() -> CastInfo { T::describe() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for &mut T { fn describe() -> CastInfo { T::describe() } fn castable(value: &Value) -> bool { T::castable(value) } } /// Cast a Rust type into a Typst [`Value`]. /// /// See also: [`Reflect`]. pub trait IntoValue { /// Cast this type into a value. fn into_value(self) -> Value; } impl IntoValue for Value { fn into_value(self) -> Value { self } } impl IntoValue for Spanned { fn into_value(self) -> Value { self.v.into_value() } } /// Cast a Rust type or result into a [`SourceResult`]. /// /// Converts `T`, [`StrResult`], or [`SourceResult`] into /// [`SourceResult`] by `Ok`-wrapping or adding span information. pub trait IntoResult { /// Cast this type into a value. fn into_result(self, span: Span) -> SourceResult; } impl IntoResult for T { fn into_result(self, _: Span) -> SourceResult { Ok(self.into_value()) } } impl IntoResult for StrResult { fn into_result(self, span: Span) -> SourceResult { self.map(IntoValue::into_value).at(span) } } impl IntoResult for SourceResult { fn into_result(self, _: Span) -> SourceResult { self.map(IntoValue::into_value) } } /// Try to cast a Typst [`Value`] into a Rust type. /// /// See also: [`Reflect`]. pub trait FromValue: Sized + Reflect { /// Try to cast the value into an instance of `Self`. fn from_value(value: V) -> StrResult; } impl FromValue for Value { fn from_value(value: Value) -> StrResult { Ok(value) } } impl FromValue> for T { fn from_value(value: Spanned) -> StrResult { T::from_value(value.v) } } impl FromValue> for Spanned { fn from_value(value: Spanned) -> StrResult { let span = value.span; T::from_value(value.v).map(|t| Spanned::new(t, span)) } } /// Describes a possible value for a cast. #[derive(Debug, Clone, Hash, PartialEq, PartialOrd)] pub enum CastInfo { /// Any value is okay. Any, /// A specific value, plus short documentation for that value. Value(Value, &'static str), /// Any value of a type. Type(&'static str), /// Multiple alternatives. Union(Vec), } impl CastInfo { /// Produce an error message describing what was expected and what was /// found. pub fn error(&self, found: &Value) -> EcoString { fn accumulate( info: &CastInfo, found: &Value, parts: &mut Vec, matching_type: &mut bool, ) { match info { CastInfo::Any => parts.push("anything".into()), CastInfo::Value(value, _) => { parts.push(value.repr().into()); if value.type_name() == found.type_name() { *matching_type = true; } } CastInfo::Type(ty) => parts.push((*ty).into()), CastInfo::Union(options) => { for option in options { accumulate(option, found, parts, matching_type); } } } } let mut matching_type = false; let mut parts = vec![]; accumulate(self, found, &mut parts, &mut matching_type); let mut msg = String::from("expected "); if parts.is_empty() { msg.push_str(" nothing"); } msg.push_str(&separated_list(&parts, "or")); if !matching_type { msg.push_str(", found "); msg.push_str(found.type_name()); } if_chain::if_chain! { if let Value::Int(i) = found; if parts.iter().any(|p| p == "length"); if !matching_type; then { write!(msg, ": a length needs a unit - did you mean {i}pt?").unwrap(); } }; msg.into() } } impl Add for CastInfo { type Output = Self; fn add(self, rhs: Self) -> Self { Self::Union(match (self, rhs) { (Self::Union(mut lhs), Self::Union(rhs)) => { for cast in rhs { if !lhs.contains(&cast) { lhs.push(cast); } } lhs } (Self::Union(mut lhs), rhs) => { if !lhs.contains(&rhs) { lhs.push(rhs); } lhs } (lhs, Self::Union(mut rhs)) => { if !rhs.contains(&lhs) { rhs.insert(0, lhs); } rhs } (lhs, rhs) => vec![lhs, rhs], }) } } /// A container for a variadic argument. pub trait Variadics { /// The contained type. type Inner; } impl Variadics for Vec { type Inner = T; } /// An uninhabitable type. pub enum Never {} impl Reflect for Never { fn describe() -> CastInfo { CastInfo::Union(vec![]) } fn castable(_: &Value) -> bool { false } } impl IntoValue for Never { fn into_value(self) -> Value { match self {} } } impl FromValue for Never { fn from_value(value: Value) -> StrResult { Err(Self::error(&value)) } }