typst_library/foundations/
cast.rs#[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, Fold, NativeElement, Packed, Repr, Str, Type, Value,
};
pub trait Reflect {
fn input() -> CastInfo;
fn output() -> CastInfo;
fn castable(value: &Value) -> bool;
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<T: Reflect> Reflect for Spanned<T> {
fn input() -> CastInfo {
T::input()
}
fn output() -> CastInfo {
T::output()
}
fn castable(value: &Value) -> bool {
T::castable(value)
}
}
impl<T: NativeElement + Reflect> Reflect for Packed<T> {
fn input() -> CastInfo {
T::input()
}
fn output() -> CastInfo {
T::output()
}
fn castable(value: &Value) -> bool {
T::castable(value)
}
}
impl<T: Reflect> Reflect for StrResult<T> {
fn input() -> CastInfo {
T::input()
}
fn output() -> CastInfo {
T::output()
}
fn castable(value: &Value) -> bool {
T::castable(value)
}
}
impl<T: Reflect> Reflect for HintedStrResult<T> {
fn input() -> CastInfo {
T::input()
}
fn output() -> CastInfo {
T::output()
}
fn castable(value: &Value) -> bool {
T::castable(value)
}
}
impl<T: Reflect> Reflect for SourceResult<T> {
fn input() -> CastInfo {
T::input()
}
fn output() -> CastInfo {
T::output()
}
fn castable(value: &Value) -> bool {
T::castable(value)
}
}
impl<T: Reflect> Reflect for &T {
fn input() -> CastInfo {
T::input()
}
fn output() -> CastInfo {
T::output()
}
fn castable(value: &Value) -> bool {
T::castable(value)
}
}
impl<T: Reflect> Reflect for &mut T {
fn input() -> CastInfo {
T::input()
}
fn output() -> CastInfo {
T::output()
}
fn castable(value: &Value) -> bool {
T::castable(value)
}
}
pub trait IntoValue {
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<T: IntoValue + Clone> IntoValue for Cow<'_, T> {
fn into_value(self) -> Value {
self.into_owned().into_value()
}
}
impl<T: NativeElement + IntoValue> IntoValue for Packed<T> {
fn into_value(self) -> Value {
Value::Content(self.pack())
}
}
impl<T: IntoValue> IntoValue for Spanned<T> {
fn into_value(self) -> Value {
self.v.into_value()
}
}
pub trait IntoResult {
fn into_result(self, span: Span) -> SourceResult<Value>;
}
impl<T: IntoValue> IntoResult for T {
fn into_result(self, _: Span) -> SourceResult<Value> {
Ok(self.into_value())
}
}
impl<T: IntoValue> IntoResult for StrResult<T> {
fn into_result(self, span: Span) -> SourceResult<Value> {
self.map(IntoValue::into_value).at(span)
}
}
impl<T: IntoValue> IntoResult for HintedStrResult<T> {
fn into_result(self, span: Span) -> SourceResult<Value> {
self.map(IntoValue::into_value).at(span)
}
}
impl<T: IntoValue> IntoResult for SourceResult<T> {
fn into_result(self, _: Span) -> SourceResult<Value> {
self.map(IntoValue::into_value)
}
}
impl<T: IntoValue> IntoValue for fn() -> T {
fn into_value(self) -> Value {
self().into_value()
}
}
pub trait FromValue<V = Value>: Sized + Reflect {
fn from_value(value: V) -> HintedStrResult<Self>;
}
impl FromValue for Value {
fn from_value(value: Value) -> HintedStrResult<Self> {
Ok(value)
}
}
impl<T: NativeElement + FromValue> FromValue for Packed<T> {
fn from_value(mut value: Value) -> HintedStrResult<Self> {
if let Value::Content(content) = value {
match content.into_packed::<T>() {
Ok(packed) => return Ok(packed),
Err(content) => value = Value::Content(content),
}
}
let val = T::from_value(value)?;
Ok(Packed::new(val))
}
}
impl<T: FromValue> FromValue<Spanned<Value>> for T {
fn from_value(value: Spanned<Value>) -> HintedStrResult<Self> {
T::from_value(value.v)
}
}
impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> {
fn from_value(value: Spanned<Value>) -> HintedStrResult<Self> {
let span = value.span;
T::from_value(value.v).map(|t| Spanned::new(t, span))
}
}
#[derive(Debug, Clone, PartialEq, Hash, PartialOrd)]
pub enum CastInfo {
Any,
Value(Value, &'static str),
Type(Type),
Union(Vec<Self>),
}
impl CastInfo {
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
}
pub fn walk<F>(&self, mut f: F)
where
F: FnMut(&Self),
{
fn inner<F>(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],
})
}
}
pub trait Container {
type Inner;
}
impl<T> Container for Option<T> {
type Inner = T;
}
impl<T> Container for Vec<T> {
type Inner = T;
}
impl<T, const N: usize> Container for SmallVec<[T; N]> {
type Inner = T;
}
#[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<Self> {
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",
}),
"normal" => MathClass::Normal,
"punctuation" => MathClass::Punctuation,
"opening" => MathClass::Opening,
"closing" => MathClass::Closing,
"fence" => MathClass::Fence,
"large" => MathClass::Large,
"relation" => MathClass::Relation,
"unary" => MathClass::Unary,
"binary" => MathClass::Binary,
"vary" => MathClass::Vary,
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Derived<S, D> {
pub source: S,
pub derived: D,
}
impl<S, D> Derived<S, D> {
pub fn new(source: S, derived: D) -> Self {
Self { source, derived }
}
}
impl<S: Reflect, D> Reflect for Derived<S, D> {
fn input() -> CastInfo {
S::input()
}
fn output() -> CastInfo {
S::output()
}
fn castable(value: &Value) -> bool {
S::castable(value)
}
fn error(found: &Value) -> HintedString {
S::error(found)
}
}
impl<S: IntoValue, D> IntoValue for Derived<S, D> {
fn into_value(self) -> Value {
self.source.into_value()
}
}
impl<S: Fold, D: Fold> Fold for Derived<S, D> {
fn fold(self, outer: Self) -> Self {
Self {
source: self.source.fold(outer.source),
derived: self.derived.fold(outer.derived),
}
}
}