Skip to main content

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