Skip to main content

aver/ast/
types.rs

1//! Aver static type representation.
2//!
3//! Lives under `crate::ast` so that `Spanned<T>` can carry an optional
4//! `Type` annotation without forming a cycle through `crate::types`
5//! (which depends on `crate::ast`). The original `crate::types::Type`
6//! is re-exported from here for backward compatibility.
7
8use crate::ir::TypeId;
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum Type {
12    Int,
13    Float,
14    Str,
15    Bool,
16    Unit,
17    Result(Box<Type>, Box<Type>),
18    Option(Box<Type>),
19    List(Box<Type>),
20    Tuple(Vec<Type>),
21    Map(Box<Type>, Box<Type>),
22    Vector(Box<Type>),
23    Fn(Vec<Type>, Box<Type>, Vec<String>),
24    Var(String), // named type variable in polymorphic builtin signatures (instantiated at call site)
25    Invalid, // checker recovery after an earlier error; matches anything in `.compatible` to suppress cascading diagnostics
26    /// User-defined or builtin named type. The `id` is `Some` once the
27    /// typechecker has resolved the reference against the program's
28    /// [`crate::ir::SymbolTable`] (#138 phase B). The `id` is `None` for
29    /// transient parser output (before typecheck has run), for builtin
30    /// record types (`HttpResponse`, `Header`, `Tcp.Connection`,
31    /// `Buffer`, …) that aren't registered in the user-program symbol
32    /// table, and for stamps the checker couldn't resolve.
33    ///
34    /// Identity:
35    /// - two `Named` with both `id = Some` compare by `id` (typed
36    ///   identity — cross-module same-bare-name types stay distinct);
37    /// - otherwise fall back to source-faithful name comparison, with
38    ///   the historical suffix tolerance for `Bare` vs `Module.Bare`.
39    Named {
40        id: Option<TypeId>,
41        name: String,
42    },
43}
44
45impl Type {
46    /// Build a `Type::Named` whose identity has not been resolved
47    /// against a `SymbolTable` (parser output, builtins, in-flight
48    /// stamps). Equivalent to `Named { id: None, name: name.into() }`.
49    pub fn named(name: impl Into<String>) -> Self {
50        Self::Named {
51            id: None,
52            name: name.into(),
53        }
54    }
55
56    /// Build a `Type::Named` resolved against the program's
57    /// `SymbolTable`. Caller is responsible for ensuring `name` is the
58    /// canonical form (`symbol_table.type_entry(id).key.canonical()`).
59    pub fn named_resolved(id: TypeId, name: impl Into<String>) -> Self {
60        Self::Named {
61            id: Some(id),
62            name: name.into(),
63        }
64    }
65
66    /// Source-faithful name of a `Named` (`"Shape"` / `"A.Shape"` /
67    /// `"HttpResponse"`); `None` for any non-`Named` variant.
68    pub fn named_name(&self) -> Option<&str> {
69        match self {
70            Type::Named { name, .. } => Some(name.as_str()),
71            _ => None,
72        }
73    }
74
75    /// Resolved `TypeId` of a `Named`, if any. `None` for unresolved
76    /// references and for any non-`Named` variant.
77    pub fn named_id(&self) -> Option<TypeId> {
78        match self {
79            Type::Named { id, .. } => *id,
80            _ => None,
81        }
82    }
83
84    /// `a.compatible(b)` — can a value of type `self` be used where `other` is expected?
85    /// Two concrete types must be equal (structurally) to be compatible. Type variables
86    /// are resolved by the type checker at call sites, not by this raw relation.
87    ///
88    /// Iron — A4: `Type::Invalid` is the checker's "already-errored"
89    /// sentinel and matches anything. Without this, a single bad
90    /// expression would fan its `Invalid` type out through every
91    /// downstream `.compatible(...)` check and produce a chain of
92    /// duplicate diagnostics.
93    pub fn compatible(&self, other: &Type) -> bool {
94        match (self, other) {
95            (Type::Invalid, _) | (_, Type::Invalid) => true,
96            (Type::Int, Type::Int) => true,
97            (Type::Float, Type::Float) => true,
98            (Type::Str, Type::Str) => true,
99            (Type::Bool, Type::Bool) => true,
100            (Type::Unit, Type::Unit) => true,
101            (Type::Var(a), Type::Var(b)) => a == b,
102            (Type::Result(a1, b1), Type::Result(a2, b2)) => a1.compatible(a2) && b1.compatible(b2),
103            (Type::Option(a), Type::Option(b)) => a.compatible(b),
104            (Type::List(a), Type::List(b)) => a.compatible(b),
105            (Type::Tuple(a), Type::Tuple(b)) => {
106                a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.compatible(y))
107            }
108            (Type::Map(k1, v1), Type::Map(k2, v2)) => k1.compatible(k2) && v1.compatible(v2),
109            (Type::Vector(a), Type::Vector(b)) => a.compatible(b),
110            (Type::Fn(p1, r1, e1), Type::Fn(p2, r2, e2)) => {
111                p1.len() == p2.len()
112                    && p1.iter().zip(p2.iter()).all(|(a, b)| a.compatible(b))
113                    && r1.compatible(r2)
114                    && e1.iter().all(|actual| {
115                        e2.iter()
116                            .any(|expected| crate::effects::effect_satisfies(expected, actual))
117                    })
118            }
119            (
120                Type::Named {
121                    id: id_a,
122                    name: name_a,
123                },
124                Type::Named {
125                    id: id_b,
126                    name: name_b,
127                },
128            ) => match (id_a, id_b) {
129                // Both sides resolved against the symbol table: typed
130                // identity is the load-bearing comparison. Two distinct
131                // `TypeId`s never compare equal even when their source
132                // names happen to coincide (cross-module `Shape` —
133                // distinct `TypeId`, must stay incompatible).
134                (Some(a), Some(b)) => a == b,
135                // Exactly one side carries a `TypeId`: the typechecker
136                // already classified that side; the other side
137                // attempted resolution and came up empty. Always
138                // reject — silent name fallback here was the round-6
139                // entry-fallback bug, where a dep module's
140                // unresolved bare `Shape` silently bound to the
141                // entry module's own `Shape`. Builtins like
142                // `HttpResponse` never set `id` on either side, so
143                // they exercise the `(None, None)` branch below;
144                // genuine cross-module typed/raw mixes hit this
145                // branch and must fail.
146                (Some(_), None) | (None, Some(_)) => false,
147                // Both sides unresolved (raw stamps, builtin records,
148                // tests). Keep the historical suffix relation as a
149                // best-effort match — there's no typed identity to
150                // disagree with on either side.
151                (None, None) => {
152                    name_a == name_b
153                        || name_a.ends_with(&format!(".{}", name_b))
154                        || name_b.ends_with(&format!(".{}", name_a))
155                }
156            },
157            _ => false,
158        }
159    }
160
161    pub fn display(&self) -> String {
162        match self {
163            Type::Int => "Int".to_string(),
164            Type::Float => "Float".to_string(),
165            Type::Str => "String".to_string(),
166            Type::Bool => "Bool".to_string(),
167            Type::Unit => "Unit".to_string(),
168            Type::Result(ok, err) => format!("Result<{}, {}>", ok.display(), err.display()),
169            Type::Option(inner) => format!("Option<{}>", inner.display()),
170            Type::List(inner) => format!("List<{}>", inner.display()),
171            Type::Tuple(items) => format!(
172                "Tuple<{}>",
173                items
174                    .iter()
175                    .map(Type::display)
176                    .collect::<Vec<_>>()
177                    .join(", ")
178            ),
179            Type::Map(key, value) => format!("Map<{}, {}>", key.display(), value.display()),
180            Type::Vector(inner) => format!("Vector<{}>", inner.display()),
181            Type::Fn(params, ret, effects) => {
182                let ps: Vec<String> = params.iter().map(|p| p.display()).collect();
183                if effects.is_empty() {
184                    format!("Fn({}) -> {}", ps.join(", "), ret.display())
185                } else {
186                    format!(
187                        "Fn({}) -> {} ! [{}]",
188                        ps.join(", "),
189                        ret.display(),
190                        effects.join(", ")
191                    )
192                }
193            }
194            Type::Var(name) => name.clone(),
195            Type::Invalid => "Invalid".to_string(),
196            Type::Named { name, .. } => name.clone(),
197        }
198    }
199}