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