typst_library/foundations/
ty.rs

1#[doc(inline)]
2pub use typst_macros::{scope, ty};
3
4use std::cmp::Ordering;
5use std::fmt::{self, Debug, Display, Formatter};
6use std::sync::LazyLock;
7
8use ecow::{eco_format, EcoString};
9use typst_utils::Static;
10
11use crate::diag::{bail, DeprecationSink, StrResult};
12use crate::foundations::{
13    cast, func, AutoValue, Func, NativeFuncData, NoneValue, Repr, Scope, Value,
14};
15
16/// Describes a kind of value.
17///
18/// To style your document, you need to work with values of different kinds:
19/// Lengths specifying the size of your elements, colors for your text and
20/// shapes, and more. Typst categorizes these into clearly defined _types_ and
21/// tells you where it expects which type of value.
22///
23/// Apart from basic types for numeric values and [typical]($int)
24/// [types]($float) [known]($str) [from]($array) [programming]($dictionary)
25/// languages, Typst provides a special type for [_content._]($content) A value
26/// of this type can hold anything that you can enter into your document: Text,
27/// elements like headings and shapes, and style information.
28///
29/// # Example
30/// ```example
31/// #let x = 10
32/// #if type(x) == int [
33///   #x is an integer!
34/// ] else [
35///   #x is another value...
36/// ]
37///
38/// An image is of type
39/// #type(image("glacier.jpg")).
40/// ```
41///
42/// The type of `10` is `int`. Now, what is the type of `int` or even `type`?
43/// ```example
44/// #type(int) \
45/// #type(type)
46/// ```
47///
48/// # Compatibility
49/// In Typst 0.7 and lower, the `type` function returned a string instead of a
50/// type. Compatibility with the old way will remain until Typst 0.14 to give
51/// package authors time to upgrade.
52///
53/// - Checks like `{int == "integer"}` evaluate to `{true}`
54/// - Adding/joining a type and string will yield a string
55/// - The `{in}` operator on a type and a dictionary will evaluate to `{true}`
56///   if the dictionary has a string key matching the type's name
57#[ty(scope, cast)]
58#[derive(Copy, Clone, Eq, PartialEq, Hash)]
59pub struct Type(Static<NativeTypeData>);
60
61impl Type {
62    /// Get the type for `T`.
63    pub fn of<T: NativeType>() -> Self {
64        T::ty()
65    }
66
67    /// The type's short name, how it is used in code (e.g. `str`).
68    pub fn short_name(&self) -> &'static str {
69        self.0.name
70    }
71
72    /// The type's long name, for use in diagnostics (e.g. `string`).
73    pub fn long_name(&self) -> &'static str {
74        self.0.long_name
75    }
76
77    /// The type's title case name, for use in documentation (e.g. `String`).
78    pub fn title(&self) -> &'static str {
79        self.0.title
80    }
81
82    /// Documentation for the type (as Markdown).
83    pub fn docs(&self) -> &'static str {
84        self.0.docs
85    }
86
87    /// Search keywords for the type.
88    pub fn keywords(&self) -> &'static [&'static str] {
89        self.0.keywords
90    }
91
92    /// This type's constructor function.
93    pub fn constructor(&self) -> StrResult<Func> {
94        self.0
95            .constructor
96            .as_ref()
97            .map(|lazy| Func::from(*lazy))
98            .ok_or_else(|| eco_format!("type {self} does not have a constructor"))
99    }
100
101    /// The type's associated scope that holds sub-definitions.
102    pub fn scope(&self) -> &'static Scope {
103        &(self.0).0.scope
104    }
105
106    /// Get a field from this type's scope, if possible.
107    pub fn field(
108        &self,
109        field: &str,
110        sink: impl DeprecationSink,
111    ) -> StrResult<&'static Value> {
112        match self.scope().get(field) {
113            Some(binding) => Ok(binding.read_checked(sink)),
114            None => bail!("type {self} does not contain field `{field}`"),
115        }
116    }
117}
118
119// Type compatibility.
120impl Type {
121    /// The type's backward-compatible name.
122    pub fn compat_name(&self) -> &str {
123        self.long_name()
124    }
125}
126
127#[scope]
128impl Type {
129    /// Determines a value's type.
130    ///
131    /// ```example
132    /// #type(12) \
133    /// #type(14.7) \
134    /// #type("hello") \
135    /// #type(<glacier>) \
136    /// #type([Hi]) \
137    /// #type(x => x + 1) \
138    /// #type(type)
139    /// ```
140    #[func(constructor)]
141    pub fn construct(
142        /// The value whose type's to determine.
143        value: Value,
144    ) -> Type {
145        value.ty()
146    }
147}
148
149impl Debug for Type {
150    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
151        write!(f, "Type({})", self.long_name())
152    }
153}
154
155impl Repr for Type {
156    fn repr(&self) -> EcoString {
157        if *self == Type::of::<AutoValue>() {
158            "type(auto)"
159        } else if *self == Type::of::<NoneValue>() {
160            "type(none)"
161        } else {
162            self.short_name()
163        }
164        .into()
165    }
166}
167
168impl Display for Type {
169    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
170        f.pad(self.long_name())
171    }
172}
173
174impl Ord for Type {
175    fn cmp(&self, other: &Self) -> Ordering {
176        self.long_name().cmp(other.long_name())
177    }
178}
179
180impl PartialOrd for Type {
181    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
182        Some(self.cmp(other))
183    }
184}
185
186/// A Typst type that is defined by a native Rust type.
187pub trait NativeType {
188    /// The type's name.
189    ///
190    /// In contrast to `data()`, this is usable in const contexts.
191    const NAME: &'static str;
192
193    /// Get the type for the native Rust type.
194    fn ty() -> Type {
195        Type::from(Self::data())
196    }
197
198    // Get the type data for the native Rust type.
199    fn data() -> &'static NativeTypeData;
200}
201
202/// Defines a native type.
203#[derive(Debug)]
204pub struct NativeTypeData {
205    /// The type's normal name (e.g. `str`), as exposed to Typst.
206    pub name: &'static str,
207    /// The type's long name (e.g. `string`), for error messages.
208    pub long_name: &'static str,
209    /// The function's title case name (e.g. `String`).
210    pub title: &'static str,
211    /// The documentation for this type as a string.
212    pub docs: &'static str,
213    /// A list of alternate search terms for this type.
214    pub keywords: &'static [&'static str],
215    /// The constructor for this type.
216    pub constructor: LazyLock<Option<&'static NativeFuncData>>,
217    /// Definitions in the scope of the type.
218    pub scope: LazyLock<Scope>,
219}
220
221impl From<&'static NativeTypeData> for Type {
222    fn from(data: &'static NativeTypeData) -> Self {
223        Self(Static(data))
224    }
225}
226
227cast! {
228    &'static NativeTypeData,
229    self => Type::from(self).into_value(),
230}