#[rustfmt::skip] #[doc(inline)] pub use typst_macros::{cast, Cast}; use std::borrow::Cow; use std::fmt::Write; use std::hash::Hash; use std::ops::Add; use ecow::eco_format; use smallvec::SmallVec; use typst_syntax::{Span, Spanned}; use unicode_math_class::MathClass; use crate::diag::{At, HintedStrResult, HintedString, SourceResult, StrResult}; use crate::foundations::{array, repr, NativeElement, Packed, Repr, Str, Type, Value}; /// 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 what can be cast into this value. fn input() -> CastInfo; /// Describe what this value can be cast into. fn output() -> 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 unacceptable value type. /// /// ```ignore /// assert_eq!( /// ::error(&Value::None), /// "expected integer, found none", /// ); /// ``` fn error(found: &Value) -> HintedString { Self::input().error(found) } } impl Reflect for Value { fn input() -> CastInfo { CastInfo::Any } fn output() -> CastInfo { CastInfo::Any } fn castable(_: &Value) -> bool { true } } impl Reflect for Spanned { fn input() -> CastInfo { T::input() } fn output() -> CastInfo { T::output() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for Packed { fn input() -> CastInfo { T::input() } fn output() -> CastInfo { T::output() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for StrResult { fn input() -> CastInfo { T::input() } fn output() -> CastInfo { T::output() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for HintedStrResult { fn input() -> CastInfo { T::input() } fn output() -> CastInfo { T::output() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for SourceResult { fn input() -> CastInfo { T::input() } fn output() -> CastInfo { T::output() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for &T { fn input() -> CastInfo { T::input() } fn output() -> CastInfo { T::output() } fn castable(value: &Value) -> bool { T::castable(value) } } impl Reflect for &mut T { fn input() -> CastInfo { T::input() } fn output() -> CastInfo { T::output() } 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 (&Str, &Value) { fn into_value(self) -> Value { Value::Array(array![self.0.clone(), self.1.clone()]) } } impl IntoValue for Cow<'_, T> { fn into_value(self) -> Value { self.into_owned().into_value() } } impl IntoValue for Packed { fn into_value(self) -> Value { Value::Content(self.pack()) } } 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 HintedStrResult { 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) } } impl IntoValue for fn() -> T { fn into_value(self) -> Value { self().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) -> HintedStrResult; } impl FromValue for Value { fn from_value(value: Value) -> HintedStrResult { Ok(value) } } impl FromValue for Packed { fn from_value(mut value: Value) -> HintedStrResult { if let Value::Content(content) = value { match content.into_packed::() { Ok(packed) => return Ok(packed), Err(content) => value = Value::Content(content), } } let val = T::from_value(value)?; Ok(Packed::new(val)) } } impl FromValue> for T { fn from_value(value: Spanned) -> HintedStrResult { T::from_value(value.v) } } impl FromValue> for Spanned { fn from_value(value: Spanned) -> HintedStrResult { 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, PartialEq, Hash, 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(Type), /// Multiple alternatives. Union(Vec), } impl CastInfo { /// Produce an error message describing what was expected and what was /// found. pub fn error(&self, found: &Value) -> HintedString { let mut matching_type = false; let mut parts = vec![]; self.walk(|info| match info { CastInfo::Any => parts.push("anything".into()), CastInfo::Value(value, _) => { parts.push(value.repr()); if value.ty() == found.ty() { matching_type = true; } } CastInfo::Type(ty) => parts.push(eco_format!("{ty}")), CastInfo::Union(_) => {} }); let mut msg = String::from("expected "); if parts.is_empty() { msg.push_str(" nothing"); } msg.push_str(&repr::separated_list(&parts, "or")); if !matching_type { msg.push_str(", found "); write!(msg, "{}", found.ty()).unwrap(); } let mut msg: HintedString = msg.into(); if let Value::Int(i) = found { if !matching_type && parts.iter().any(|p| p == "length") { msg.hint(eco_format!("a length needs a unit - did you mean {i}pt?")); } } else if let Value::Str(s) = found { if !matching_type && parts.iter().any(|p| p == "label") { if typst_syntax::is_valid_label_literal_id(s) { msg.hint(eco_format!( "use `<{s}>` or `label({})` to create a label", s.repr() )); } else { msg.hint(eco_format!("use `label({})` to create a label", s.repr())); } } } else if let Value::Decimal(_) = found { if !matching_type && parts.iter().any(|p| p == "float") { msg.hint(eco_format!( "if loss of precision is acceptable, explicitly cast the \ decimal to a float with `float(value)`" )); } } msg } /// Walk all contained non-union infos. pub fn walk(&self, mut f: F) where F: FnMut(&Self), { fn inner(info: &CastInfo, f: &mut F) where F: FnMut(&CastInfo), { if let CastInfo::Union(infos) = info { for child in infos { inner(child, f); } } else { f(info); } } inner(self, &mut f) } } 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 an argument. pub trait Container { /// The contained type. type Inner; } impl Container for Option { type Inner = T; } impl Container for Vec { type Inner = T; } impl Container for SmallVec<[T; N]> { type Inner = T; } /// An uninhabitable type. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum Never {} impl Reflect for Never { fn input() -> CastInfo { CastInfo::Union(vec![]) } fn output() -> 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) -> HintedStrResult { Err(Self::error(&value)) } } cast! { MathClass, self => IntoValue::into_value(match self { MathClass::Normal => "normal", MathClass::Alphabetic => "alphabetic", MathClass::Binary => "binary", MathClass::Closing => "closing", MathClass::Diacritic => "diacritic", MathClass::Fence => "fence", MathClass::GlyphPart => "glyph-part", MathClass::Large => "large", MathClass::Opening => "opening", MathClass::Punctuation => "punctuation", MathClass::Relation => "relation", MathClass::Space => "space", MathClass::Unary => "unary", MathClass::Vary => "vary", MathClass::Special => "special", }), /// The default class for non-special things. "normal" => MathClass::Normal, /// Punctuation, e.g. a comma. "punctuation" => MathClass::Punctuation, /// An opening delimiter, e.g. `(`. "opening" => MathClass::Opening, /// A closing delimiter, e.g. `)`. "closing" => MathClass::Closing, /// A delimiter that is the same on both sides, e.g. `|`. "fence" => MathClass::Fence, /// A large operator like `sum`. "large" => MathClass::Large, /// A relation like `=` or `prec`. "relation" => MathClass::Relation, /// A unary operator like `not`. "unary" => MathClass::Unary, /// A binary operator like `times`. "binary" => MathClass::Binary, /// An operator that can be both unary or binary like `+`. "vary" => MathClass::Vary, }