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