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}