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