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::{EcoString, eco_format};
9use typst_utils::Static;
10
11use crate::diag::{DeprecationSink, StrResult, bail};
12use crate::foundations::{
13    AutoValue, Func, NativeFuncData, NoneValue, Repr, Scope, Value, cast, func,
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/// Unlike other types like `int`, [none] and [auto] do not have a name
49/// representing them. To test if a value is one of these, compare your value to
50/// them directly, e.g:
51/// ```example
52/// #let val = none
53/// #if val == none [
54///   Yep, it's none.
55/// ]
56/// ```
57///
58/// Note that `type` will return [`content`] for all document elements. To
59/// programmatically determine which kind of content you are dealing with, see
60/// [`content.func`].
61#[ty(scope, cast)]
62#[derive(Copy, Clone, Eq, PartialEq, Hash)]
63pub struct Type(Static<NativeTypeData>);
64
65impl Type {
66    /// Get the type for `T`.
67    pub fn of<T: NativeType>() -> Self {
68        T::ty()
69    }
70
71    /// The type's short name, how it is used in code (e.g. `str`).
72    pub fn short_name(&self) -> &'static str {
73        self.0.name
74    }
75
76    /// The type's long name, for use in diagnostics (e.g. `string`).
77    pub fn long_name(&self) -> &'static str {
78        self.0.long_name
79    }
80
81    /// The type's title case name, for use in documentation (e.g. `String`).
82    pub fn title(&self) -> &'static str {
83        self.0.title
84    }
85
86    /// Documentation for the type (as Markdown).
87    pub fn docs(&self) -> &'static str {
88        self.0.docs
89    }
90
91    /// Search keywords for the type.
92    pub fn keywords(&self) -> &'static [&'static str] {
93        self.0.keywords
94    }
95
96    /// This type's constructor function.
97    pub fn constructor(&self) -> StrResult<Func> {
98        self.0
99            .constructor
100            .as_ref()
101            .map(|lazy| Func::from(*lazy))
102            .ok_or_else(|| eco_format!("type {self} does not have a constructor"))
103    }
104
105    /// The type's associated scope that holds sub-definitions.
106    pub fn scope(&self) -> &'static Scope {
107        &(self.0).0.scope
108    }
109
110    /// Get a field from this type's scope, if possible.
111    pub fn field(
112        &self,
113        field: &str,
114        sink: impl DeprecationSink,
115    ) -> StrResult<&'static Value> {
116        match self.scope().get(field) {
117            Some(binding) => Ok(binding.read_checked(sink)),
118            None => bail!("type {self} does not contain field `{field}`"),
119        }
120    }
121}
122
123#[scope]
124impl Type {
125    /// Determines a value's type.
126    ///
127    /// ```example
128    /// #type(12) \
129    /// #type(14.7) \
130    /// #type("hello") \
131    /// #type(<glacier>) \
132    /// #type([Hi]) \
133    /// #type(x => x + 1) \
134    /// #type(type)
135    /// ```
136    #[func(constructor)]
137    pub fn construct(
138        /// The value whose type's to determine.
139        value: Value,
140    ) -> Type {
141        value.ty()
142    }
143}
144
145impl Debug for Type {
146    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
147        write!(f, "Type({})", self.long_name())
148    }
149}
150
151impl Repr for Type {
152    fn repr(&self) -> EcoString {
153        if *self == Type::of::<AutoValue>() {
154            "type(auto)"
155        } else if *self == Type::of::<NoneValue>() {
156            "type(none)"
157        } else {
158            self.short_name()
159        }
160        .into()
161    }
162}
163
164impl Display for Type {
165    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
166        f.pad(self.long_name())
167    }
168}
169
170impl Ord for Type {
171    fn cmp(&self, other: &Self) -> Ordering {
172        self.long_name().cmp(other.long_name())
173    }
174}
175
176impl PartialOrd for Type {
177    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
178        Some(self.cmp(other))
179    }
180}
181
182/// A Typst type that is defined by a native Rust type.
183pub trait NativeType {
184    /// The type's name.
185    ///
186    /// In contrast to `data()`, this is usable in const contexts.
187    const NAME: &'static str;
188
189    /// Get the type for the native Rust type.
190    fn ty() -> Type {
191        Type::from(Self::data())
192    }
193
194    // Get the type data for the native Rust type.
195    fn data() -> &'static NativeTypeData;
196}
197
198/// Defines a native type.
199#[derive(Debug)]
200pub struct NativeTypeData {
201    /// The type's normal name (e.g. `str`), as exposed to Typst.
202    pub name: &'static str,
203    /// The type's long name (e.g. `string`), for error messages.
204    pub long_name: &'static str,
205    /// The function's title case name (e.g. `String`).
206    pub title: &'static str,
207    /// The documentation for this type as a string.
208    pub docs: &'static str,
209    /// A list of alternate search terms for this type.
210    pub keywords: &'static [&'static str],
211    /// The constructor for this type.
212    pub constructor: LazyLock<Option<&'static NativeFuncData>>,
213    /// Definitions in the scope of the type.
214    pub scope: LazyLock<Scope>,
215}
216
217impl From<&'static NativeTypeData> for Type {
218    fn from(data: &'static NativeTypeData) -> Self {
219        Self(Static(data))
220    }
221}
222
223cast! {
224    &'static NativeTypeData,
225    self => Type::from(self).into_value(),
226}