1use crate::{
2 CollectionColumns, CompareTypes, OneOf, SyntaxShape, TypeRelation, TypeSet, ast::PathMember,
3};
4use serde::{Deserialize, Serialize};
5use std::{borrow::Cow, fmt::Display};
6#[cfg(test)]
7use strum_macros::EnumIter;
8
9#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Hash, Ord, PartialOrd)]
10#[cfg_attr(test, derive(EnumIter))]
11pub enum Type {
12 Any,
14 Binary,
15 Block,
16 Bool,
17 CellPath,
18 Closure,
19 Custom(Box<str>),
20 Date,
21 Duration,
22 Error,
23 Filesize,
24 Float,
25 Int,
26 List(Box<Type>),
27 #[default]
28 Nothing,
29 Number,
31 OneOf(OneOf),
33 Range,
34 Record(CollectionColumns<Type>),
35 String,
36 Glob,
37 Table(CollectionColumns<Type>),
38}
39
40fn follow_cell_path_recursive<'a>(
41 current: Cow<'a, Type>,
42 path_members: &mut dyn Iterator<Item = &'a PathMember>,
43) -> Option<Cow<'a, Type>> {
44 let Some(first) = path_members.next() else {
45 return Some(current);
46 };
47 match (current.as_ref(), first) {
48 (Type::Record(_), PathMember::String { val, .. }) => {
49 let next = match current {
50 Cow::Borrowed(Type::Record(f)) => {
51 Cow::Borrowed(&f.iter().find(|(name, _)| name == val)?.1)
52 }
53 Cow::Owned(Type::Record(f)) => {
54 Cow::Owned(f.into_iter().find(|(name, _)| name == val)?.1)
55 }
56 _ => unreachable!(),
57 };
58 follow_cell_path_recursive(next, path_members)
59 }
60
61 (Type::Table(f), PathMember::Int { .. }) => {
63 follow_cell_path_recursive(Cow::Owned(Type::Record(f.clone())), path_members)
64 }
65
66 (Type::Table(columns), PathMember::String { val, .. }) => {
68 let (_, sub_type) = columns.iter().find(|(name, _)| name == val)?;
69 let list_type = Type::List(Box::new(sub_type.clone()));
70 follow_cell_path_recursive(Cow::Owned(list_type), path_members)
71 }
72
73 (Type::List(_), PathMember::Int { .. }) => {
74 let next = match current {
75 Cow::Borrowed(Type::List(i)) => Cow::Borrowed(i.as_ref()),
76 Cow::Owned(Type::List(i)) => Cow::Owned(*i),
77 _ => unreachable!(),
78 };
79 follow_cell_path_recursive(next, path_members)
80 }
81
82 (Type::List(_), PathMember::String { .. }) => {
84 let next = match current {
85 Cow::Borrowed(Type::List(i)) => Cow::Borrowed(i.as_ref()),
86 Cow::Owned(Type::List(i)) => Cow::Owned(*i),
87 _ => unreachable!(),
88 };
89
90 let mut found_int_member = false;
91 let mut new_iter = std::iter::once(first).chain(path_members).filter(|pm| {
92 let first_int = !found_int_member && matches!(pm, PathMember::Int { .. });
93 if first_int {
94 found_int_member = true;
95 }
96 !first_int
97 });
98 let inner_ty = follow_cell_path_recursive(next, &mut new_iter);
99
100 if found_int_member {
103 inner_ty
104 } else {
105 inner_ty.map(|inner_ty| Cow::Owned(Type::List(Box::new(inner_ty.into_owned()))))
106 }
107 }
108
109 _ => None,
110 }
111}
112
113impl Type {
114 pub fn list(inner: Type) -> Self {
115 Self::List(Box::new(inner))
116 }
117
118 pub fn one_of(types: impl IntoIterator<Item = Type>) -> Self {
121 Self::OneOf(OneOf::from_iter(types))
122 }
123
124 pub fn record() -> Self {
125 Self::Record(Default::default())
126 }
127
128 pub fn table() -> Self {
129 Self::Table(Default::default())
130 }
131
132 pub fn custom(name: impl Into<Box<str>>) -> Self {
133 Self::Custom(name.into())
134 }
135
136 pub(crate) fn flat_widen(lhs: Type, rhs: Type) -> Result<Type, (Type, Type)> {
138 match (lhs, rhs) {
139 (Type::Any, _) | (_, Type::Any) => Ok(Type::Any),
141
142 (Type::Int, Type::Float) | (Type::Float, Type::Int) => Ok(Type::Number),
144
145 tys @ ((Type::Glob, Type::String)
147 | (Type::String, Type::Glob)
148 | (Type::String | Type::Int, Type::CellPath)
149 | (Type::CellPath, Type::String | Type::Int)) => Err(tys),
150
151 (Type::Record(lhs), Type::Record(rhs)) => Ok(Type::Record(lhs.union(rhs))),
153 (Type::Table(lhs), Type::Table(rhs)) => Ok(Type::Table(lhs.union(rhs))),
154
155 tys @ ((Type::List(_), Type::Table(_)) | (Type::Table(_), Type::List(_))) => Err(tys),
158
159 (lhs, rhs) => match lhs.compare_types(&rhs) {
161 Some(rel) => Ok(match rel {
162 TypeRelation::Subtype => rhs,
163 TypeRelation::Equal => lhs,
164 TypeRelation::Supertype => lhs,
165 }),
166 None => Err((lhs, rhs)),
168 },
169 }
170 }
171
172 pub fn supertype_of(it: impl IntoIterator<Item = Type>) -> Option<Self> {
175 let mut it = it.into_iter();
176 it.next().and_then(|head| {
177 it.try_fold(head, |acc, e| match acc.union(e) {
178 Type::Any => None,
179 r => Some(r),
180 })
181 })
182 }
183
184 pub fn is_numeric(&self) -> bool {
185 matches!(self, Type::Int | Type::Float | Type::Number)
186 }
187
188 pub fn is_list(&self) -> bool {
189 matches!(self, Type::List(_))
190 }
191
192 pub fn accepts_cell_paths(&self) -> bool {
194 matches!(self, Type::List(_) | Type::Record(_) | Type::Table(_))
195 }
196
197 pub fn to_shape(&self) -> SyntaxShape {
198 match self {
199 Type::Int => SyntaxShape::Int,
200 Type::Float => SyntaxShape::Float,
201 Type::Range => SyntaxShape::Range,
202 Type::Bool => SyntaxShape::Boolean,
203 Type::String => SyntaxShape::String,
204 Type::Block => SyntaxShape::Block, Type::Closure => SyntaxShape::Closure(None), Type::CellPath => SyntaxShape::CellPath,
207 Type::Duration => SyntaxShape::Duration,
208 Type::Date => SyntaxShape::DateTime,
209 Type::Filesize => SyntaxShape::Filesize,
210 Type::List(x) => SyntaxShape::List(Box::new(x.to_shape())),
211 Type::Number => SyntaxShape::Number,
212 Type::OneOf(types) => SyntaxShape::OneOf(types.iter().map(Type::to_shape).collect()),
213 Type::Nothing => SyntaxShape::Nothing,
214 Type::Record(entries) => SyntaxShape::Record(entries.map(Type::to_shape)),
215 Type::Table(columns) => SyntaxShape::Table(columns.map(Type::to_shape)),
216 Type::Any => SyntaxShape::Any,
217 Type::Error => SyntaxShape::Any,
218 Type::Binary => SyntaxShape::Binary,
219 Type::Custom(_) => SyntaxShape::Any,
220 Type::Glob => SyntaxShape::GlobPattern,
221 }
222 }
223
224 pub fn get_non_specified_string(&self) -> String {
227 match self {
228 Type::Closure => String::from("closure"),
229 Type::Bool => String::from("bool"),
230 Type::Block => String::from("block"),
231 Type::CellPath => String::from("cell-path"),
232 Type::Date => String::from("datetime"),
233 Type::Duration => String::from("duration"),
234 Type::Filesize => String::from("filesize"),
235 Type::Float => String::from("float"),
236 Type::Int => String::from("int"),
237 Type::Range => String::from("range"),
238 Type::Record(_) => String::from("record"),
239 Type::Table(_) => String::from("table"),
240 Type::List(_) => String::from("list"),
241 Type::Nothing => String::from("nothing"),
242 Type::Number => String::from("number"),
243 Type::OneOf(_) => String::from("oneof"),
244 Type::String => String::from("string"),
245 Type::Any => String::from("any"),
246 Type::Error => String::from("error"),
247 Type::Binary => String::from("binary"),
248 Type::Custom(_) => String::from("custom"),
249 Type::Glob => String::from("glob"),
250 }
251 }
252
253 pub fn follow_cell_path<'a>(&'a self, path_members: &'a [PathMember]) -> Option<Cow<'a, Self>> {
254 follow_cell_path_recursive(Cow::Borrowed(self), &mut path_members.iter())
255 }
256}
257
258impl CompareTypes for Type {
259 fn compare_types(&self, other: &Self) -> Option<TypeRelation> {
260 match (self, other) {
261 (_, Type::Any) => Some(TypeRelation::Subtype),
262 (Type::Any, _) => Some(TypeRelation::Supertype),
263
264 (Type::Closure, Type::Block) => Some(TypeRelation::Supertype),
267 (Type::Block, Type::Closure) => Some(TypeRelation::Subtype),
268
269 (Type::String | Type::Int, Type::CellPath) => Some(TypeRelation::Subtype),
272 (Type::CellPath, Type::String | Type::Int) => Some(TypeRelation::Supertype),
273
274 (Type::Float | Type::Int, Type::Number) => Some(TypeRelation::Subtype),
275 (Type::Number, Type::Float | Type::Int) => Some(TypeRelation::Supertype),
276
277 (Type::Glob, Type::String) => Some(TypeRelation::Supertype),
278 (Type::String, Type::Glob) => Some(TypeRelation::Subtype),
279
280 (Type::List(t), Type::List(u)) => t.compare_types(u.as_ref()),
282
283 (Type::Record(this), Type::Record(that)) | (Type::Table(this), Type::Table(that)) => {
284 this.compare_types(that)
285 }
286
287 (Type::Table(table_cols), Type::List(list_elem)) => match list_elem.as_ref() {
288 Type::Any => Some(TypeRelation::Subtype),
289 Type::Record(record_cols) => table_cols.compare_types(record_cols),
290 ty => Type::Record(table_cols.clone()).compare_types(ty),
291 },
292 (Type::List(list_elem), Type::Table(table_cols)) => match list_elem.as_ref() {
293 Type::Any => Some(TypeRelation::Supertype),
294 Type::Record(record_cols) => record_cols.compare_types(table_cols),
295 ty => ty.compare_types(&Type::Record(table_cols.clone())),
296 },
297
298 (Type::OneOf(lhs_oneof), Type::OneOf(rhs_oneof)) => lhs_oneof.compare_types(rhs_oneof),
299 (Type::OneOf(lhs_oneof), rhs) => lhs_oneof.compare_types(rhs),
300 (lhs, Type::OneOf(rhs_oneof)) => lhs.compare_types(rhs_oneof),
301
302 (t, u) if t == u => Some(TypeRelation::Equal),
303
304 _ => None,
305 }
306 }
307
308 fn is_subtype_of(&self, other: &Self) -> bool {
315 matches!(
316 self.compare_types(other),
317 Some(TypeRelation::Subtype | TypeRelation::Equal)
318 )
319 }
320
321 fn is_any(&self) -> bool {
322 matches!(self, Type::Any)
323 }
324
325 fn is_assignable_to(&self, dst: &Self) -> bool {
326 let src = self;
327 match (dst, src) {
328 (Type::Table(dst_cols), Type::List(src_ty)) => match src_ty.as_ref() {
329 Type::Any => true,
330 Type::Record(src_cols) => src_cols.is_assignable_to(dst_cols),
331 src_ty => src_ty.is_assignable_to(&Type::Record(dst_cols.clone())),
332 },
333 (Type::List(dst_ty), Type::Table(src_cols)) => match dst_ty.as_ref() {
334 Type::Any => true,
335 Type::Record(dst_cols) => src_cols.is_assignable_to(dst_cols),
336 dst_ty => Type::Record(src_cols.clone()).is_assignable_to(dst_ty),
337 },
338 (Type::List(dst_ty), Type::List(src_ty)) => src_ty.is_assignable_to(dst_ty.as_ref()),
339 (Type::Record(dst_cols), Type::Record(src_cols))
340 | (Type::Table(dst_cols), Type::Table(src_cols)) => src_cols.is_assignable_to(dst_cols),
341 (Type::Glob, Type::String) => true,
343 (Type::String, Type::Glob) => false,
345 (Type::OneOf(dst_tys), Type::OneOf(src_tys)) => src_tys.is_assignable_to(dst_tys),
347 (Type::OneOf(dst_tys), src_ty) => src_ty.is_assignable_to(dst_tys),
348 (dst_ty, Type::OneOf(src_tys)) => src_tys.is_assignable_to(dst_ty),
349 (Type::List(_) | Type::Table(_) | Type::Record(_), Type::Custom(_)) => true,
351 (lhs, rhs @ Type::CellPath) => rhs.is_subtype_of(lhs),
352 (lhs, rhs) => rhs.compare_types(lhs).is_some(),
353 }
354 }
355}
356
357impl TypeSet for Type {
358 fn union(self, other: Self) -> Self {
359 let (lhs, rhs) = match Self::flat_widen(self, other) {
360 Ok(t) => return t,
361 Err(tys) => tys,
362 };
363
364 match (lhs, rhs) {
365 (Type::OneOf(ts), Type::OneOf(us)) => Type::OneOf(ts.union(us)),
366 (Type::OneOf(oneof), t) | (t, Type::OneOf(oneof)) => Type::OneOf(oneof.add_ty(t)),
367 (this, other) => Type::one_of([this, other]),
368 }
369 }
370}
371
372impl Display for Type {
373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374 match self {
375 Type::Block => write!(f, "block"),
376 Type::Closure => write!(f, "closure"),
377 Type::Bool => write!(f, "bool"),
378 Type::CellPath => write!(f, "cell-path"),
379 Type::Date => write!(f, "datetime"),
380 Type::Duration => write!(f, "duration"),
381 Type::Filesize => write!(f, "filesize"),
382 Type::Float => write!(f, "float"),
383 Type::Int => write!(f, "int"),
384 Type::Range => write!(f, "range"),
385 Type::Record(columns) => write!(f, "record{columns}"),
386 Type::Table(columns) => write!(f, "table{columns}"),
387 Type::List(l) => write!(f, "list<{l}>"),
388 Type::Nothing => write!(f, "nothing"),
389 Type::Number => write!(f, "number"),
390 Type::OneOf(oneof) => write!(f, "{oneof}"),
391 Type::String => write!(f, "string"),
392 Type::Any => write!(f, "any"),
393 Type::Error => write!(f, "error"),
394 Type::Binary => write!(f, "binary"),
395 Type::Custom(custom) => write!(f, "{custom}"),
396 Type::Glob => write!(f, "glob"),
397 }
398 }
399}
400
401pub fn combined_type_string<'a, I>(types: I, join_word: &str) -> Option<String>
405where
406 I: IntoIterator<Item = &'a Type>,
407{
408 use std::fmt::Write as _;
409
410 let mut seen = Vec::new();
413 for t in types {
414 if !seen.contains(t) {
415 seen.push(t.clone());
416 }
417 }
418
419 match seen.as_slice() {
420 [] => None,
421 [one] => Some(one.to_string()),
422 [one, two] => Some(format!("{one} {join_word} {two}")),
423 [initial @ .., last] => {
424 let mut out = String::new();
425 for ele in initial {
426 let _ = write!(out, "{ele}, ");
427 }
428 let _ = write!(out, "{join_word} {last}");
429 Some(out)
430 }
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437 use strum::IntoEnumIterator;
438
439 mod subtype_relation {
440 use super::*;
441
442 #[test]
443 fn test_reflexivity() {
444 for ty in Type::iter() {
445 assert!(ty.is_subtype_of(&ty));
446 }
447 }
448
449 #[test]
450 fn test_any_is_top_type() {
451 for ty in Type::iter() {
452 assert!(ty.is_subtype_of(&Type::Any));
453 }
454 }
455
456 #[test]
457 fn test_number_supertype() {
458 assert!(Type::Int.is_subtype_of(&Type::Number));
459 assert!(Type::Float.is_subtype_of(&Type::Number));
460 }
461
462 #[test]
463 fn test_list_covariance() {
464 for ty1 in Type::iter() {
465 for ty2 in Type::iter() {
466 let list_ty1 = Type::List(Box::new(ty1.clone()));
467 let list_ty2 = Type::List(Box::new(ty2.clone()));
468 assert_eq!(list_ty1.is_subtype_of(&list_ty2), ty1.is_subtype_of(&ty2));
469 }
470 }
471 }
472
473 #[test]
474 fn table_list_oneof_covariance() {
475 let columns = CollectionColumns::<Type>::default();
476
477 let single_ty = Type::Record(columns.clone());
478 let union_ty = Type::one_of([Type::Number, Type::Record(columns.clone())]);
479
480 let direct_cmp = union_ty.compare_types(&single_ty);
481 let list_cmp = Type::list(union_ty.clone()).compare_types(&Type::list(single_ty));
482
483 let table_cmp = Type::list(union_ty).compare_types(&Type::Table(columns));
484
485 assert_eq!(list_cmp, direct_cmp);
486 assert_eq!(table_cmp, list_cmp);
487 }
488 }
489
490 mod oneof {
491 use super::*;
492
493 #[test]
494 fn oneof_lhs() {
495 let rel = Type::one_of([Type::Int, Type::Nothing]).compare_types(&Type::Int);
496 assert_eq!(rel, Some(TypeRelation::Supertype));
497 }
498
499 #[test]
500 fn oneof_rhs() {
501 let rel = Type::Int.compare_types(&Type::one_of([Type::Int, Type::Nothing]));
502 assert_eq!(rel, Some(TypeRelation::Subtype));
503 }
504 }
505
506 mod oneof_flattening {
507 use super::*;
508
509 #[test]
510 fn test_oneof_creation_flattens() {
511 let nested = Type::one_of([
512 Type::String,
513 Type::one_of([Type::Int, Type::Float]),
514 Type::Bool,
515 ]);
516 if let Type::OneOf(oneof) = nested {
517 let types_vec: Vec<Type> = oneof.into_iter().collect();
518 assert_eq!(types_vec.len(), 3);
519 assert!(types_vec.contains(&Type::String));
520 assert!(types_vec.contains(&Type::Number));
521 assert!(types_vec.contains(&Type::Bool));
522 } else {
523 panic!("Expected OneOf");
524 }
525 }
526
527 #[test]
528 fn test_widen_flattens_oneof() {
529 let a = Type::one_of([Type::String, Type::Int]);
530 let b = Type::one_of([Type::Float, Type::Bool]);
531 let widened = a.union(b);
532 if let Type::OneOf(oneof) = widened {
533 let types_vec: Vec<Type> = oneof.into_iter().collect();
534 assert_eq!(types_vec.len(), 3);
535 assert!(types_vec.contains(&Type::String));
536 assert!(types_vec.contains(&Type::Number)); assert!(types_vec.contains(&Type::Bool));
538 } else {
539 panic!("Expected OneOf");
540 }
541 }
542
543 #[test]
544 fn test_oneof_deduplicates() {
545 let record_type =
546 Type::Record(vec![("content".to_string(), Type::list(Type::String))].into());
547 let oneof = Type::one_of([Type::String, record_type.clone(), record_type.clone()]);
548 if let Type::OneOf(oneof) = oneof {
549 let types_vec: Vec<Type> = oneof.into_iter().collect();
550 assert_eq!(types_vec.len(), 2);
551 assert!(types_vec.contains(&Type::String));
552 assert!(types_vec.contains(&record_type));
553 } else {
554 panic!("Expected OneOf");
555 }
556 }
557 }
558
559 mod widen_shortcuts {
561 use super::*;
562
563 #[test]
564 fn test_widen_subtype_shortcut() {
565 let union = Type::one_of([Type::String, Type::Number]);
567 let result = union.clone().union(Type::Int);
568 assert_eq!(result, union);
569
570 let union2 = Type::one_of([Type::Int, Type::String]);
572 let result2 = Type::Int.union(union2.clone());
573 assert_eq!(result2, union2);
574 }
575
576 #[test]
577 fn test_chain_shortcut() {
578 let mut t = Type::String;
580 for _ in 0..100 {
581 t = t.union(Type::Int);
582 }
583 let expected = Type::one_of([Type::String, Type::Int]);
584 assert_eq!(t, expected);
585 }
586
587 #[test]
588 fn test_list_table_widen_preserves_list() {
589 let list_record = Type::list(Type::Record(vec![("a".to_string(), Type::Int)].into()));
590 let table = Type::Table(vec![("a".to_string(), Type::Int)].into());
591
592 let widened = list_record.clone().union(table.clone());
593 let expected = Type::one_of([list_record, table]);
594
595 assert_eq!(widened, expected);
596 }
597
598 #[test]
599 fn test_glob_string_union() {
600 let g = Type::Glob;
601 let s = Type::String;
602 let w1 = g.clone().union(s.clone());
603 let w2 = s.clone().union(g.clone());
604 let expected1 = Type::one_of([Type::Glob, Type::String]);
605 let expected2 = Type::one_of([Type::String, Type::Glob]);
606 assert_eq!(w1, expected1);
607 assert_eq!(w2, expected2);
608 }
609 }
610}