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