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