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}