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