summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/foundations/ty.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/foundations/ty.rs')
-rw-r--r--crates/typst-library/src/foundations/ty.rs223
1 files changed, 223 insertions, 0 deletions
diff --git a/crates/typst-library/src/foundations/ty.rs b/crates/typst-library/src/foundations/ty.rs
new file mode 100644
index 00000000..70845dd2
--- /dev/null
+++ b/crates/typst-library/src/foundations/ty.rs
@@ -0,0 +1,223 @@
+#[doc(inline)]
+pub use typst_macros::{scope, ty};
+
+use std::cmp::Ordering;
+use std::fmt::{self, Debug, Display, Formatter};
+
+use ecow::{eco_format, EcoString};
+use once_cell::sync::Lazy;
+use typst_utils::Static;
+
+use crate::diag::StrResult;
+use crate::foundations::{
+ cast, func, AutoValue, Func, NativeFuncData, NoneValue, Repr, Scope, Value,
+};
+
+/// Describes a kind of value.
+///
+/// To style your document, you need to work with values of different kinds:
+/// Lengths specifying the size of your elements, colors for your text and
+/// shapes, and more. Typst categorizes these into clearly defined _types_ and
+/// tells you where it expects which type of value.
+///
+/// Apart from basic types for numeric values and [typical]($int)
+/// [types]($float) [known]($str) [from]($array) [programming]($dictionary)
+/// languages, Typst provides a special type for [_content._]($content) A value
+/// of this type can hold anything that you can enter into your document: Text,
+/// elements like headings and shapes, and style information.
+///
+/// # Example
+/// ```example
+/// #let x = 10
+/// #if type(x) == int [
+/// #x is an integer!
+/// ] else [
+/// #x is another value...
+/// ]
+///
+/// An image is of type
+/// #type(image("glacier.jpg")).
+/// ```
+///
+/// The type of `10` is `int`. Now, what is the type of `int` or even `type`?
+/// ```example
+/// #type(int) \
+/// #type(type)
+/// ```
+///
+/// # Compatibility
+/// In Typst 0.7 and lower, the `type` function returned a string instead of a
+/// type. Compatibility with the old way will remain for a while to give package
+/// authors time to upgrade, but it will be removed at some point.
+///
+/// - Checks like `{int == "integer"}` evaluate to `{true}`
+/// - Adding/joining a type and string will yield a string
+/// - The `{in}` operator on a type and a dictionary will evaluate to `{true}`
+/// if the dictionary has a string key matching the type's name
+#[ty(scope, cast)]
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub struct Type(Static<NativeTypeData>);
+
+impl Type {
+ /// Get the type for `T`.
+ pub fn of<T: NativeType>() -> Self {
+ T::ty()
+ }
+
+ /// The type's short name, how it is used in code (e.g. `str`).
+ pub fn short_name(&self) -> &'static str {
+ self.0.name
+ }
+
+ /// The type's long name, for use in diagnostics (e.g. `string`).
+ pub fn long_name(&self) -> &'static str {
+ self.0.long_name
+ }
+
+ /// The type's title case name, for use in documentation (e.g. `String`).
+ pub fn title(&self) -> &'static str {
+ self.0.title
+ }
+
+ /// Documentation for the type (as Markdown).
+ pub fn docs(&self) -> &'static str {
+ self.0.docs
+ }
+
+ /// Search keywords for the type.
+ pub fn keywords(&self) -> &'static [&'static str] {
+ self.0.keywords
+ }
+
+ /// This type's constructor function.
+ pub fn constructor(&self) -> StrResult<Func> {
+ self.0
+ .constructor
+ .as_ref()
+ .map(|lazy| Func::from(*lazy))
+ .ok_or_else(|| eco_format!("type {self} does not have a constructor"))
+ }
+
+ /// The type's associated scope that holds sub-definitions.
+ pub fn scope(&self) -> &'static Scope {
+ &(self.0).0.scope
+ }
+
+ /// Get a field from this type's scope, if possible.
+ pub fn field(&self, field: &str) -> StrResult<&'static Value> {
+ self.scope()
+ .get(field)
+ .ok_or_else(|| eco_format!("type {self} does not contain field `{field}`"))
+ }
+}
+
+// Type compatibility.
+impl Type {
+ /// The type's backward-compatible name.
+ pub fn compat_name(&self) -> &str {
+ self.long_name()
+ }
+}
+
+#[scope]
+impl Type {
+ /// Determines a value's type.
+ ///
+ /// ```example
+ /// #type(12) \
+ /// #type(14.7) \
+ /// #type("hello") \
+ /// #type(<glacier>) \
+ /// #type([Hi]) \
+ /// #type(x => x + 1) \
+ /// #type(type)
+ /// ```
+ #[func(constructor)]
+ pub fn construct(
+ /// The value whose type's to determine.
+ value: Value,
+ ) -> Type {
+ value.ty()
+ }
+}
+
+impl Debug for Type {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Type({})", self.long_name())
+ }
+}
+
+impl Repr for Type {
+ fn repr(&self) -> EcoString {
+ if *self == Type::of::<AutoValue>() {
+ "type(auto)"
+ } else if *self == Type::of::<NoneValue>() {
+ "type(none)"
+ } else {
+ self.long_name()
+ }
+ .into()
+ }
+}
+
+impl Display for Type {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.long_name())
+ }
+}
+
+impl Ord for Type {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.long_name().cmp(other.long_name())
+ }
+}
+
+impl PartialOrd for Type {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+/// A Typst type that is defined by a native Rust type.
+pub trait NativeType {
+ /// The type's name.
+ ///
+ /// In contrast to `data()`, this is usable in const contexts.
+ const NAME: &'static str;
+
+ /// Get the type for the native Rust type.
+ fn ty() -> Type {
+ Type::from(Self::data())
+ }
+
+ // Get the type data for the native Rust type.
+ fn data() -> &'static NativeTypeData;
+}
+
+/// Defines a native type.
+#[derive(Debug)]
+pub struct NativeTypeData {
+ /// The type's normal name (e.g. `str`), as exposed to Typst.
+ pub name: &'static str,
+ pub long_name: &'static str,
+ /// The function's title case name (e.g. `String`).
+ pub title: &'static str,
+ /// The documentation for this type as a string.
+ pub docs: &'static str,
+ /// A list of alternate search terms for this type.
+ pub keywords: &'static [&'static str],
+ /// The constructor for this type.
+ pub constructor: Lazy<Option<&'static NativeFuncData>>,
+ pub scope: Lazy<Scope>,
+}
+
+impl From<&'static NativeTypeData> for Type {
+ fn from(data: &'static NativeTypeData) -> Self {
+ Self(Static(data))
+ }
+}
+
+cast! {
+ &'static NativeTypeData,
+ self => Type::from(self).into_value(),
+}