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