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