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