1use core::fmt;
2use std::path::Path;
3use std::sync::LazyLock;
4
5use ecow::{EcoString, eco_format};
6use regex::RegexSet;
7use strum::{EnumIter, IntoEnumIterator};
8use typst::foundations::{CastInfo, Regex};
9use typst::layout::Ratio;
10use typst::syntax::FileId;
11use typst::{
12 foundations::{AutoValue, Content, Func, NoneValue, ParamInfo, Type, Value},
13 layout::Length,
14};
15use typst_shim::syntax::RootedPathExt;
16
17use crate::syntax::Decl;
18use crate::ty::*;
19
20#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
22pub enum PathKind {
23 Source {
25 allow_package: bool,
27 },
28 Wasm,
30 Csv,
32 Image,
34 Json,
36 Yaml,
38 Xml,
40 Toml,
42 Csl,
44 Bibliography,
46 RawTheme,
48 RawSyntax,
50 Special,
52 None,
54}
55
56impl PathKind {
57 pub fn ext_matcher(&self) -> &'static RegexSet {
59 type RegSet = LazyLock<RegexSet>;
60
61 fn make_regex(patterns: &[&str]) -> RegexSet {
62 let patterns = patterns.iter().map(|pattern| format!("(?i)^{pattern}$"));
63 RegexSet::new(patterns).unwrap()
64 }
65
66 static SOURCE_REGSET: RegSet = RegSet::new(|| make_regex(&["typ", "typc"]));
67 static WASM_REGSET: RegSet = RegSet::new(|| make_regex(&["wasm"]));
68 static IMAGE_REGSET: RegSet = RegSet::new(|| {
69 make_regex(&[
70 "ico", "bmp", "png", "webp", "jpg", "jpeg", "jfif", "tiff", "gif", "svg", "svgz",
71 "pdf",
72 ])
73 });
74 static JSON_REGSET: RegSet = RegSet::new(|| make_regex(&["json", "jsonc", "json5"]));
75 static YAML_REGSET: RegSet = RegSet::new(|| make_regex(&["yaml", "yml"]));
76 static XML_REGSET: RegSet = RegSet::new(|| make_regex(&["xml"]));
77 static TOML_REGSET: RegSet = RegSet::new(|| make_regex(&["toml"]));
78 static CSV_REGSET: RegSet = RegSet::new(|| make_regex(&["csv"]));
79 static BIB_REGSET: RegSet = RegSet::new(|| make_regex(&["yaml", "yml", "bib"]));
80 static CSL_REGSET: RegSet = RegSet::new(|| make_regex(&["csl"]));
81 static RAW_THEME_REGSET: RegSet = RegSet::new(|| make_regex(&["tmTheme", "xml"]));
82 static RAW_SYNTAX_REGSET: RegSet =
83 RegSet::new(|| make_regex(&["tmLanguage", "sublime-syntax"]));
84
85 static ALL_REGSET: RegSet = RegSet::new(|| RegexSet::new([r".*"]).unwrap());
86 static ALL_SPECIAL_REGSET: RegSet = RegSet::new(|| {
87 RegexSet::new({
88 let patterns = SOURCE_REGSET.patterns();
89 let patterns = patterns.iter().chain(WASM_REGSET.patterns());
90 let patterns = patterns.chain(IMAGE_REGSET.patterns());
91 let patterns = patterns.chain(JSON_REGSET.patterns());
92 let patterns = patterns.chain(YAML_REGSET.patterns());
93 let patterns = patterns.chain(XML_REGSET.patterns());
94 let patterns = patterns.chain(TOML_REGSET.patterns());
95 let patterns = patterns.chain(CSV_REGSET.patterns());
96 let patterns = patterns.chain(BIB_REGSET.patterns());
97 let patterns = patterns.chain(CSL_REGSET.patterns());
98 let patterns = patterns.chain(RAW_THEME_REGSET.patterns());
99 patterns.chain(RAW_SYNTAX_REGSET.patterns())
100 })
101 .unwrap()
102 });
103
104 match self {
105 PathKind::Source { .. } => &SOURCE_REGSET,
106 PathKind::Wasm => &WASM_REGSET,
107 PathKind::Csv => &CSV_REGSET,
108 PathKind::Image => &IMAGE_REGSET,
109 PathKind::Json => &JSON_REGSET,
110 PathKind::Yaml => &YAML_REGSET,
111 PathKind::Xml => &XML_REGSET,
112 PathKind::Toml => &TOML_REGSET,
113 PathKind::Csl => &CSL_REGSET,
114 PathKind::Bibliography => &BIB_REGSET,
115 PathKind::RawTheme => &RAW_THEME_REGSET,
116 PathKind::RawSyntax => &RAW_SYNTAX_REGSET,
117 PathKind::Special => &ALL_SPECIAL_REGSET,
118 PathKind::None => &ALL_REGSET,
119 }
120 }
121
122 pub fn is_match(&self, path: &Path) -> bool {
124 let ext = path.extension().and_then(|ext| ext.to_str());
125 ext.is_some_and(|ext| self.ext_matcher().is_match(ext))
126 }
127
128 pub fn from_ext(path: &str) -> Option<Self> {
130 PathKind::iter().find(|preference| preference.is_match(std::path::Path::new(path)))
131 }
132}
133
134impl Ty {
135 pub fn from_cast_info(ty: &CastInfo) -> Ty {
137 match &ty {
138 CastInfo::Any => Ty::Any,
139 CastInfo::Value(val, doc) => Ty::Value(InsTy::new_doc(val.clone(), *doc)),
140 CastInfo::Type(ty) => Ty::Builtin(BuiltinTy::Type(*ty)),
141 CastInfo::Union(types) => {
142 Ty::iter_union(UnionIter(vec![types.as_slice().iter()]).map(Self::from_cast_info))
143 }
144 }
145 }
146
147 pub fn from_param_site(func: &Func, param: &ParamInfo) -> Ty {
149 use typst::foundations::FuncInner;
150 match func.inner() {
151 FuncInner::Element(..) | FuncInner::Native(..) | FuncInner::Plugin(..) => {
152 if let Some(ty) = param_mapping(func, param) {
153 return ty;
154 }
155 }
156 FuncInner::Closure(_) => {}
157 FuncInner::With(w) => return Ty::from_param_site(&w.0, param),
158 };
159
160 param
161 .to_native()
162 .map(|native| Self::from_cast_info(&native.input))
163 .unwrap_or(Ty::Any)
164 }
165
166 pub(crate) fn from_return_site(func: &Func, ty: &'_ CastInfo) -> Self {
168 use typst::foundations::FuncInner;
169 match func.inner() {
170 FuncInner::Element(elem) => return Ty::Builtin(BuiltinTy::Content(Some(*elem))),
171 FuncInner::Closure(_) | FuncInner::Plugin(_) => {}
172 FuncInner::With(w) => return Ty::from_return_site(&w.0, ty),
173 FuncInner::Native(_) => {}
174 };
175
176 Self::from_cast_info(ty)
177 }
178}
179
180struct UnionIter<'a>(Vec<std::slice::Iter<'a, CastInfo>>);
182
183impl<'a> Iterator for UnionIter<'a> {
184 type Item = &'a CastInfo;
185
186 fn next(&mut self) -> Option<Self::Item> {
187 loop {
188 let iter = self.0.last_mut()?;
189 if let Some(ty) = iter.next() {
190 match ty {
191 CastInfo::Union(types) => {
192 self.0.push(types.as_slice().iter());
193 }
194 _ => return Some(ty),
195 }
196 } else {
197 self.0.pop();
198 }
199 }
200 }
201}
202
203#[derive(Debug, Clone, Copy)]
206pub enum BuiltinSig<'a> {
207 TupleMap(&'a Ty),
209 TupleAt(&'a Ty),
211}
212
213#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
215pub struct PackageId {
216 pub namespace: StrRef,
218 pub name: StrRef,
220}
221
222impl fmt::Debug for PackageId {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 write!(f, "@{}/{}", self.namespace, self.name)
225 }
226}
227
228impl TryFrom<FileId> for PackageId {
229 type Error = ();
230
231 fn try_from(value: FileId) -> Result<Self, Self::Error> {
232 let spec = value.package_compat().ok_or(())?;
233 Ok(PackageId {
234 namespace: spec.namespace.as_str().into(),
235 name: spec.name.as_str().into(),
236 })
237 }
238}
239
240#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
242pub enum BuiltinTy {
243 Clause,
245 Undef,
247 Space,
249 None,
251 Break,
253 Continue,
255 Infer,
257 FlowNone,
259 Auto,
261
262 Args,
264 Color,
266 TextSize,
268 TextFont,
270 TextFeature,
272 TextLang,
274 TextRegion,
276 Dir,
278 Label,
280 CiteLabel,
282 RefLabel,
284 Length,
286 Float,
288 Stroke,
290 Margin,
292 Inset,
294 Outset,
296 Radius,
298
299 Tag(Box<(StrRef, Option<Interned<PackageId>>)>),
301
302 Type(typst::foundations::Type),
304 TypeType(typst::foundations::Type),
306 Content(Option<typst::foundations::Element>),
311 Element(typst::foundations::Element),
313
314 Module(Interned<Decl>),
316 Path(PathKind),
318}
319
320impl fmt::Debug for BuiltinTy {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 match self {
323 BuiltinTy::Clause => f.write_str("Clause"),
324 BuiltinTy::Undef => f.write_str("Undef"),
325 BuiltinTy::Content(ty) => {
326 if let Some(ty) = ty {
327 write!(f, "Content({})", ty.name())
328 } else {
329 f.write_str("Content")
330 }
331 }
332 BuiltinTy::Space => f.write_str("Space"),
333 BuiltinTy::None => f.write_str("None"),
334 BuiltinTy::Break => f.write_str("Break"),
335 BuiltinTy::Continue => f.write_str("Continue"),
336 BuiltinTy::Infer => f.write_str("Infer"),
337 BuiltinTy::FlowNone => f.write_str("FlowNone"),
338 BuiltinTy::Auto => f.write_str("Auto"),
339
340 BuiltinTy::Args => write!(f, "Args"),
341 BuiltinTy::Color => write!(f, "Color"),
342 BuiltinTy::TextSize => write!(f, "TextSize"),
343 BuiltinTy::TextFont => write!(f, "TextFont"),
344 BuiltinTy::TextFeature => write!(f, "TextFeature"),
345 BuiltinTy::TextLang => write!(f, "TextLang"),
346 BuiltinTy::TextRegion => write!(f, "TextRegion"),
347 BuiltinTy::Dir => write!(f, "Dir"),
348 BuiltinTy::Length => write!(f, "Length"),
349 BuiltinTy::Label => write!(f, "Label"),
350 BuiltinTy::CiteLabel => write!(f, "CiteLabel"),
351 BuiltinTy::RefLabel => write!(f, "RefLabel"),
352 BuiltinTy::Float => write!(f, "Float"),
353 BuiltinTy::Stroke => write!(f, "Stroke"),
354 BuiltinTy::Margin => write!(f, "Margin"),
355 BuiltinTy::Inset => write!(f, "Inset"),
356 BuiltinTy::Outset => write!(f, "Outset"),
357 BuiltinTy::Radius => write!(f, "Radius"),
358 BuiltinTy::TypeType(ty) => write!(f, "TypeType({})", ty.short_name()),
359 BuiltinTy::Type(ty) => write!(f, "Type({})", ty.short_name()),
360 BuiltinTy::Element(elem) => elem.fmt(f),
361 BuiltinTy::Tag(tag) => {
362 let (name, id) = tag.as_ref();
363 if let Some(id) = id {
364 write!(f, "Tag({name:?}) of {id:?}")
365 } else {
366 write!(f, "Tag({name:?})")
367 }
368 }
369 BuiltinTy::Module(decl) => write!(f, "{decl:?}"),
370 BuiltinTy::Path(preference) => write!(f, "Path({preference:?})"),
371 }
372 }
373}
374
375impl BuiltinTy {
376 pub fn from_value(builtin: &Value) -> Ty {
378 if let Value::Bool(v) = builtin {
379 return Ty::Boolean(Some(*v));
380 }
381
382 Self::from_builtin(builtin.ty())
383 }
384
385 pub fn from_builtin(builtin: Type) -> Ty {
387 if builtin == Type::of::<AutoValue>() {
388 return Ty::Builtin(BuiltinTy::Auto);
389 }
390 if builtin == Type::of::<NoneValue>() {
391 return Ty::Builtin(BuiltinTy::None);
392 }
393 if builtin == Type::of::<typst::visualize::Color>() {
394 return Color.literally();
395 }
396 if builtin == Type::of::<bool>() {
397 return Ty::Builtin(BuiltinTy::None);
398 }
399 if builtin == Type::of::<f64>() {
400 return Float.literally();
401 }
402 if builtin == Type::of::<Length>() {
403 return Length.literally();
404 }
405 if builtin == Type::of::<Content>() {
406 return Ty::Builtin(BuiltinTy::Content(Option::None));
407 }
408
409 BuiltinTy::Type(builtin).literally()
410 }
411
412 pub(crate) fn describe(&self) -> EcoString {
414 let res = match self {
415 BuiltinTy::Clause => "any",
416 BuiltinTy::Undef => "any",
417 BuiltinTy::Content(ty) => {
418 return if let Some(ty) = ty {
419 eco_format!("content({})", ty.name())
420 } else {
421 "content".into()
422 };
423 }
424 BuiltinTy::Space => "content",
425 BuiltinTy::None => "none",
426 BuiltinTy::Break => "break",
427 BuiltinTy::Continue => "continue",
428 BuiltinTy::Infer => "any",
429 BuiltinTy::FlowNone => "none",
430 BuiltinTy::Auto => "auto",
431
432 BuiltinTy::Args => "arguments",
433 BuiltinTy::Color => "color",
434 BuiltinTy::TextSize => "text.size",
435 BuiltinTy::TextFont => "text.font",
436 BuiltinTy::TextFeature => "text.feature",
437 BuiltinTy::TextLang => "text.lang",
438 BuiltinTy::TextRegion => "text.region",
439 BuiltinTy::Dir => "dir",
440 BuiltinTy::Length => "length",
441 BuiltinTy::Float => "float",
442 BuiltinTy::Label => "label",
443 BuiltinTy::CiteLabel => "cite-label",
444 BuiltinTy::RefLabel => "ref-label",
445 BuiltinTy::Stroke => "stroke",
446 BuiltinTy::Margin => "margin",
447 BuiltinTy::Inset => "inset",
448 BuiltinTy::Outset => "outset",
449 BuiltinTy::Radius => "radius",
450 BuiltinTy::TypeType(..) => "type",
451 BuiltinTy::Type(ty) => ty.short_name(),
452 BuiltinTy::Element(ty) => ty.name(),
453 BuiltinTy::Tag(tag) => {
454 let (name, id) = tag.as_ref();
455 return if let Some(id) = id {
456 eco_format!("tag {name} of {id:?}")
457 } else {
458 eco_format!("tag {name}")
459 };
460 }
461 BuiltinTy::Module(m) => return eco_format!("module({})", m.name()),
462 BuiltinTy::Path(s) => match s {
463 PathKind::None => "[any]",
464 PathKind::Special => "[any]",
465 PathKind::Source { .. } => "[source]",
466 PathKind::Wasm => "[wasm]",
467 PathKind::Csv => "[csv]",
468 PathKind::Image => "[image]",
469 PathKind::Json => "[json]",
470 PathKind::Yaml => "[yaml]",
471 PathKind::Xml => "[xml]",
472 PathKind::Toml => "[toml]",
473 PathKind::Csl => "[csl]",
474 PathKind::Bibliography => "[bib]",
475 PathKind::RawTheme => "[theme]",
476 PathKind::RawSyntax => "[syntax]",
477 },
478 };
479
480 res.into()
481 }
482}
483
484use BuiltinTy::*;
485
486fn literally(s: impl FlowBuiltinLiterally) -> Ty {
488 s.literally()
489}
490
491trait FlowBuiltinLiterally {
493 fn literally(self) -> Ty;
494}
495
496impl FlowBuiltinLiterally for &str {
497 fn literally(self) -> Ty {
498 Ty::Value(InsTy::new(Value::Str(self.into())))
499 }
500}
501
502impl FlowBuiltinLiterally for BuiltinTy {
503 fn literally(self) -> Ty {
504 Ty::Builtin(self.clone())
505 }
506}
507
508impl FlowBuiltinLiterally for Ty {
509 fn literally(self) -> Ty {
510 self
511 }
512}
513
514macro_rules! flow_builtin_union_inner {
516 ($literal_kind:expr) => {
517 literally($literal_kind)
518 };
519 ($($x:expr),+ $(,)?) => {
520 Vec::from_iter([
521 $(flow_builtin_union_inner!($x)),*
522 ])
523 };
524}
525
526macro_rules! flow_union {
528 ($($b:tt)*) => {
530 Ty::iter_union(flow_builtin_union_inner!( $($b)* ).into_iter())
531 };
532
533}
534
535macro_rules! flow_record {
537 ($($name:expr => $ty:expr),* $(,)?) => {
538 RecordTy::new(vec![
539 $(
540 (
541 $name.into(),
542 $ty,
543 ),
544 )*
545 ])
546 };
547}
548
549pub(super) fn param_mapping(func: &Func, param: &ParamInfo) -> Option<Ty> {
551 let input_ty = || {
553 param
554 .to_native()
555 .map(|native| Ty::from_cast_info(&native.input))
556 .unwrap_or(Ty::Any)
557 };
558
559 match (func.name()?, param.name()?) {
560 ("embed", "path") => Some(literally(Path(PathKind::None))),
562 ("cbor", "path" | "source") => Some(literally(Path(PathKind::None))),
563 ("plugin", "source") => Some(literally(Path(PathKind::Wasm))),
564 ("csv", "path" | "source") => Some(literally(Path(PathKind::Csv))),
565 ("image", "path" | "source") => Some(literally(Path(PathKind::Image))),
566 ("read", "path" | "source") => Some(literally(Path(PathKind::None))),
567 ("json", "path" | "source") => Some(literally(Path(PathKind::Json))),
568 ("yaml", "path" | "source") => Some(literally(Path(PathKind::Yaml))),
569 ("xml", "path" | "source") => Some(literally(Path(PathKind::Xml))),
570 ("toml", "path" | "source") => Some(literally(Path(PathKind::Toml))),
571 ("raw", "theme") => Some(literally(Path(PathKind::RawTheme))),
572 ("raw", "syntaxes") => Some(literally(Path(PathKind::RawSyntax))),
573 ("bibliography" | "cite", "style") => {
574 Some(Ty::iter_union([literally(Path(PathKind::Csl)), input_ty()]))
575 }
576 ("cite", "key") => Some(Ty::iter_union([literally(CiteLabel)])),
577 ("ref", "target") => Some(Ty::iter_union([literally(RefLabel)])),
578 ("footnote", "body") => Some(Ty::iter_union([literally(RefLabel), input_ty()])),
579 ("link", "dest") => {
580 static LINK_DEST_TYPE: LazyLock<Ty> = LazyLock::new(|| {
581 flow_union!(
582 literally(RefLabel),
583 Ty::Builtin(BuiltinTy::Type(Type::of::<foundations::Str>())),
584 Ty::Builtin(BuiltinTy::Type(Type::of::<typst::introspection::Location>())),
585 Ty::Dict(RecordTy::new(vec![
586 ("x".into(), literally(Length)),
587 ("y".into(), literally(Length)),
588 ])),
589 )
590 });
591 Some(LINK_DEST_TYPE.clone())
592 }
593 ("bibliography", "path" | "sources") => {
594 static BIB_PATH_TYPE: LazyLock<Ty> = LazyLock::new(|| {
595 let bib_path_ty = literally(Path(PathKind::Bibliography));
596 Ty::iter_union([bib_path_ty.clone(), Ty::Array(bib_path_ty.into())])
597 });
598 Some(BIB_PATH_TYPE.clone())
599 }
600 ("text", "size") => Some(literally(TextSize)),
601 ("text", "font") => {
602 static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
604 Ty::iter_union([literally(TextFont), Ty::Array(literally(TextFont).into())])
605 });
606 Some(FONT_TYPE.clone())
607 }
608 ("text", "feature") => {
609 static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
610 Ty::iter_union([
611 Ty::Builtin(BuiltinTy::Type(Type::of::<foundations::Dict>())),
613 Ty::Array(literally(TextFeature).into()),
614 ])
615 });
616 Some(FONT_TYPE.clone())
617 }
618 ("text", "costs") => {
619 static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
620 Ty::Dict(flow_record!(
621 "hyphenation" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
622 "runt" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
623 "widow" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
624 "orphan" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
625 ))
626 });
627 Some(FONT_TYPE.clone())
628 }
629 ("text", "lang") => Some(literally(TextLang)),
630 ("text", "region") => Some(literally(TextRegion)),
631 ("text" | "stack", "dir") => Some(literally(Dir)),
632 ("par", "first-line-indent") => {
633 static FIRST_LINE_INDENT: LazyLock<Ty> = LazyLock::new(|| {
634 Ty::iter_union([
635 literally(Length),
636 Ty::Dict(RecordTy::new(vec![
637 ("amount".into(), literally(Length)),
638 ("all".into(), Ty::Boolean(Option::None)),
639 ])),
640 ])
641 });
642 Some(FIRST_LINE_INDENT.clone())
643 }
644 (
645 "page" | "highlight" | "text" | "path" | "curve" | "rect" | "ellipse" | "circle"
647 | "polygon" | "box" | "block" | "table" | "regular",
648 "fill",
649 ) => Some(literally(Color)),
650 (
651 "table" | "cell" | "block" | "box" | "circle" | "ellipse" | "rect" | "square",
653 "inset",
654 ) => Some(literally(Inset)),
655 ("block" | "box" | "circle" | "ellipse" | "rect" | "square", "outset") => {
656 Some(literally(Outset))
657 }
658 ("block" | "box" | "rect" | "square" | "highlight", "radius") => Some(literally(Radius)),
659 ("grid" | "table", "columns" | "rows" | "gutter" | "column-gutter" | "row-gutter") => {
660 static COLUMN_TYPE: LazyLock<Ty> = LazyLock::new(|| {
661 flow_union!(
662 Ty::Value(InsTy::new(Value::Auto)),
663 Ty::Value(InsTy::new(Value::Type(Type::of::<i64>()))),
664 literally(Length),
665 Ty::Array(literally(Length).into()),
666 )
667 });
668 Some(COLUMN_TYPE.clone())
669 }
670 ("pattern" | "tiling", "size") => {
671 static PATTERN_SIZE_TYPE: LazyLock<Ty> = LazyLock::new(|| {
672 flow_union!(
673 Ty::Value(InsTy::new(Value::Auto)),
674 Ty::Array(Ty::Builtin(Length).into()),
675 )
676 });
677 Some(PATTERN_SIZE_TYPE.clone())
678 }
679 ("stroke", "dash") => Some(FLOW_STROKE_DASH_TYPE.clone()),
680 (
681 "cancel" | "highlight" | "overline" | "strike" | "underline" | "text" | "path"
683 | "curve" | "rect" | "ellipse" | "circle" | "polygon" | "box" | "block" | "table"
684 | "line" | "cell" | "hline" | "vline" | "regular",
685 "stroke",
686 ) => Some(Ty::Builtin(Stroke)),
687 ("page", "margin") => Some(Ty::Builtin(Margin)),
688 _ => Option::None,
689 }
690}
691
692static FLOW_STROKE_DASH_TYPE: LazyLock<Ty> = LazyLock::new(|| {
694 flow_union!(
695 "solid",
696 "dotted",
697 "densely-dotted",
698 "loosely-dotted",
699 "dashed",
700 "densely-dashed",
701 "loosely-dashed",
702 "dash-dotted",
703 "densely-dash-dotted",
704 "loosely-dash-dotted",
705 Ty::Array(flow_union!("dot", literally(Float)).into()),
706 Ty::Dict(flow_record!(
707 "array" => Ty::Array(flow_union!("dot", literally(Float)).into()),
708 "phase" => literally(Length),
709 ))
710 )
711});
712
713pub static FLOW_STROKE_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
715 flow_record!(
716 "paint" => literally(Color),
717 "thickness" => literally(Length),
718 "cap" => flow_union!("butt", "round", "square"),
719 "join" => flow_union!("miter", "round", "bevel"),
720 "dash" => FLOW_STROKE_DASH_TYPE.clone(),
721 "miter-limit" => literally(Float),
722 )
723});
724
725pub static FLOW_MARGIN_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
727 flow_record!(
728 "top" => literally(Length),
729 "right" => literally(Length),
730 "bottom" => literally(Length),
731 "left" => literally(Length),
732 "inside" => literally(Length),
733 "outside" => literally(Length),
734 "x" => literally(Length),
735 "y" => literally(Length),
736 "rest" => literally(Length),
737 )
738});
739
740pub static FLOW_INSET_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
742 flow_record!(
743 "top" => literally(Length),
744 "right" => literally(Length),
745 "bottom" => literally(Length),
746 "left" => literally(Length),
747 "x" => literally(Length),
748 "y" => literally(Length),
749 "rest" => literally(Length),
750 )
751});
752
753pub static FLOW_OUTSET_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
755 flow_record!(
756 "top" => literally(Length),
757 "right" => literally(Length),
758 "bottom" => literally(Length),
759 "left" => literally(Length),
760 "x" => literally(Length),
761 "y" => literally(Length),
762 "rest" => literally(Length),
763 )
764});
765
766pub static FLOW_RADIUS_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
768 flow_record!(
769 "top" => literally(Length),
770 "right" => literally(Length),
771 "bottom" => literally(Length),
772 "left" => literally(Length),
773 "top-left" => literally(Length),
774 "top-right" => literally(Length),
775 "bottom-left" => literally(Length),
776 "bottom-right" => literally(Length),
777 "rest" => literally(Length),
778 )
779});
780
781pub static FLOW_TEXT_FONT_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
783 flow_record!(
784 "name" => literally(TextFont),
785 "covers" => flow_union!("latin-in-cjk", BuiltinTy::Type(Type::of::<Regex>())),
786 )
787});
788
789#[cfg(test)]
808mod tests {
809
810 use crate::syntax::Decl;
811
812 use super::{SigTy, Ty, TypeVar};
813
814 #[test]
815 fn test_image_extension() {
816 let path = "test.png";
817 let preference = super::PathKind::from_ext(path).unwrap();
818 assert_eq!(preference, super::PathKind::Image);
819 }
820
821 #[test]
822 fn test_image_extension_uppercase() {
823 let path = "TEST.PNG";
824 let preference = super::PathKind::from_ext(path).unwrap();
825 assert_eq!(preference, super::PathKind::Image);
826 }
827
828 #[test]
833 fn test_map() {
834 let u = Ty::Var(TypeVar::new("u".into(), Decl::lit("u").into()));
835 let v = Ty::Var(TypeVar::new("v".into(), Decl::lit("v").into()));
836 let mapper_fn =
837 Ty::Func(SigTy::new([u].into_iter(), None, None, None, Some(v.clone())).into());
838 let map_fn =
839 Ty::Func(SigTy::new([mapper_fn].into_iter(), None, None, None, Some(v)).into());
840 let _ = map_fn;
841 }
843}