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