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