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