Skip to main content

aver/types/
mod.rs

1/// Aver static type representation and built-in type namespaces.
2///
3/// Type annotations in the AST are plain strings; this module converts them
4/// to a structured enum and provides the compatibility relation used by
5/// the type checker.
6///
7/// Sub-modules:
8/// - `checker` — static type checker
9/// - `bool`, `int`, `float`, `string`, `list`, `map`, `char`, `byte` — pure namespace helpers (no effects)
10pub 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
30// `Type` lives in `crate::ast::types` so that `Spanned<T>` can carry an
31// optional `Type` annotation without a cycle through `crate::types`. This
32// re-export preserves the historical `crate::types::Type` path.
33pub use crate::ast::Type;
34
35/// Parse a type annotation string strictly.
36/// Returns `Err(unknown_name)` if the string is a non-empty identifier
37/// that does not map to a known type (i.e. a likely typo).
38/// Generic forms (`Result<...>`, `Option<...>`, `List<...>`) with valid inner types are accepted.
39pub 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            // Capitalized identifier with only alphanumeric/_ and dot chars = user-defined type name
101            // Supports dotted names like "Tcp.Connection"
102            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
114/// Parse an Aver type annotation string into a `Type`.
115/// Returns `Type::Invalid` for malformed or unknown type strings (internal recovery).
116/// Prefer `parse_type_str_strict` for user-facing type annotations.
117pub 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            // Try generic forms: Result<A, B>, Option<A>, List<A>
143            if let Some(inner) = strip_wrapper(s, "Result<", ">") {
144                // Split on the first top-level comma
145                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            // Capitalized identifier with only alphanumeric/_ and dot chars = user-defined type
170            // Supports dotted names like "Tcp.Connection"
171            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            // Invalid — internal recovery fallback
179            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
342/// If `s` starts with `prefix` and ends with `suffix`, return the middle part.
343fn 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
352/// Split a string on the first top-level comma (depth=0), returning the two sides.
353fn 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        // Capitalized identifiers are now parsed as user-defined Named types
419        assert_eq!(
420            parse_type_str("SomeUnknownType"),
421            Type::Named("SomeUnknownType".to_string())
422        );
423        // Lowercase non-keyword identifiers and empty strings become invalid recovery.
424        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)); // no implicit widening
434        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        // Http.get fits where Http is expected (subset of namespace)
487        assert!(child.compatible(&parent));
488        // Http does NOT fit where Http.get is expected (might use Http.post)
489        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        // Capitalized identifiers are accepted as user-defined Named types
515        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        // Lowercase unknown types still fail
531        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        // List/User-type keys are now valid — runtime hashes deeply.
559        assert!(parse_type_str_strict("Map<List<Int>, String>").is_ok());
560        // Functions still cannot be hashed.
561        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}