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        let mut span = Span::detached();
266        if let Value::Content(content) = value {
267            match content.into_packed::<T>() {
268                Ok(packed) => return Ok(packed),
269                Err(content) => {
270                    span = content.span();
271                    value = Value::Content(content)
272                }
273            }
274        }
275        let val = T::from_value(value)?;
276        Ok(Packed::new(val).spanned(span))
277    }
278}
279
280impl<T: FromValue> FromValue<Spanned<Value>> for T {
281    fn from_value(value: Spanned<Value>) -> HintedStrResult<Self> {
282        T::from_value(value.v)
283    }
284}
285
286impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> {
287    fn from_value(value: Spanned<Value>) -> HintedStrResult<Self> {
288        let span = value.span;
289        T::from_value(value.v).map(|t| Spanned::new(t, span))
290    }
291}
292
293/// Describes a possible value for a cast.
294#[derive(Debug, Clone, PartialEq, PartialOrd, Hash)]
295pub enum CastInfo {
296    /// Any value is okay.
297    Any,
298    /// A specific value, plus short documentation for that value.
299    Value(Value, &'static str),
300    /// Any value of a type.
301    Type(Type),
302    /// Multiple alternatives.
303    Union(Vec<Self>),
304}
305
306impl CastInfo {
307    /// Produce an error message describing what was expected and what was
308    /// found.
309    pub fn error(&self, found: &Value) -> HintedString {
310        let mut matching_type = false;
311        let mut parts = vec![];
312
313        self.walk(|info| match info {
314            CastInfo::Any => parts.push("anything".into()),
315            CastInfo::Value(value, _) => {
316                parts.push(value.repr());
317                if value.ty() == found.ty() {
318                    matching_type = true;
319                }
320            }
321            CastInfo::Type(ty) => parts.push(eco_format!("{ty}")),
322            CastInfo::Union(_) => {}
323        });
324
325        let mut msg = String::from("expected ");
326        if parts.is_empty() {
327            msg.push_str(" nothing");
328        }
329
330        msg.push_str(&repr::separated_list(&parts, "or"));
331
332        if !matching_type {
333            msg.push_str(", found ");
334            write!(msg, "{}", found.ty()).unwrap();
335        }
336
337        let mut msg: HintedString = msg.into();
338
339        if let Value::Int(i) = found {
340            if !matching_type && parts.iter().any(|p| p == "length") {
341                msg.hint(eco_format!("a length needs a unit - did you mean {i}pt?"));
342            }
343        } else if let Value::Str(s) = found {
344            if !matching_type && parts.iter().any(|p| p == "label") {
345                if typst_syntax::is_valid_label_literal_id(s) {
346                    msg.hint(eco_format!(
347                        "use `<{s}>` or `label({})` to create a label",
348                        s.repr()
349                    ));
350                } else {
351                    msg.hint(eco_format!("use `label({})` to create a label", s.repr()));
352                }
353            }
354        } else if let Value::Decimal(_) = found
355            && !matching_type
356            && parts.iter().any(|p| p == "float")
357        {
358            msg.hint(eco_format!(
359                "if loss of precision is acceptable, explicitly cast the \
360                     decimal to a float with `float(value)`"
361            ));
362        }
363
364        msg
365    }
366
367    /// Walk all contained non-union infos.
368    pub fn walk<F>(&self, mut f: F)
369    where
370        F: FnMut(&Self),
371    {
372        fn inner<F>(info: &CastInfo, f: &mut F)
373        where
374            F: FnMut(&CastInfo),
375        {
376            if let CastInfo::Union(infos) = info {
377                for child in infos {
378                    inner(child, f);
379                }
380            } else {
381                f(info);
382            }
383        }
384
385        inner(self, &mut f)
386    }
387}
388
389impl Add for CastInfo {
390    type Output = Self;
391
392    fn add(self, rhs: Self) -> Self {
393        Self::Union(match (self, rhs) {
394            (Self::Union(mut lhs), Self::Union(rhs)) => {
395                for cast in rhs {
396                    if !lhs.contains(&cast) {
397                        lhs.push(cast);
398                    }
399                }
400                lhs
401            }
402            (Self::Union(mut lhs), rhs) => {
403                if !lhs.contains(&rhs) {
404                    lhs.push(rhs);
405                }
406                lhs
407            }
408            (lhs, Self::Union(mut rhs)) => {
409                if !rhs.contains(&lhs) {
410                    rhs.insert(0, lhs);
411                }
412                rhs
413            }
414            (lhs, rhs) => vec![lhs, rhs],
415        })
416    }
417}
418
419/// A container for an argument.
420pub trait Container {
421    /// The contained type.
422    type Inner;
423}
424
425impl<T> Container for Option<T> {
426    type Inner = T;
427}
428
429impl<T> Container for Vec<T> {
430    type Inner = T;
431}
432
433impl<T, const N: usize> Container for SmallVec<[T; N]> {
434    type Inner = T;
435}
436
437/// An uninhabitable type.
438#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
439pub enum Never {}
440
441impl Reflect for Never {
442    fn input() -> CastInfo {
443        CastInfo::Union(vec![])
444    }
445
446    fn output() -> CastInfo {
447        CastInfo::Union(vec![])
448    }
449
450    fn castable(_: &Value) -> bool {
451        false
452    }
453}
454
455impl IntoValue for Never {
456    fn into_value(self) -> Value {
457        match self {}
458    }
459}
460
461impl FromValue for Never {
462    fn from_value(value: Value) -> HintedStrResult<Self> {
463        Err(Self::error(&value))
464    }
465}
466
467cast! {
468    SyntaxMode,
469    self => IntoValue::into_value(match self {
470        SyntaxMode::Markup => "markup",
471        SyntaxMode::Math => "math",
472        SyntaxMode::Code => "code",
473    }),
474    /// Evaluate as markup, as in a Typst file.
475    "markup" => SyntaxMode::Markup,
476    /// Evaluate as math, as in an equation.
477    "math" => SyntaxMode::Math,
478    /// Evaluate as code, as after a hash.
479    "code" => SyntaxMode::Code,
480}
481
482cast! {
483    MathClass,
484    self => IntoValue::into_value(match self {
485        MathClass::Normal => "normal",
486        MathClass::Alphabetic => "alphabetic",
487        MathClass::Binary => "binary",
488        MathClass::Closing => "closing",
489        MathClass::Diacritic => "diacritic",
490        MathClass::Fence => "fence",
491        MathClass::GlyphPart => "glyph-part",
492        MathClass::Large => "large",
493        MathClass::Opening => "opening",
494        MathClass::Punctuation => "punctuation",
495        MathClass::Relation => "relation",
496        MathClass::Space => "space",
497        MathClass::Unary => "unary",
498        MathClass::Vary => "vary",
499        MathClass::Special => "special",
500    }),
501    /// The default class for non-special things.
502    "normal" => MathClass::Normal,
503    /// Punctuation, e.g. a comma.
504    "punctuation" => MathClass::Punctuation,
505    /// An opening delimiter, e.g. `(`.
506    "opening" => MathClass::Opening,
507    /// A closing delimiter, e.g. `)`.
508    "closing" => MathClass::Closing,
509    /// A delimiter that is the same on both sides, e.g. `|`.
510    "fence" => MathClass::Fence,
511    /// A large operator like `sum`.
512    "large" => MathClass::Large,
513    /// A relation like `=` or `prec`.
514    "relation" => MathClass::Relation,
515    /// A unary operator like `not`.
516    "unary" => MathClass::Unary,
517    /// A binary operator like `times`.
518    "binary" => MathClass::Binary,
519    /// An operator that can be both unary or binary like `+`.
520    "vary" => MathClass::Vary,
521}
522
523/// A type that contains a user-visible source portion and something that is
524/// derived from it, but not user-visible.
525///
526/// An example usage would be `source` being a `DataSource` and `derived` a
527/// TextMate theme parsed from it. With `Derived`, we can store both parts in
528/// the `RawElem::theme` field and get automatic nice `Reflect` and `IntoValue`
529/// impls.
530#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
531pub struct Derived<S, D> {
532    /// The source portion.
533    pub source: S,
534    /// The derived portion.
535    pub derived: D,
536}
537
538impl<S, D> Derived<S, D> {
539    /// Create a new instance from the `source` and the `derived` data.
540    pub fn new(source: S, derived: D) -> Self {
541        Self { source, derived }
542    }
543}
544
545impl<S: Reflect, D> Reflect for Derived<S, D> {
546    fn input() -> CastInfo {
547        S::input()
548    }
549
550    fn output() -> CastInfo {
551        S::output()
552    }
553
554    fn castable(value: &Value) -> bool {
555        S::castable(value)
556    }
557
558    fn error(found: &Value) -> HintedString {
559        S::error(found)
560    }
561}
562
563impl<S: IntoValue, D> IntoValue for Derived<S, D> {
564    fn into_value(self) -> Value {
565        self.source.into_value()
566    }
567}
568
569impl<S: Fold, D: Fold> Fold for Derived<S, D> {
570    fn fold(self, outer: Self) -> Self {
571        Self {
572            source: self.source.fold(outer.source),
573            derived: self.derived.fold(outer.derived),
574        }
575    }
576}