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 ArgumentsPos(&'a Ty),
213}
214
215impl<'a> BuiltinSig<'a> {
216 pub fn arguments_method(receiver: &'a Ty, method: &str) -> Option<Self> {
218 (method == "pos").then_some(Self::ArgumentsPos(receiver))
219 }
220}
221
222#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
224pub struct PackageId {
225 pub namespace: StrRef,
227 pub name: StrRef,
229}
230
231impl fmt::Debug for PackageId {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 write!(f, "@{}/{}", self.namespace, self.name)
234 }
235}
236
237impl TryFrom<FileId> for PackageId {
238 type Error = ();
239
240 fn try_from(value: FileId) -> Result<Self, Self::Error> {
241 let spec = value.package_compat().ok_or(())?;
242 Ok(PackageId {
243 namespace: spec.namespace.as_str().into(),
244 name: spec.name.as_str().into(),
245 })
246 }
247}
248
249#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
251pub enum BuiltinTy {
252 Clause,
254 Undef,
256 Space,
258 None,
260 Break,
262 Continue,
264 Never,
266 Infer,
268 FlowNone,
270 Auto,
272
273 Args,
275 Color,
277 TextSize,
279 TextFont,
281 TextFeature,
283 TextLang,
285 TextRegion,
287 Dir,
289 Label,
291 CiteLabel,
293 RefLabel,
295 Length,
297 Float,
299 Stroke,
301 Margin,
303 Inset,
305 Outset,
307 Radius,
309
310 Tag(Box<(StrRef, Option<Interned<PackageId>>)>),
312
313 Type(typst::foundations::Type),
315 TypeType(typst::foundations::Type),
317 Content(Option<typst::foundations::Element>),
322 Element(typst::foundations::Element),
324
325 Module(Interned<Decl>),
327 Path(PathKind),
329}
330
331impl fmt::Debug for BuiltinTy {
332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 match self {
334 BuiltinTy::Clause => f.write_str("Clause"),
335 BuiltinTy::Undef => f.write_str("Undef"),
336 BuiltinTy::Content(ty) => {
337 if let Some(ty) = ty {
338 write!(f, "Content({})", ty.name())
339 } else {
340 f.write_str("Content")
341 }
342 }
343 BuiltinTy::Space => f.write_str("Space"),
344 BuiltinTy::None => f.write_str("None"),
345 BuiltinTy::Break => f.write_str("Break"),
346 BuiltinTy::Continue => f.write_str("Continue"),
347 BuiltinTy::Never => f.write_str("Never"),
348 BuiltinTy::Infer => f.write_str("Infer"),
349 BuiltinTy::FlowNone => f.write_str("FlowNone"),
350 BuiltinTy::Auto => f.write_str("Auto"),
351
352 BuiltinTy::Args => write!(f, "Args"),
353 BuiltinTy::Color => write!(f, "Color"),
354 BuiltinTy::TextSize => write!(f, "TextSize"),
355 BuiltinTy::TextFont => write!(f, "TextFont"),
356 BuiltinTy::TextFeature => write!(f, "TextFeature"),
357 BuiltinTy::TextLang => write!(f, "TextLang"),
358 BuiltinTy::TextRegion => write!(f, "TextRegion"),
359 BuiltinTy::Dir => write!(f, "Dir"),
360 BuiltinTy::Length => write!(f, "Length"),
361 BuiltinTy::Label => write!(f, "Label"),
362 BuiltinTy::CiteLabel => write!(f, "CiteLabel"),
363 BuiltinTy::RefLabel => write!(f, "RefLabel"),
364 BuiltinTy::Float => write!(f, "Float"),
365 BuiltinTy::Stroke => write!(f, "Stroke"),
366 BuiltinTy::Margin => write!(f, "Margin"),
367 BuiltinTy::Inset => write!(f, "Inset"),
368 BuiltinTy::Outset => write!(f, "Outset"),
369 BuiltinTy::Radius => write!(f, "Radius"),
370 BuiltinTy::TypeType(ty) => write!(f, "TypeType({})", ty.short_name()),
371 BuiltinTy::Type(ty) => write!(f, "Type({})", ty.short_name()),
372 BuiltinTy::Element(elem) => elem.fmt(f),
373 BuiltinTy::Tag(tag) => {
374 let (name, id) = tag.as_ref();
375 if let Some(id) = id {
376 write!(f, "Tag({name:?}) of {id:?}")
377 } else {
378 write!(f, "Tag({name:?})")
379 }
380 }
381 BuiltinTy::Module(decl) => write!(f, "{decl:?}"),
382 BuiltinTy::Path(preference) => write!(f, "Path({preference:?})"),
383 }
384 }
385}
386
387impl BuiltinTy {
388 pub fn from_value(builtin: &Value) -> Ty {
390 if let Value::Bool(v) = builtin {
391 return Ty::Boolean(Some(*v));
392 }
393
394 Self::from_builtin(builtin.ty())
395 }
396
397 pub fn from_builtin(builtin: Type) -> Ty {
399 if builtin == Type::of::<AutoValue>() {
400 return Ty::Builtin(BuiltinTy::Auto);
401 }
402 if builtin == Type::of::<NoneValue>() {
403 return Ty::Builtin(BuiltinTy::None);
404 }
405 if builtin == Type::of::<typst::visualize::Color>() {
406 return Color.literally();
407 }
408 if builtin == Type::of::<bool>() {
409 return Ty::Builtin(BuiltinTy::None);
410 }
411 if builtin == Type::of::<f64>() {
412 return Float.literally();
413 }
414 if builtin == Type::of::<Length>() {
415 return Length.literally();
416 }
417 if builtin == Type::of::<Content>() {
418 return Ty::Builtin(BuiltinTy::Content(Option::None));
419 }
420
421 BuiltinTy::Type(builtin).literally()
422 }
423
424 pub(crate) fn describe(&self) -> EcoString {
426 let res = match self {
427 BuiltinTy::Clause => "any",
428 BuiltinTy::Undef => "any",
429 BuiltinTy::Content(ty) => {
430 return if let Some(ty) = ty {
431 eco_format!("content({})", ty.name())
432 } else {
433 "content".into()
434 };
435 }
436 BuiltinTy::Space => "content",
437 BuiltinTy::None => "none",
438 BuiltinTy::Break => "break",
439 BuiltinTy::Continue => "continue",
440 BuiltinTy::Never => "never",
441 BuiltinTy::Infer => "any",
442 BuiltinTy::FlowNone => "none",
443 BuiltinTy::Auto => "auto",
444
445 BuiltinTy::Args => "arguments",
446 BuiltinTy::Color => "color",
447 BuiltinTy::TextSize => "text.size",
448 BuiltinTy::TextFont => "text.font",
449 BuiltinTy::TextFeature => "text.feature",
450 BuiltinTy::TextLang => "text.lang",
451 BuiltinTy::TextRegion => "text.region",
452 BuiltinTy::Dir => "dir",
453 BuiltinTy::Length => "length",
454 BuiltinTy::Float => "float",
455 BuiltinTy::Label => "label",
456 BuiltinTy::CiteLabel => "cite-label",
457 BuiltinTy::RefLabel => "ref-label",
458 BuiltinTy::Stroke => "stroke",
459 BuiltinTy::Margin => "margin",
460 BuiltinTy::Inset => "inset",
461 BuiltinTy::Outset => "outset",
462 BuiltinTy::Radius => "radius",
463 BuiltinTy::TypeType(..) => "type",
464 BuiltinTy::Type(ty) => ty.short_name(),
465 BuiltinTy::Element(ty) => ty.name(),
466 BuiltinTy::Tag(tag) => {
467 let (name, id) = tag.as_ref();
468 return if let Some(id) = id {
469 eco_format!("tag {name} of {id:?}")
470 } else {
471 eco_format!("tag {name}")
472 };
473 }
474 BuiltinTy::Module(m) => return eco_format!("module({})", m.name()),
475 BuiltinTy::Path(s) => match s {
476 PathKind::None => "[any]",
477 PathKind::Special => "[any]",
478 PathKind::Source { .. } => "[source]",
479 PathKind::Wasm => "[wasm]",
480 PathKind::Csv => "[csv]",
481 PathKind::Image => "[image]",
482 PathKind::Json => "[json]",
483 PathKind::Yaml => "[yaml]",
484 PathKind::Xml => "[xml]",
485 PathKind::Toml => "[toml]",
486 PathKind::Csl => "[csl]",
487 PathKind::Bibliography => "[bib]",
488 PathKind::RawTheme => "[theme]",
489 PathKind::RawSyntax => "[syntax]",
490 },
491 };
492
493 res.into()
494 }
495}
496
497use BuiltinTy::*;
498
499fn literally(s: impl FlowBuiltinLiterally) -> Ty {
501 s.literally()
502}
503
504trait FlowBuiltinLiterally {
506 fn literally(self) -> Ty;
507}
508
509impl FlowBuiltinLiterally for &str {
510 fn literally(self) -> Ty {
511 Ty::Value(InsTy::new(Value::Str(self.into())))
512 }
513}
514
515impl FlowBuiltinLiterally for BuiltinTy {
516 fn literally(self) -> Ty {
517 Ty::Builtin(self.clone())
518 }
519}
520
521impl FlowBuiltinLiterally for Ty {
522 fn literally(self) -> Ty {
523 self
524 }
525}
526
527macro_rules! flow_builtin_union_inner {
529 ($literal_kind:expr) => {
530 literally($literal_kind)
531 };
532 ($($x:expr),+ $(,)?) => {
533 Vec::from_iter([
534 $(flow_builtin_union_inner!($x)),*
535 ])
536 };
537}
538
539macro_rules! flow_union {
541 ($($b:tt)*) => {
543 Ty::iter_union(flow_builtin_union_inner!( $($b)* ).into_iter())
544 };
545
546}
547
548macro_rules! flow_record {
550 ($($name:expr => $ty:expr),* $(,)?) => {
551 RecordTy::new(vec![
552 $(
553 (
554 $name.into(),
555 $ty,
556 ),
557 )*
558 ])
559 };
560}
561
562pub(super) fn param_mapping(func: &Func, param: &ParamInfo) -> Option<Ty> {
564 let input_ty = || {
566 param
567 .to_native()
568 .map(|native| Ty::from_cast_info(&native.input))
569 .unwrap_or(Ty::Any)
570 };
571
572 match (func.name()?, param.name()?) {
573 ("embed", "path") => Some(literally(Path(PathKind::None))),
575 ("cbor", "path" | "source") => Some(literally(Path(PathKind::None))),
576 ("plugin", "source") => Some(literally(Path(PathKind::Wasm))),
577 ("csv", "path" | "source") => Some(literally(Path(PathKind::Csv))),
578 ("image", "path" | "source") => Some(literally(Path(PathKind::Image))),
579 ("read", "path" | "source") => Some(literally(Path(PathKind::None))),
580 ("json", "path" | "source") => Some(literally(Path(PathKind::Json))),
581 ("yaml", "path" | "source") => Some(literally(Path(PathKind::Yaml))),
582 ("xml", "path" | "source") => Some(literally(Path(PathKind::Xml))),
583 ("toml", "path" | "source") => Some(literally(Path(PathKind::Toml))),
584 ("raw", "theme") => Some(literally(Path(PathKind::RawTheme))),
585 ("raw", "syntaxes") => Some(literally(Path(PathKind::RawSyntax))),
586 ("bibliography" | "cite", "style") => {
587 Some(Ty::iter_union([literally(Path(PathKind::Csl)), input_ty()]))
588 }
589 ("cite", "key") => Some(Ty::iter_union([literally(CiteLabel)])),
590 ("ref", "target") => Some(Ty::iter_union([literally(RefLabel)])),
591 ("footnote", "body") => Some(Ty::iter_union([literally(RefLabel), input_ty()])),
592 ("link", "dest") => {
593 static LINK_DEST_TYPE: LazyLock<Ty> = LazyLock::new(|| {
594 flow_union!(
595 literally(RefLabel),
596 Ty::Builtin(BuiltinTy::Type(Type::of::<foundations::Str>())),
597 Ty::Builtin(BuiltinTy::Type(Type::of::<typst::introspection::Location>())),
598 Ty::Dict(RecordTy::new(vec![
599 ("x".into(), literally(Length)),
600 ("y".into(), literally(Length)),
601 ])),
602 )
603 });
604 Some(LINK_DEST_TYPE.clone())
605 }
606 ("bibliography", "path" | "sources") => {
607 static BIB_PATH_TYPE: LazyLock<Ty> = LazyLock::new(|| {
608 let bib_path_ty = literally(Path(PathKind::Bibliography));
609 Ty::iter_union([bib_path_ty.clone(), Ty::Array(bib_path_ty.into())])
610 });
611 Some(BIB_PATH_TYPE.clone())
612 }
613 ("text", "size") => Some(literally(TextSize)),
614 ("text", "font") => {
615 static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
617 Ty::iter_union([literally(TextFont), Ty::Array(literally(TextFont).into())])
618 });
619 Some(FONT_TYPE.clone())
620 }
621 ("text", "feature") => {
622 static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
623 Ty::iter_union([
624 Ty::Builtin(BuiltinTy::Type(Type::of::<foundations::Dict>())),
626 Ty::Array(literally(TextFeature).into()),
627 ])
628 });
629 Some(FONT_TYPE.clone())
630 }
631 ("text", "costs") => {
632 static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
633 Ty::Dict(flow_record!(
634 "hyphenation" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
635 "runt" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
636 "widow" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
637 "orphan" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
638 ))
639 });
640 Some(FONT_TYPE.clone())
641 }
642 ("text", "lang") => Some(literally(TextLang)),
643 ("text", "region") => Some(literally(TextRegion)),
644 ("text" | "stack", "dir") => Some(literally(Dir)),
645 ("par", "first-line-indent") => {
646 static FIRST_LINE_INDENT: LazyLock<Ty> = LazyLock::new(|| {
647 Ty::iter_union([
648 literally(Length),
649 Ty::Dict(RecordTy::new(vec![
650 ("amount".into(), literally(Length)),
651 ("all".into(), Ty::Boolean(Option::None)),
652 ])),
653 ])
654 });
655 Some(FIRST_LINE_INDENT.clone())
656 }
657 (
658 "page" | "highlight" | "text" | "path" | "curve" | "rect" | "ellipse" | "circle"
660 | "polygon" | "box" | "block" | "table" | "regular",
661 "fill",
662 ) => Some(literally(Color)),
663 (
664 "table" | "cell" | "block" | "box" | "circle" | "ellipse" | "rect" | "square",
666 "inset",
667 ) => Some(literally(Inset)),
668 ("block" | "box" | "circle" | "ellipse" | "rect" | "square", "outset") => {
669 Some(literally(Outset))
670 }
671 ("block" | "box" | "rect" | "square" | "highlight", "radius") => Some(literally(Radius)),
672 ("grid" | "table", "columns" | "rows" | "gutter" | "column-gutter" | "row-gutter") => {
673 static COLUMN_TYPE: LazyLock<Ty> = LazyLock::new(|| {
674 flow_union!(
675 Ty::Value(InsTy::new(Value::Auto)),
676 Ty::Value(InsTy::new(Value::Type(Type::of::<i64>()))),
677 literally(Length),
678 Ty::Array(literally(Length).into()),
679 )
680 });
681 Some(COLUMN_TYPE.clone())
682 }
683 ("pattern" | "tiling", "size") => {
684 static PATTERN_SIZE_TYPE: LazyLock<Ty> = LazyLock::new(|| {
685 flow_union!(
686 Ty::Value(InsTy::new(Value::Auto)),
687 Ty::Array(Ty::Builtin(Length).into()),
688 )
689 });
690 Some(PATTERN_SIZE_TYPE.clone())
691 }
692 ("stroke", "dash") => Some(FLOW_STROKE_DASH_TYPE.clone()),
693 (
694 "cancel" | "highlight" | "overline" | "strike" | "underline" | "text" | "path"
696 | "curve" | "rect" | "ellipse" | "circle" | "polygon" | "box" | "block" | "table"
697 | "line" | "cell" | "hline" | "vline" | "regular",
698 "stroke",
699 ) => Some(Ty::Builtin(Stroke)),
700 ("page", "margin") => Some(Ty::Builtin(Margin)),
701 _ => Option::None,
702 }
703}
704
705static FLOW_STROKE_DASH_TYPE: LazyLock<Ty> = LazyLock::new(|| {
707 flow_union!(
708 "solid",
709 "dotted",
710 "densely-dotted",
711 "loosely-dotted",
712 "dashed",
713 "densely-dashed",
714 "loosely-dashed",
715 "dash-dotted",
716 "densely-dash-dotted",
717 "loosely-dash-dotted",
718 Ty::Array(flow_union!("dot", literally(Float)).into()),
719 Ty::Dict(flow_record!(
720 "array" => Ty::Array(flow_union!("dot", literally(Float)).into()),
721 "phase" => literally(Length),
722 ))
723 )
724});
725
726pub static FLOW_STROKE_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
728 flow_record!(
729 "paint" => literally(Color),
730 "thickness" => literally(Length),
731 "cap" => flow_union!("butt", "round", "square"),
732 "join" => flow_union!("miter", "round", "bevel"),
733 "dash" => FLOW_STROKE_DASH_TYPE.clone(),
734 "miter-limit" => literally(Float),
735 )
736});
737
738pub static FLOW_MARGIN_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
740 flow_record!(
741 "top" => literally(Length),
742 "right" => literally(Length),
743 "bottom" => literally(Length),
744 "left" => literally(Length),
745 "inside" => literally(Length),
746 "outside" => literally(Length),
747 "x" => literally(Length),
748 "y" => literally(Length),
749 "rest" => literally(Length),
750 )
751});
752
753pub static FLOW_INSET_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_OUTSET_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 "x" => literally(Length),
774 "y" => literally(Length),
775 "rest" => literally(Length),
776 )
777});
778
779pub static FLOW_RADIUS_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
781 flow_record!(
782 "top" => literally(Length),
783 "right" => literally(Length),
784 "bottom" => literally(Length),
785 "left" => literally(Length),
786 "top-left" => literally(Length),
787 "top-right" => literally(Length),
788 "bottom-left" => literally(Length),
789 "bottom-right" => literally(Length),
790 "rest" => literally(Length),
791 )
792});
793
794pub static FLOW_TEXT_FONT_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
796 flow_record!(
797 "name" => literally(TextFont),
798 "covers" => flow_union!("latin-in-cjk", BuiltinTy::Type(Type::of::<Regex>())),
799 )
800});
801
802#[cfg(test)]
821mod tests {
822
823 use crate::syntax::Decl;
824
825 use super::{SigTy, Ty, TypeVar};
826
827 #[test]
828 fn test_image_extension() {
829 let path = "test.png";
830 let preference = super::PathKind::from_ext(path).unwrap();
831 assert_eq!(preference, super::PathKind::Image);
832 }
833
834 #[test]
835 fn test_image_extension_uppercase() {
836 let path = "TEST.PNG";
837 let preference = super::PathKind::from_ext(path).unwrap();
838 assert_eq!(preference, super::PathKind::Image);
839 }
840
841 #[test]
846 fn test_map() {
847 let u = Ty::Var(TypeVar::new("u".into(), Decl::lit("u").into()));
848 let v = Ty::Var(TypeVar::new("v".into(), Decl::lit("v").into()));
849 let mapper_fn =
850 Ty::Func(SigTy::new([u].into_iter(), None, None, None, Some(v.clone())).into());
851 let map_fn =
852 Ty::Func(SigTy::new([mapper_fn].into_iter(), None, None, None, Some(v)).into());
853 let _ = map_fn;
854 }
856}