Skip to main content

lex_types/
env.rs

1//! Type environment: type-decl info and value-binding scopes.
2
3use crate::types::*;
4use indexmap::IndexMap;
5
6#[derive(Debug, Clone)]
7pub struct TypeDef {
8    pub params: Vec<String>,
9    pub kind: TypeDefKind,
10}
11
12#[derive(Debug, Clone)]
13pub enum TypeDefKind {
14    /// A union: variant name → optional payload.
15    Union(IndexMap<String, Option<Ty>>),
16    /// A record alias: `type Foo = { x :: Int }` etc.
17    Alias(Ty),
18    /// Built-in opaque (Map, Set, ...).
19    Opaque,
20}
21
22#[derive(Debug, Clone, Default)]
23pub struct TypeEnv {
24    /// Type-name → definition.
25    pub types: IndexMap<String, TypeDef>,
26    /// Constructor name → owning type-name.
27    pub ctor_to_type: IndexMap<String, String>,
28}
29
30impl TypeEnv {
31    pub fn new_with_builtins() -> Self {
32        let mut e = TypeEnv::default();
33        // Result[T, E] = Ok(T) | Err(E)
34        let mut r_variants = IndexMap::new();
35        r_variants.insert("Ok".into(), Some(Ty::Var(0))); // T
36        r_variants.insert("Err".into(), Some(Ty::Var(1))); // E
37        e.types.insert("Result".into(), TypeDef {
38            params: vec!["T".into(), "E".into()],
39            kind: TypeDefKind::Union(r_variants),
40        });
41        e.ctor_to_type.insert("Ok".into(), "Result".into());
42        e.ctor_to_type.insert("Err".into(), "Result".into());
43
44        // Option[T] = Some(T) | None
45        let mut o_variants = IndexMap::new();
46        o_variants.insert("Some".into(), Some(Ty::Var(0))); // T
47        o_variants.insert("None".into(), None);
48        e.types.insert("Option".into(), TypeDef {
49            params: vec!["T".into()],
50            kind: TypeDefKind::Union(o_variants),
51        });
52        e.ctor_to_type.insert("Some".into(), "Option".into());
53        e.ctor_to_type.insert("None".into(), "Option".into());
54
55        // Nil = Unit (alias)
56        e.types.insert("Nil".into(), TypeDef {
57            params: vec![],
58            kind: TypeDefKind::Alias(Ty::Unit),
59        });
60
61        // Map, Set: opaque-ish. We just register the names so they parse as Cons.
62        e.types.insert("Map".into(), TypeDef { params: vec!["K".into(), "V".into()], kind: TypeDefKind::Opaque });
63        e.types.insert("Set".into(), TypeDef { params: vec!["T".into()], kind: TypeDefKind::Opaque });
64
65        // Stream[T]: opaque streaming iterator (#305 slice 3).
66        // Built and consumed exclusively through the `stream.*` and
67        // `agent.cloud_stream` effect builtins; the runtime
68        // represents a Stream value as an opaque variant carrying a
69        // handle id. Registered as Opaque so type-checking knows
70        // `Stream[Str]` parses but doesn't unwrap it structurally.
71        e.types.insert("Stream".into(), TypeDef { params: vec!["T".into()], kind: TypeDefKind::Opaque });
72
73        // Tz = Utc | Local | Offset(Int) | Iana(Str).
74        // Used by std.datetime; the variant-typed alternative to the
75        // pre-v1 stringly Tz ("UTC" / "Local" / "+05:30" / IANA name).
76        // Registered globally so users don't have to import a module
77        // to mention `Utc` / `Iana("America/New_York")` etc.
78        let mut tz_variants = IndexMap::new();
79        tz_variants.insert("Utc".into(), None);
80        tz_variants.insert("Local".into(), None);
81        tz_variants.insert("Offset".into(), Some(Ty::int()));
82        tz_variants.insert("Iana".into(), Some(Ty::str()));
83        e.types.insert("Tz".into(), TypeDef {
84            params: vec![],
85            kind: TypeDefKind::Union(tz_variants),
86        });
87        for ctor in &["Utc", "Local", "Offset", "Iana"] {
88            e.ctor_to_type.insert((*ctor).into(), "Tz".into());
89        }
90
91        // HttpError = NetworkError(Str) | TimeoutError | TlsError(Str)
92        //           | DecodeError(Str)
93        // Used by std.http; structured failure shape so callers can
94        // discriminate transport vs. timeout vs. TLS vs. body-decode
95        // errors without parsing strings.
96        let mut http_err_variants = IndexMap::new();
97        http_err_variants.insert("NetworkError".into(), Some(Ty::str()));
98        http_err_variants.insert("TimeoutError".into(), None);
99        http_err_variants.insert("TlsError".into(), Some(Ty::str()));
100        http_err_variants.insert("DecodeError".into(), Some(Ty::str()));
101        e.types.insert("HttpError".into(), TypeDef {
102            params: vec![],
103            kind: TypeDefKind::Union(http_err_variants),
104        });
105        for ctor in &["NetworkError", "TimeoutError", "TlsError", "DecodeError"] {
106            e.ctor_to_type.insert((*ctor).into(), "HttpError".into());
107        }
108
109        // HttpRequest = { method, url, headers, body, timeout_ms }.
110        // The std.http request shape. Anonymous record literals coerce
111        // to this nominal alias at every position (per the §3.13
112        // record-coercion rules), so users write
113        // `{ method: "GET", url: u, headers: map.new(), body: None,
114        // timeout_ms: None }` rather than a dedicated constructor —
115        // builders (`http.with_header` etc.) are pure transforms over
116        // the same shape.
117        let mut req_fields = IndexMap::new();
118        req_fields.insert("method".into(), Ty::str());
119        req_fields.insert("url".into(), Ty::str());
120        req_fields.insert("headers".into(), Ty::Con("Map".into(), vec![Ty::str(), Ty::str()]));
121        req_fields.insert("body".into(), Ty::Con("Option".into(), vec![Ty::bytes()]));
122        req_fields.insert("timeout_ms".into(), Ty::Con("Option".into(), vec![Ty::int()]));
123        e.types.insert("HttpRequest".into(), TypeDef {
124            params: vec![],
125            kind: TypeDefKind::Alias(Ty::Record(req_fields)),
126        });
127
128        // HttpResponse = { status, headers, body }. Returned by every
129        // `http.{send,get,post}` happy path; also the input to
130        // `http.{json_body,text_body}`.
131        let mut resp_fields = IndexMap::new();
132        resp_fields.insert("status".into(), Ty::int());
133        resp_fields.insert("headers".into(), Ty::Con("Map".into(), vec![Ty::str(), Ty::str()]));
134        resp_fields.insert("body".into(), Ty::bytes());
135        e.types.insert("HttpResponse".into(), TypeDef {
136            params: vec![],
137            kind: TypeDefKind::Alias(Ty::Record(resp_fields)),
138        });
139
140        // Matrix = { rows :: Int, cols :: Int, data :: List[Float] }.
141        // Used by std.math; runtime values are the F64Array fast lane,
142        // not a real record. The alias makes math.* signatures readable
143        // (`:: Matrix` instead of an inline record) and lets call sites
144        // unify nominally. Field access via `m.rows` would type-check
145        // but fail at runtime — use `math.rows / math.cols / math.get`.
146        let mut mat_fields = IndexMap::new();
147        mat_fields.insert("rows".into(), Ty::int());
148        mat_fields.insert("cols".into(), Ty::int());
149        mat_fields.insert("data".into(), Ty::List(Box::new(Ty::float())));
150        e.types.insert("Matrix".into(), TypeDef {
151            params: vec![],
152            kind: TypeDefKind::Alias(Ty::Record(mat_fields)),
153        });
154
155        e
156    }
157
158    pub fn add_user_type(&mut self, name: &str, decl: lex_ast::TypeDecl) -> Result<(), String> {
159        match &decl.definition {
160            lex_ast::TypeExpr::Union { variants } => {
161                let mut vmap = IndexMap::new();
162                for v in variants {
163                    let payload = v.payload.as_ref().map(|p| ty_from_canon(p, &decl.params));
164                    vmap.insert(v.name.clone(), payload);
165                    self.ctor_to_type.insert(v.name.clone(), name.to_string());
166                }
167                self.types.insert(name.to_string(), TypeDef {
168                    params: decl.params.clone(),
169                    kind: TypeDefKind::Union(vmap),
170                });
171            }
172            other => {
173                let ty = ty_from_canon(other, &decl.params);
174                self.types.insert(name.to_string(), TypeDef {
175                    params: decl.params.clone(),
176                    kind: TypeDefKind::Alias(ty),
177                });
178            }
179        }
180        Ok(())
181    }
182}
183
184/// Convert canonical TypeExpr to internal Ty, treating type params as
185/// fresh-numbered Vars (0..n in declaration order). When instantiating, we
186/// substitute these out.
187pub fn ty_from_canon(t: &lex_ast::TypeExpr, params: &[String]) -> Ty {
188    match t {
189        lex_ast::TypeExpr::Named { name, args } => {
190            // type param?
191            if let Some(idx) = params.iter().position(|p| p == name) {
192                if !args.is_empty() {
193                    // Type params don't take args.
194                    return Ty::Con(name.clone(), args.iter().map(|a| ty_from_canon(a, params)).collect());
195                }
196                return Ty::Var(idx as u32);
197            }
198            // Primitives.
199            match name.as_str() {
200                "Int" => return Ty::int(),
201                "Float" => return Ty::float(),
202                "Bool" => return Ty::bool(),
203                "Str" => return Ty::str(),
204                "Bytes" => return Ty::bytes(),
205                "Unit" | "Nil" => return Ty::Unit,
206                "Never" => return Ty::Never,
207                "List" if args.len() == 1 => return Ty::List(Box::new(ty_from_canon(&args[0], params))),
208                // `Tuple[T0, T1, ...]` is the constructor surface for
209                // tuples; canonicalize to the structural Ty::Tuple so
210                // it unifies with `(T0, T1)` literal-tuple syntax and
211                // with std.tuple's signatures.
212                "Tuple" => return Ty::Tuple(args.iter().map(|a| ty_from_canon(a, params)).collect()),
213                _ => {}
214            }
215            Ty::Con(name.clone(), args.iter().map(|a| ty_from_canon(a, params)).collect())
216        }
217        lex_ast::TypeExpr::Record { fields } => {
218            let mut m = IndexMap::new();
219            for f in fields { m.insert(f.name.clone(), ty_from_canon(&f.ty, params)); }
220            Ty::Record(m)
221        }
222        lex_ast::TypeExpr::Tuple { items } => Ty::Tuple(items.iter().map(|t| ty_from_canon(t, params)).collect()),
223        lex_ast::TypeExpr::Function { params: ps, effects, ret } => {
224            // Plumb effect args (#207).
225            let effs = EffectSet {
226                concrete: {
227                    let mut s = std::collections::BTreeSet::new();
228                    for e in effects {
229                        let arg = e.arg.as_ref().map(|a| match a {
230                            lex_ast::EffectArg::Str { value } => crate::types::EffectArg::Str(value.clone()),
231                            lex_ast::EffectArg::Int { value } => crate::types::EffectArg::Int(*value),
232                            lex_ast::EffectArg::Ident { value } => crate::types::EffectArg::Ident(value.clone()),
233                        });
234                        s.insert(crate::types::EffectKind { name: e.name.clone(), arg });
235                    }
236                    s
237                },
238                var: None,
239            };
240            Ty::Function {
241                params: ps.iter().map(|t| ty_from_canon(t, params)).collect(),
242                effects: effs,
243                ret: Box::new(ty_from_canon(ret, params)),
244            }
245        }
246        lex_ast::TypeExpr::Union { .. } => {
247            // Unions on the RHS of type-decls; not in arbitrary positions.
248            Ty::Unit
249        }
250        lex_ast::TypeExpr::Refined { base, .. } => {
251            // #209 slice 1: refinement types unify structurally as
252            // their base type. The predicate is parsed and stored in
253            // the AST (so `lex-vcs` content-addressing picks up
254            // refinement edits), but static discharge and runtime
255            // residual checks land in slices 2 and 3 of #209. The
256            // unification behavior here means a function declaring
257            // `Int{x | x > 0}` interoperates with plain `Int` callers
258            // — the predicate is informational until discharge is
259            // wired up.
260            ty_from_canon(base, params)
261        }
262    }
263}