1pub mod bool;
11pub mod byte;
12pub mod char;
13pub mod checker;
14pub mod float;
15pub mod int;
16#[cfg(feature = "runtime")]
17pub mod list;
18#[cfg(feature = "runtime")]
19pub mod map;
20pub mod option;
21pub mod result;
22#[cfg(feature = "runtime")]
23pub mod string;
24#[cfg(feature = "runtime")]
25pub mod vector;
26
27#[derive(Debug, Clone, PartialEq)]
28pub enum Type {
29 Int,
30 Float,
31 Str,
32 Bool,
33 Unit,
34 Result(Box<Type>, Box<Type>),
35 Option(Box<Type>),
36 List(Box<Type>),
37 Tuple(Vec<Type>),
38 Map(Box<Type>, Box<Type>),
39 Vector(Box<Type>),
40 Fn(Vec<Type>, Box<Type>, Vec<String>),
41 Unknown, Named(String), }
44
45impl Type {
46 pub fn compatible(&self, other: &Type) -> bool {
50 if matches!(self, Type::Unknown) || matches!(other, Type::Unknown) {
51 return true;
52 }
53 match (self, other) {
54 (Type::Int, Type::Int) => true,
55 (Type::Float, Type::Float) => true,
56 (Type::Str, Type::Str) => true,
57 (Type::Bool, Type::Bool) => true,
58 (Type::Unit, Type::Unit) => true,
59 (Type::Result(a1, b1), Type::Result(a2, b2)) => a1.compatible(a2) && b1.compatible(b2),
60 (Type::Option(a), Type::Option(b)) => a.compatible(b),
61 (Type::List(a), Type::List(b)) => a.compatible(b),
62 (Type::Tuple(a), Type::Tuple(b)) => {
63 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.compatible(y))
64 }
65 (Type::Map(k1, v1), Type::Map(k2, v2)) => k1.compatible(k2) && v1.compatible(v2),
66 (Type::Vector(a), Type::Vector(b)) => a.compatible(b),
67 (Type::Fn(p1, r1, e1), Type::Fn(p2, r2, e2)) => {
68 p1.len() == p2.len()
69 && p1.iter().zip(p2.iter()).all(|(a, b)| a.compatible(b))
70 && r1.compatible(r2)
71 && e1.iter().all(|actual| {
72 e2.iter()
73 .any(|expected| crate::effects::effect_satisfies(expected, actual))
74 })
75 }
76 (Type::Named(a), Type::Named(b)) => {
77 a == b || a.ends_with(&format!(".{}", b)) || b.ends_with(&format!(".{}", a))
78 }
79 _ => false,
80 }
81 }
82
83 pub fn display(&self) -> String {
84 match self {
85 Type::Int => "Int".to_string(),
86 Type::Float => "Float".to_string(),
87 Type::Str => "String".to_string(),
88 Type::Bool => "Bool".to_string(),
89 Type::Unit => "Unit".to_string(),
90 Type::Result(ok, err) => format!("Result<{}, {}>", ok.display(), err.display()),
91 Type::Option(inner) => format!("Option<{}>", inner.display()),
92 Type::List(inner) => format!("List<{}>", inner.display()),
93 Type::Tuple(items) => format!(
94 "({})",
95 items
96 .iter()
97 .map(Type::display)
98 .collect::<Vec<_>>()
99 .join(", ")
100 ),
101 Type::Map(key, value) => format!("Map<{}, {}>", key.display(), value.display()),
102 Type::Vector(inner) => format!("Vector<{}>", inner.display()),
103 Type::Fn(params, ret, effects) => {
104 let ps: Vec<String> = params.iter().map(|p| p.display()).collect();
105 if effects.is_empty() {
106 format!("Fn({}) -> {}", ps.join(", "), ret.display())
107 } else {
108 format!(
109 "Fn({}) -> {} ! [{}]",
110 ps.join(", "),
111 ret.display(),
112 effects.join(", ")
113 )
114 }
115 }
116 Type::Unknown => "Unknown".to_string(),
117 Type::Named(n) => n.clone(),
118 }
119 }
120}
121
122pub fn parse_type_str_strict(s: &str) -> Result<Type, String> {
127 let s = s.trim();
128 if s.is_empty() || s == "Any" {
129 return Err(s.to_string());
130 }
131 if let Some(fn_ty) = parse_fn_type_strict(s)? {
132 return Ok(fn_ty);
133 }
134
135 if s.starts_with('(') && s.ends_with(')') {
136 let inner = &s[1..s.len() - 1];
137 let parts = split_top_level(inner, ',')?;
138 if parts.len() < 2 {
139 return Err(s.to_string());
140 }
141 let elems = parts
142 .into_iter()
143 .map(parse_type_str_strict)
144 .collect::<Result<Vec<_>, _>>()?;
145 return Ok(Type::Tuple(elems));
146 }
147
148 match s {
149 "Int" => Ok(Type::Int),
150 "Float" => Ok(Type::Float),
151 "String" | "Str" => Ok(Type::Str),
152 "Bool" => Ok(Type::Bool),
153 "Unit" => Ok(Type::Unit),
154 _ => {
155 if let Some(inner) = strip_wrapper(s, "Result<", ">") {
156 if let Some((ok_s, err_s)) = split_top_level_comma(inner) {
157 let ok_ty = parse_type_str_strict(ok_s)?;
158 let err_ty = parse_type_str_strict(err_s)?;
159 return Ok(Type::Result(Box::new(ok_ty), Box::new(err_ty)));
160 }
161 return Err(s.to_string());
162 }
163 if let Some(inner) = strip_wrapper(s, "Option<", ">") {
164 let inner_ty = parse_type_str_strict(inner)?;
165 return Ok(Type::Option(Box::new(inner_ty)));
166 }
167 if let Some(inner) = strip_wrapper(s, "List<", ">") {
168 let inner_ty = parse_type_str_strict(inner)?;
169 return Ok(Type::List(Box::new(inner_ty)));
170 }
171 if let Some(inner) = strip_wrapper(s, "Map<", ">") {
172 if let Some((key_s, value_s)) = split_top_level_comma(inner) {
173 let key_ty = parse_type_str_strict(key_s)?;
174 if !matches!(key_ty, Type::Int | Type::Float | Type::Str | Type::Bool) {
175 return Err(s.to_string());
176 }
177 let value_ty = parse_type_str_strict(value_s)?;
178 return Ok(Type::Map(Box::new(key_ty), Box::new(value_ty)));
179 }
180 return Err(s.to_string());
181 }
182 if let Some(inner) = strip_wrapper(s, "Vector<", ">") {
183 let inner_ty = parse_type_str_strict(inner)?;
184 return Ok(Type::Vector(Box::new(inner_ty)));
185 }
186
187 if s.chars().next().is_some_and(|c| c.is_uppercase())
190 && s.chars()
191 .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
192 {
193 return Ok(Type::Named(s.to_string()));
194 }
195
196 Err(s.to_string())
197 }
198 }
199}
200
201pub fn parse_type_str(s: &str) -> Type {
205 let s = s.trim();
206 if s.starts_with("Fn(") {
207 if let Ok(Some(fn_ty)) = parse_fn_type_strict(s) {
208 return fn_ty;
209 }
210 return Type::Unknown;
211 }
212 if s.starts_with('(') && s.ends_with(')') {
213 let inner = &s[1..s.len() - 1];
214 if let Ok(parts) = split_top_level(inner, ',')
215 && parts.len() >= 2
216 {
217 return Type::Tuple(parts.into_iter().map(parse_type_str).collect());
218 }
219 return Type::Unknown;
220 }
221 match s {
222 "Int" => Type::Int,
223 "Float" => Type::Float,
224 "String" | "Str" => Type::Str,
225 "Bool" => Type::Bool,
226 "Unit" => Type::Unit,
227 "" => Type::Unknown,
228 _ => {
229 if let Some(inner) = strip_wrapper(s, "Result<", ">") {
231 if let Some((ok_str, err_str)) = split_top_level_comma(inner) {
233 return Type::Result(
234 Box::new(parse_type_str(ok_str)),
235 Box::new(parse_type_str(err_str)),
236 );
237 }
238 }
239 if let Some(inner) = strip_wrapper(s, "Option<", ">") {
240 return Type::Option(Box::new(parse_type_str(inner)));
241 }
242 if let Some(inner) = strip_wrapper(s, "List<", ">") {
243 return Type::List(Box::new(parse_type_str(inner)));
244 }
245 if let Some(inner) = strip_wrapper(s, "Map<", ">")
246 && let Some((key_str, value_str)) = split_top_level_comma(inner)
247 {
248 return Type::Map(
249 Box::new(parse_type_str(key_str)),
250 Box::new(parse_type_str(value_str)),
251 );
252 }
253 if let Some(inner) = strip_wrapper(s, "Vector<", ">") {
254 return Type::Vector(Box::new(parse_type_str(inner)));
255 }
256 if s.chars().next().is_some_and(|c| c.is_uppercase())
259 && s.chars()
260 .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
261 && s != "Any"
262 {
263 return Type::Named(s.to_string());
264 }
265 Type::Unknown
267 }
268 }
269}
270
271fn parse_fn_type_strict(s: &str) -> Result<Option<Type>, String> {
272 if !s.starts_with("Fn(") {
273 return Ok(None);
274 }
275
276 let close_idx = find_matching_paren(s, 2).ok_or_else(|| s.to_string())?;
277 let params_src = &s[3..close_idx];
278
279 let after_params = s[close_idx + 1..].trim_start();
280 if !after_params.starts_with("->") {
281 return Err(s.to_string());
282 }
283 let ret_and_effects = after_params[2..].trim();
284 if ret_and_effects.is_empty() {
285 return Err(s.to_string());
286 }
287
288 let (ret_src, effects) = split_fn_effects_suffix(ret_and_effects)?;
289 let ret_ty = parse_type_str_strict(ret_src)?;
290 let params = parse_type_list_strict(params_src)?;
291 Ok(Some(Type::Fn(params, Box::new(ret_ty), effects)))
292}
293
294fn parse_type_list_strict(src: &str) -> Result<Vec<Type>, String> {
295 if src.trim().is_empty() {
296 return Ok(vec![]);
297 }
298 split_top_level(src, ',')?
299 .into_iter()
300 .map(|part| {
301 let part = part.trim();
302 if part.is_empty() {
303 Err(src.to_string())
304 } else {
305 parse_type_str_strict(part)
306 }
307 })
308 .collect()
309}
310
311fn split_fn_effects_suffix(src: &str) -> Result<(&str, Vec<String>), String> {
312 if let Some(bang_idx) = find_top_level_bang(src) {
313 let ret_src = src[..bang_idx].trim();
314 if ret_src.is_empty() {
315 return Err(src.to_string());
316 }
317 let effects_src = src[bang_idx + 1..].trim();
318 if !(effects_src.starts_with('[') && effects_src.ends_with(']')) {
319 return Err(src.to_string());
320 }
321 let inner = &effects_src[1..effects_src.len() - 1];
322 let effects = if inner.trim().is_empty() {
323 vec![]
324 } else {
325 split_top_level(inner, ',')?
326 .into_iter()
327 .map(|part| {
328 let name = part.trim();
329 if name.is_empty() {
330 Err(src.to_string())
331 } else {
332 Ok(name.to_string())
333 }
334 })
335 .collect::<Result<Vec<_>, _>>()?
336 };
337 Ok((ret_src, effects))
338 } else {
339 Ok((src.trim(), vec![]))
340 }
341}
342
343fn find_matching_paren(s: &str, open_idx: usize) -> Option<usize> {
344 if s.as_bytes().get(open_idx).copied() != Some(b'(') {
345 return None;
346 }
347 let mut depth = 1usize;
348 for (i, ch) in s.char_indices().skip(open_idx + 1) {
349 match ch {
350 '(' => depth += 1,
351 ')' => {
352 depth -= 1;
353 if depth == 0 {
354 return Some(i);
355 }
356 }
357 _ => {}
358 }
359 }
360 None
361}
362
363fn find_top_level_bang(s: &str) -> Option<usize> {
364 let mut angle = 0usize;
365 let mut paren = 0usize;
366 let mut bracket = 0usize;
367
368 for (i, ch) in s.char_indices() {
369 match ch {
370 '<' => angle += 1,
371 '>' => angle = angle.saturating_sub(1),
372 '(' => paren += 1,
373 ')' => paren = paren.saturating_sub(1),
374 '[' => bracket += 1,
375 ']' => bracket = bracket.saturating_sub(1),
376 '!' if angle == 0 && paren == 0 && bracket == 0 => return Some(i),
377 _ => {}
378 }
379 }
380
381 None
382}
383
384fn split_top_level(s: &str, delimiter: char) -> Result<Vec<&str>, String> {
385 let mut out = Vec::new();
386 let mut start = 0usize;
387 let mut angle = 0usize;
388 let mut paren = 0usize;
389 let mut bracket = 0usize;
390
391 for (i, ch) in s.char_indices() {
392 match ch {
393 '<' => angle += 1,
394 '>' => {
395 if angle == 0 {
396 return Err(s.to_string());
397 }
398 angle -= 1;
399 }
400 '(' => paren += 1,
401 ')' => {
402 if paren == 0 {
403 return Err(s.to_string());
404 }
405 paren -= 1;
406 }
407 '[' => bracket += 1,
408 ']' => {
409 if bracket == 0 {
410 return Err(s.to_string());
411 }
412 bracket -= 1;
413 }
414 _ if ch == delimiter && angle == 0 && paren == 0 && bracket == 0 => {
415 out.push(&s[start..i]);
416 start = i + ch.len_utf8();
417 }
418 _ => {}
419 }
420 }
421
422 if angle != 0 || paren != 0 || bracket != 0 {
423 return Err(s.to_string());
424 }
425 out.push(&s[start..]);
426 Ok(out)
427}
428
429fn strip_wrapper<'a>(s: &'a str, prefix: &str, suffix: &str) -> Option<&'a str> {
431 if s.starts_with(prefix) && s.ends_with(suffix) {
432 let inner = &s[prefix.len()..s.len() - suffix.len()];
433 Some(inner)
434 } else {
435 None
436 }
437}
438
439fn split_top_level_comma(s: &str) -> Option<(&str, &str)> {
441 let mut angle = 0usize;
442 let mut paren = 0usize;
443 let mut bracket = 0usize;
444 for (i, ch) in s.char_indices() {
445 match ch {
446 '<' => angle += 1,
447 '>' => angle = angle.saturating_sub(1),
448 '(' => paren += 1,
449 ')' => paren = paren.saturating_sub(1),
450 '[' => bracket += 1,
451 ']' => bracket = bracket.saturating_sub(1),
452 ',' if angle == 0 && paren == 0 && bracket == 0 => {
453 return Some((&s[..i], &s[i + 1..]));
454 }
455 _ => {}
456 }
457 }
458 None
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 #[test]
466 fn test_primitives() {
467 assert_eq!(parse_type_str("Int"), Type::Int);
468 assert_eq!(parse_type_str("Float"), Type::Float);
469 assert_eq!(parse_type_str("String"), Type::Str);
470 assert_eq!(parse_type_str("Bool"), Type::Bool);
471 assert_eq!(parse_type_str("Unit"), Type::Unit);
472 }
473
474 #[test]
475 fn test_generics() {
476 assert_eq!(
477 parse_type_str("Result<Int, String>"),
478 Type::Result(Box::new(Type::Int), Box::new(Type::Str))
479 );
480 assert_eq!(
481 parse_type_str("Option<Bool>"),
482 Type::Option(Box::new(Type::Bool))
483 );
484 assert_eq!(parse_type_str("List<Int>"), Type::List(Box::new(Type::Int)));
485 assert_eq!(
486 parse_type_str("Map<String, Int>"),
487 Type::Map(Box::new(Type::Str), Box::new(Type::Int))
488 );
489 assert_eq!(
490 parse_type_str("(Int, String)"),
491 Type::Tuple(vec![Type::Int, Type::Str])
492 );
493 }
494
495 #[test]
496 fn test_nested() {
497 assert_eq!(
498 parse_type_str("Result<Float, String>"),
499 Type::Result(Box::new(Type::Float), Box::new(Type::Str))
500 );
501 }
502
503 #[test]
504 fn test_unknown() {
505 assert_eq!(
507 parse_type_str("SomeUnknownType"),
508 Type::Named("SomeUnknownType".to_string())
509 );
510 assert_eq!(parse_type_str(""), Type::Unknown);
512 }
513
514 #[test]
515 fn test_compatible() {
516 assert!(Type::Int.compatible(&Type::Int));
517 assert!(!Type::Int.compatible(&Type::Str));
518 assert!(Type::Unknown.compatible(&Type::Int));
519 assert!(Type::Int.compatible(&Type::Unknown));
520 assert!(!Type::Int.compatible(&Type::Float)); assert!(
522 Type::Result(Box::new(Type::Int), Box::new(Type::Str))
523 .compatible(&Type::Result(Box::new(Type::Int), Box::new(Type::Str)))
524 );
525 assert!(
526 Type::Map(Box::new(Type::Str), Box::new(Type::Int))
527 .compatible(&Type::Map(Box::new(Type::Str), Box::new(Type::Int)))
528 );
529 assert!(
530 !Type::Map(Box::new(Type::Str), Box::new(Type::Int))
531 .compatible(&Type::Map(Box::new(Type::Int), Box::new(Type::Int)))
532 );
533 }
534
535 #[test]
536 fn test_function_type_parsing() {
537 assert_eq!(
538 parse_type_str_strict("Fn(Int, String) -> Bool").unwrap(),
539 Type::Fn(vec![Type::Int, Type::Str], Box::new(Type::Bool), vec![])
540 );
541 assert_eq!(
542 parse_type_str_strict("Fn(Int) -> Int ! [Console]").unwrap(),
543 Type::Fn(
544 vec![Type::Int],
545 Box::new(Type::Int),
546 vec!["Console".to_string()]
547 )
548 );
549 }
550
551 #[test]
552 fn test_function_effect_compatibility_subset() {
553 let pure = Type::Fn(vec![Type::Int], Box::new(Type::Int), vec![]);
554 let console = Type::Fn(
555 vec![Type::Int],
556 Box::new(Type::Int),
557 vec!["Console".to_string()],
558 );
559
560 assert!(pure.compatible(&console));
561 assert!(!console.compatible(&pure));
562
563 let child = Type::Fn(
564 vec![Type::Int],
565 Box::new(Type::Int),
566 vec!["Http.get".to_string()],
567 );
568 let parent = Type::Fn(
569 vec![Type::Int],
570 Box::new(Type::Int),
571 vec!["Http".to_string()],
572 );
573 assert!(child.compatible(&parent));
575 assert!(!parent.compatible(&child));
577 }
578
579 #[test]
580 fn test_strict_parser_accepts_valid_generics() {
581 assert_eq!(
582 parse_type_str_strict("Result<Int, String>").unwrap(),
583 Type::Result(Box::new(Type::Int), Box::new(Type::Str))
584 );
585 assert_eq!(
586 parse_type_str_strict("List<Option<Float>>").unwrap(),
587 Type::List(Box::new(Type::Option(Box::new(Type::Float))))
588 );
589 assert_eq!(
590 parse_type_str_strict("Map<String, Int>").unwrap(),
591 Type::Map(Box::new(Type::Str), Box::new(Type::Int))
592 );
593 assert_eq!(
594 parse_type_str_strict("(Int, String)").unwrap(),
595 Type::Tuple(vec![Type::Int, Type::Str])
596 );
597 }
598
599 #[test]
600 fn test_strict_parser_accepts_user_defined_types() {
601 assert_eq!(
603 parse_type_str_strict("Result<MyError, String>").unwrap(),
604 Type::Result(
605 Box::new(Type::Named("MyError".to_string())),
606 Box::new(Type::Str)
607 )
608 );
609 assert_eq!(
610 parse_type_str_strict("Option<Shape>").unwrap(),
611 Type::Option(Box::new(Type::Named("Shape".to_string())))
612 );
613 assert_eq!(
614 parse_type_str_strict("List<User>").unwrap(),
615 Type::List(Box::new(Type::Named("User".to_string())))
616 );
617 assert!(parse_type_str_strict("integ").is_err());
619 }
620
621 #[test]
622 fn test_dotted_named_type() {
623 assert_eq!(
624 parse_type_str("Tcp.Connection"),
625 Type::Named("Tcp.Connection".to_string())
626 );
627 assert_eq!(
628 parse_type_str_strict("Tcp.Connection").unwrap(),
629 Type::Named("Tcp.Connection".to_string())
630 );
631 assert_eq!(
632 parse_type_str_strict("Result<Tcp.Connection, String>").unwrap(),
633 Type::Result(
634 Box::new(Type::Named("Tcp.Connection".to_string())),
635 Box::new(Type::Str)
636 )
637 );
638 }
639
640 #[test]
641 fn test_strict_parser_rejects_malformed_generics() {
642 assert!(parse_type_str_strict("Result<Int>").is_err());
643 assert!(parse_type_str_strict("Option<Int, String>").is_err());
644 assert!(parse_type_str_strict("Map<Int>").is_err());
645 assert!(parse_type_str_strict("Map<List<Int>, String>").is_err());
646 assert!(parse_type_str_strict("(Int)").is_err());
647 assert!(parse_type_str_strict("Fn(Int) Int").is_err());
648 assert!(parse_type_str_strict("Fn(Int) -> ! [Console]").is_err());
649 }
650}