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