typst_library/foundations/
cast.rs

1#[rustfmt::skip]
2#[doc(inline)]
3pub use typst_macros::{cast, Cast};
4
5use std::borrow::Cow;
6use std::fmt::Write;
7use std::hash::Hash;
8use std::ops::Add;
9
10use ecow::eco_format;
11use smallvec::SmallVec;
12use typst_syntax::{Span, Spanned, SyntaxMode};
13use unicode_math_class::MathClass;
14
15use crate::diag::{At, HintedStrResult, HintedString, SourceResult, StrResult};
16use crate::foundations::{
17    Fold, NativeElement, Packed, Repr, Str, Type, Value, array, repr,
18};
19
20/// Determine details of a type.
21///
22/// Type casting works as follows:
23/// - [`Reflect for T`](Reflect) describes the possible Typst values for `T`
24///   (for documentation and autocomplete).
25/// - [`IntoValue for T`](IntoValue) is for conversion from `T -> Value`
26///   (infallible)
27/// - [`FromValue for T`](FromValue) is for conversion from `Value -> T`
28///   (fallible).
29///
30/// We can't use `TryFrom<Value>` due to conflicting impls. We could use
31/// `From<T> for Value`, but that inverses the impl and leads to tons of
32/// `.into()` all over the place that become hard to decipher.
33pub trait Reflect {
34    /// Describe what can be cast into this value.
35    fn input() -> CastInfo;
36
37    /// Describe what this value can be cast into.
38    fn output() -> CastInfo;
39
40    /// Whether the given value can be converted to `T`.
41    ///
42    /// This exists for performance. The check could also be done through the
43    /// [`CastInfo`], but it would be much more expensive (heap allocation +
44    /// dynamic checks instead of optimized machine code for each type).
45    fn castable(value: &Value) -> bool;
46
47    /// Produce an error message for an unacceptable value type.
48    ///
49    /// ```ignore
50    /// assert_eq!(
51    ///   <i64 as Reflect>::error(&Value::None),
52    ///   "expected integer, found none",
53    /// );
54    /// ```
55    fn error(found: &Value) -> HintedString {
56        Self::input().error(found)
57    }
58}
59
60impl Reflect for Value {
61    fn input() -> CastInfo {
62        CastInfo::Any
63    }
64
65    fn output() -> CastInfo {
66        CastInfo::Any
67    }
68
69    fn castable(_: &Value) -> bool {
70        true
71    }
72}
73
74impl<T: Reflect> Reflect for Spanned<T> {
75    fn input() -> CastInfo {
76        T::input()
77    }
78
79    fn output() -> CastInfo {
80        T::output()
81    }
82
83    fn castable(value: &Value) -> bool {
84        T::castable(value)
85    }
86}
87
88impl<T: NativeElement + Reflect> Reflect for Packed<T> {
89    fn input() -> CastInfo {
90        T::input()
91    }
92
93    fn output() -> CastInfo {
94        T::output()
95    }
96
97    fn castable(value: &Value) -> bool {
98        T::castable(value)
99    }
100}
101
102impl<T: Reflect> Reflect for StrResult<T> {
103    fn input() -> CastInfo {
104        T::input()
105    }
106
107    fn output() -> CastInfo {
108        T::output()
109    }
110
111    fn castable(value: &Value) -> bool {
112        T::castable(value)
113    }
114}
115
116impl<T: Reflect> Reflect for HintedStrResult<T> {
117    fn input() -> CastInfo {
118        T::input()
119    }
120
121    fn output() -> CastInfo {
122        T::output()
123    }
124
125    fn castable(value: &Value) -> bool {
126        T::castable(value)
127    }
128}
129
130impl<T: Reflect> Reflect for SourceResult<T> {
131    fn input() -> CastInfo {
132        T::input()
133    }
134
135    fn output() -> CastInfo {
136        T::output()
137    }
138
139    fn castable(value: &Value) -> bool {
140        T::castable(value)
141    }
142}
143
144impl<T: Reflect> Reflect for &T {
145    fn input() -> CastInfo {
146        T::input()
147    }
148
149    fn output() -> CastInfo {
150        T::output()
151    }
152
153    fn castable(value: &Value) -> bool {
154        T::castable(value)
155    }
156}
157
158impl<T: Reflect> Reflect for &mut T {
159    fn input() -> CastInfo {
160        T::input()
161    }
162
163    fn output() -> CastInfo {
164        T::output()
165    }
166
167    fn castable(value: &Value) -> bool {
168        T::castable(value)
169    }
170}
171
172/// Cast a Rust type into a Typst [`Value`].
173///
174/// See also: [`Reflect`].
175pub trait IntoValue {
176    /// Cast this type into a value.
177    fn into_value(self) -> Value;
178}
179
180impl IntoValue for Value {
181    fn into_value(self) -> Value {
182        self
183    }
184}
185
186impl IntoValue for (&Str, &Value) {
187    fn into_value(self) -> Value {
188        Value::Array(array![self.0.clone(), self.1.clone()])
189    }
190}
191
192impl<T: IntoValue + Clone> IntoValue for Cow<'_, T> {
193    fn into_value(self) -> Value {
194        self.into_owned().into_value()
195    }
196}
197
198impl<T: NativeElement + IntoValue> IntoValue for Packed<T> {
199    fn into_value(self) -> Value {
200        Value::Content(self.pack())
201    }
202}
203
204impl<T: IntoValue> IntoValue for Spanned<T> {
205    fn into_value(self) -> Value {
206        self.v.into_value()
207    }
208}
209
210/// Cast a Rust type or result into a [`SourceResult<Value>`].
211///
212/// Converts `T`, [`StrResult<T>`], or [`SourceResult<T>`] into
213/// [`SourceResult<Value>`] by `Ok`-wrapping or adding span information.
214pub trait IntoResult {
215    /// Cast this type into a value.
216    fn into_result(self, span: Span) -> SourceResult<Value>;
217}
218
219impl<T: IntoValue> IntoResult for T {
220    fn into_result(self, _: Span) -> SourceResult<Value> {
221        Ok(self.into_value())
222    }
223}
224
225impl<T: IntoValue> IntoResult for StrResult<T> {
226    fn into_result(self, span: Span) -> SourceResult<Value> {
227        self.map(IntoValue::into_value).at(span)
228    }
229}
230
231impl<T: IntoValue> IntoResult for HintedStrResult<T> {
232    fn into_result(self, span: Span) -> SourceResult<Value> {
233        self.map(IntoValue::into_value).at(span)
234    }
235}
236
237impl<T: IntoValue> IntoResult for SourceResult<T> {
238    fn into_result(self, _: Span) -> SourceResult<Value> {
239        self.map(IntoValue::into_value)
240    }
241}
242
243impl<T: IntoValue> IntoValue for fn() -> T {
244    fn into_value(self) -> Value {
245        self().into_value()
246    }
247}
248
249/// Try to cast a Typst [`Value`] into a Rust type.
250///
251/// See also: [`Reflect`].
252pub trait FromValue<V = Value>: Sized + Reflect {
253    /// Try to cast the value into an instance of `Self`.
254    fn from_value(value: V) -> HintedStrResult<Self>;
255}
256
257impl FromValue for Value {
258    fn from_value(value: Value) -> HintedStrResult<Self> {
259        Ok(value)
260    }
261}
262
263impl<T: NativeElement + FromValue> FromValue for Packed<T> {
264    fn from_value(mut value: Value) -> HintedStrResult<Self> {
265        if let Value::Content(content) = value {
266            match content.into_packed::<T>() {
267                Ok(packed) => return Ok(packed),
268                Err(content) => value = Value::Content(content),
269            }
270        }
271        let val = T::from_value(value)?;
272        Ok(Packed::new(val))
273    }
274}
275
276impl<T: FromValue> FromValue<Spanned<Value>> for T {
277    fn from_value(value: Spanned<Value>) -> HintedStrResult<Self> {
278        T::from_value(value.v)
279    }
280}
281
282impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> {
283    fn from_value(value: Spanned<Value>) -> HintedStrResult<Self> {
284        let span = value.span;
285        T::from_value(value.v).map(|t| Spanned::new(t, span))
286    }
287}
288
289/// Describes a possible value for a cast.
290#[derive(Debug, Clone, PartialEq, PartialOrd, Hash)]
291pub enum CastInfo {
292    /// Any value is okay.
293    Any,
294    /// A specific value, plus short documentation for that value.
295    Value(Value, &'static str),
296    /// Any value of a type.
297    Type(Type),
298    /// Multiple alternatives.
299    Union(Vec<Self>),
300}
301
302impl CastInfo {
303    /// Produce an error message describing what was expected and what was
304    /// found.
305    pub fn error(&self, found: &Value) -> HintedString {
306        let mut matching_type = false;
307        let mut parts = vec![];
308
309        self.walk(|info| match info {
310            CastInfo::Any => parts.push("anything".into()),
311            CastInfo::Value(value, _) => {
312                parts.push(value.repr());
313                if value.ty() == found.ty() {
314                    matching_type = true;
315                }
316            }
317            CastInfo::Type(ty) => parts.push(eco_format!("{ty}")),
318            CastInfo::Union(_) => {}
319        });
320
321        let mut msg = String::from("expected ");
322        if parts.is_empty() {
323            msg.push_str(" nothing");
324        }
325
326        msg.push_str(&repr::separated_list(&parts, "or"));
327
328        if !matching_type {
329            msg.push_str(", found ");
330            write!(msg, "{}", found.ty()).unwrap();
331        }
332
333        let mut msg: HintedString = msg.into();
334
335        if let Value::Int(i) = found {
336            if !matching_type && parts.iter().any(|p| p == "length") {
337                msg.hint(eco_format!("a length needs a unit - did you mean {i}pt?"));
338            }
339        } else if let Value::Str(s) = found {
340            if !matching_type && parts.iter().any(|p| p == "label") {
341                if typst_syntax::is_valid_label_literal_id(s) {
342                    msg.hint(eco_format!(
343                        "use `<{s}>` or `label({})` to create a label",
344                        s.repr()
345                    ));
346                } else {
347                    msg.hint(eco_format!("use `label({})` to create a label", s.repr()));
348                }
349            }
350        } else if let Value::Decimal(_) = found
351            && !matching_type
352            && parts.iter().any(|p| p == "float")
353        {
354            msg.hint(eco_format!(
355                "if loss of precision is acceptable, explicitly cast the \
356                     decimal to a float with `float(value)`"
357            ));
358        }
359
360        msg
361    }
362
363    /// Walk all contained non-union infos.
364    pub fn walk<F>(&self, mut f: F)
365    where
366        F: FnMut(&Self),
367    {
368        fn inner<F>(info: &CastInfo, f: &mut F)
369        where
370            F: FnMut(&CastInfo),
371        {
372            if let CastInfo::Union(infos) = info {
373                for child in infos {
374                    inner(child, f);
375                }
376            } else {
377                f(info);
378            }
379        }
380
381        inner(self, &mut f)
382    }
383}
384
385impl Add for CastInfo {
386    type Output = Self;
387
388    fn add(self, rhs: Self) -> Self {
389        Self::Union(match (self, rhs) {
390            (Self::Union(mut lhs), Self::Union(rhs)) => {
391                for cast in rhs {
392                    if !lhs.contains(&cast) {
393                        lhs.push(cast);
394                    }
395                }
396                lhs
397            }
398            (Self::Union(mut lhs), rhs) => {
399                if !lhs.contains(&rhs) {
400                    lhs.push(rhs);
401                }
402                lhs
403            }
404            (lhs, Self::Union(mut rhs)) => {
405                if !rhs.contains(&lhs) {
406                    rhs.insert(0, lhs);
407                }
408                rhs
409            }
410            (lhs, rhs) => vec![lhs, rhs],
411        })
412    }
413}
414
415/// A container for an argument.
416pub trait Container {
417    /// The contained type.
418    type Inner;
419}
420
421impl<T> Container for Option<T> {
422    type Inner = T;
423}
424
425impl<T> Container for Vec<T> {
426    type Inner = T;
427}
428
429impl<T, const N: usize> Container for SmallVec<[T; N]> {
430    type Inner = T;
431}
432
433/// An uninhabitable type.
434#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
435pub enum Never {}
436
437impl Reflect for Never {
438    fn input() -> CastInfo {
439        CastInfo::Union(vec![])
440    }
441
442    fn output() -> CastInfo {
443        CastInfo::Union(vec![])
444    }
445
446    fn castable(_: &Value) -> bool {
447        false
448    }
449}
450
451impl IntoValue for Never {
452    fn into_value(self) -> Value {
453        match self {}
454    }
455}
456
457impl FromValue for Never {
458    fn from_value(value: Value) -> HintedStrResult<Self> {
459        Err(Self::error(&value))
460    }
461}
462
463cast! {
464    SyntaxMode,
465    self => IntoValue::into_value(match self {
466        SyntaxMode::Markup => "markup",
467        SyntaxMode::Math => "math",
468        SyntaxMode::Code => "code",
469    }),
470    /// Evaluate as markup, as in a Typst file.
471    "markup" => SyntaxMode::Markup,
472    /// Evaluate as math, as in an equation.
473    "math" => SyntaxMode::Math,
474    /// Evaluate as code, as after a hash.
475    "code" => SyntaxMode::Code,
476}
477
478cast! {
479    MathClass,
480    self => IntoValue::into_value(match self {
481        MathClass::Normal => "normal",
482        MathClass::Alphabetic => "alphabetic",
483        MathClass::Binary => "binary",
484        MathClass::Closing => "closing",
485        MathClass::Diacritic => "diacritic",
486        MathClass::Fence => "fence",
487        MathClass::GlyphPart => "glyph-part",
488        MathClass::Large => "large",
489        MathClass::Opening => "opening",
490        MathClass::Punctuation => "punctuation",
491        MathClass::Relation => "relation",
492        MathClass::Space => "space",
493        MathClass::Unary => "unary",
494        MathClass::Vary => "vary",
495        MathClass::Special => "special",
496    }),
497    /// The default class for non-special things.
498    "normal" => MathClass::Normal,
499    /// Punctuation, e.g. a comma.
500    "punctuation" => MathClass::Punctuation,
501    /// An opening delimiter, e.g. `(`.
502    "opening" => MathClass::Opening,
503    /// A closing delimiter, e.g. `)`.
504    "closing" => MathClass::Closing,
505    /// A delimiter that is the same on both sides, e.g. `|`.
506    "fence" => MathClass::Fence,
507    /// A large operator like `sum`.
508    "large" => MathClass::Large,
509    /// A relation like `=` or `prec`.
510    "relation" => MathClass::Relation,
511    /// A unary operator like `not`.
512    "unary" => MathClass::Unary,
513    /// A binary operator like `times`.
514    "binary" => MathClass::Binary,
515    /// An operator that can be both unary or binary like `+`.
516    "vary" => MathClass::Vary,
517}
518
519/// A type that contains a user-visible source portion and something that is
520/// derived from it, but not user-visible.
521///
522/// An example usage would be `source` being a `DataSource` and `derived` a
523/// TextMate theme parsed from it. With `Derived`, we can store both parts in
524/// the `RawElem::theme` field and get automatic nice `Reflect` and `IntoValue`
525/// impls.
526#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
527pub struct Derived<S, D> {
528    /// The source portion.
529    pub source: S,
530    /// The derived portion.
531    pub derived: D,
532}
533
534impl<S, D> Derived<S, D> {
535    /// Create a new instance from the `source` and the `derived` data.
536    pub fn new(source: S, derived: D) -> Self {
537        Self { source, derived }
538    }
539}
540
541impl<S: Reflect, D> Reflect for Derived<S, D> {
542    fn input() -> CastInfo {
543        S::input()
544    }
545
546    fn output() -> CastInfo {
547        S::output()
548    }
549
550    fn castable(value: &Value) -> bool {
551        S::castable(value)
552    }
553
554    fn error(found: &Value) -> HintedString {
555        S::error(found)
556    }
557}
558
559impl<S: IntoValue, D> IntoValue for Derived<S, D> {
560    fn into_value(self) -> Value {
561        self.source.into_value()
562    }
563}
564
565impl<S: Fold, D: Fold> Fold for Derived<S, D> {
566    fn fold(self, outer: Self) -> Self {
567        Self {
568            source: self.source.fold(outer.source),
569            derived: self.derived.fold(outer.derived),
570        }
571    }
572}