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