Skip to main content

crisp_typeck/
types.rs

1use crisp_ast::Span;
2use std::collections::{BTreeMap, HashMap};
3
4pub type TypeVar = u32;
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum Ty {
8    Var(TypeVar),
9    Never,
10    Unit,
11    Bool,
12    Int,
13    UInt,
14    Float,
15    Char,
16    Str,
17    StrSlice,
18    Tuple(Vec<Ty>),
19    Array { elem: Box<Ty>, len: u64 },
20    Slice(Box<Ty>),
21    Fn { params: Vec<Ty>, ret: Box<Ty> },
22    Option(Box<Ty>),
23    Ref { mutable: bool, inner: Box<Ty> },
24    Named { name: String, args: Vec<Ty> },
25    Error,
26}
27
28impl Ty {
29    pub fn is_stringish(&self) -> bool {
30        matches!(self, Ty::Str | Ty::StrSlice)
31    }
32}
33
34#[derive(Debug, Clone)]
35pub struct Scheme {
36    pub vars: Vec<TypeVar>,
37    pub ty: Ty,
38}
39
40#[derive(Debug, Clone)]
41pub struct InferContext {
42    pub next_var: TypeVar,
43    pub subst: HashMap<TypeVar, Ty>,
44}
45
46impl InferContext {
47    pub fn new() -> Self {
48        Self {
49            next_var: 0,
50            subst: HashMap::new(),
51        }
52    }
53
54    pub fn fresh(&mut self) -> Ty {
55        let v = self.next_var;
56        self.next_var += 1;
57        Ty::Var(v)
58    }
59
60    pub fn apply(&mut self, ty: &Ty) -> Ty {
61        match ty {
62            Ty::Var(v) => {
63                if let Some(t) = self.subst.get(v).cloned() {
64                    let t = self.apply(&t);
65                    self.subst.insert(*v, t.clone());
66                    t
67                } else {
68                    ty.clone()
69                }
70            }
71            Ty::Tuple(ts) => Ty::Tuple(ts.iter().map(|t| self.apply(t)).collect()),
72            Ty::Array { elem, len } => Ty::Array {
73                elem: Box::new(self.apply(elem)),
74                len: *len,
75            },
76            Ty::Slice(inner) => Ty::Slice(Box::new(self.apply(inner))),
77            Ty::Fn { params, ret } => Ty::Fn {
78                params: params.iter().map(|p| self.apply(p)).collect(),
79                ret: Box::new(self.apply(ret)),
80            },
81            Ty::Option(inner) => Ty::Option(Box::new(self.apply(inner))),
82            Ty::Ref { mutable, inner } => Ty::Ref {
83                mutable: *mutable,
84                inner: Box::new(self.apply(inner)),
85            },
86            Ty::Named { name, args } => Ty::Named {
87                name: name.clone(),
88                args: args.iter().map(|a| self.apply(a)).collect(),
89            },
90            other => other.clone(),
91        }
92    }
93}
94
95#[derive(Debug, Clone)]
96pub struct InferredSig {
97    pub module: String,
98    pub name: String,
99    /// When set, this signature is an inherent `impl Type` method (§5.4).
100    pub impl_ty: Option<String>,
101    pub params: Vec<(String, Ty)>,
102    pub ret: Ty,
103    pub span: Span,
104    /// Explicit or inferred type parameters (`id(x) = x` → `["T"]`).
105    pub generics: Vec<String>,
106    pub is_pub: bool,
107    /// True when generics were named from leftover free vars (`id(x) = x`), not a pin.
108    pub inferred_from_use: bool,
109    /// Distinct concrete call-site instantiations (`int`, `str`) for reveal.
110    pub instantiations: Vec<String>,
111    /// Single concrete instantiation for crate-internal emit (#76). Scheme stays generic.
112    pub mono_args: Option<Vec<Ty>>,
113    /// Inferred bounds on generics (`T` → `Add` / `Show` / …) from operators and unique trait methods (#84).
114    pub op_bounds: BTreeMap<String, Vec<String>>,
115}
116
117impl InferredSig {
118    /// Named-generic substitution used when a crate-internal scheme is monomorphized (#76, #119).
119    pub fn emit_subst(&self) -> BTreeMap<String, Ty> {
120        let mut subst = BTreeMap::new();
121        let Some(args) = &self.mono_args else {
122            return subst;
123        };
124        for ((_, sty), ity) in self.params.iter().zip(args.iter()) {
125            collect_generic_subst(sty, ity, &self.generics, &mut subst);
126        }
127        subst
128    }
129
130    /// Param types, return type, and generics as they should be emitted (#76).
131    pub fn emit_view(&self) -> (Vec<(String, Ty)>, Ty, Vec<String>) {
132        let Some(args) = &self.mono_args else {
133            return (self.params.clone(), self.ret.clone(), self.generics.clone());
134        };
135        let subst = self.emit_subst();
136        let params = self
137            .params
138            .iter()
139            .zip(args.iter())
140            .map(|((n, _), t)| (n.clone(), t.clone()))
141            .collect();
142        let ret = subst_named(&self.ret, &subst);
143        (params, ret, Vec::new())
144    }
145
146    /// Emit-style binder list, including the hidden `T: Clone` bound (#78).
147    pub fn scheme_prefix(&self) -> String {
148        self.scheme_prefix_for(&self.generics)
149    }
150
151    pub fn scheme_prefix_for(&self, gens: &[String]) -> String {
152        if gens.is_empty() {
153            String::new()
154        } else {
155            format!(
156                "<{}>",
157                gens.iter()
158                    .map(|g| self.crisp_generic_bound(g))
159                    .collect::<Vec<_>>()
160                    .join(", ")
161            )
162        }
163    }
164
165    fn crisp_generic_bound(&self, g: &str) -> String {
166        let mut parts = vec![format!("{g}: Clone")];
167        if let Some(ops) = self.op_bounds.get(g) {
168            if ops.iter().any(|o| is_arith_bound(o)) {
169                parts.push("Copy".into());
170            }
171            for op in ops {
172                parts.push(op.clone());
173            }
174        }
175        parts.join(" + ")
176    }
177
178    /// Rust binder list (`T: Clone + std::ops::Add<Output = T>`).
179    pub fn rust_scheme_prefix_for(&self, gens: &[String]) -> String {
180        if gens.is_empty() {
181            String::new()
182        } else {
183            format!(
184                "<{}>",
185                gens.iter()
186                    .map(|g| self.rust_generic_bound(g))
187                    .collect::<Vec<_>>()
188                    .join(", ")
189            )
190        }
191    }
192
193    pub fn rust_generic_bound(&self, g: &str) -> String {
194        let mut parts = vec![format!("{g}: Clone")];
195        if let Some(ops) = self.op_bounds.get(g) {
196            if ops.iter().any(|o| is_arith_bound(o)) {
197                parts.push("Copy".into());
198            }
199            for op in ops {
200                parts.push(rust_op_bound(g, op));
201            }
202        }
203        parts.join(" + ")
204    }
205}
206
207/// Prelude arithmetic traits inferred from `+` `-` `*` `/` (spec §15.4).
208pub fn is_arith_bound(name: &str) -> bool {
209    matches!(name, "Add" | "Sub" | "Mul" | "Div")
210}
211
212/// Prelude operator trait → `std::ops` bound (spec §15.4).
213pub fn rust_op_bound(generic: &str, op: &str) -> String {
214    match op {
215        "Add" => format!("std::ops::Add<Output = {generic}>"),
216        "Sub" => format!("std::ops::Sub<Output = {generic}>"),
217        "Mul" => format!("std::ops::Mul<Output = {generic}>"),
218        "Div" => format!("std::ops::Div<Output = {generic}>"),
219        other => other.to_string(),
220    }
221}
222
223fn collect_generic_subst(
224    scheme: &Ty,
225    inst: &Ty,
226    generics: &[String],
227    subst: &mut std::collections::BTreeMap<String, Ty>,
228) {
229    match (scheme, inst) {
230        (Ty::Named { name, args }, inst)
231            if args.is_empty() && generics.iter().any(|g| g == name) =>
232        {
233            subst.entry(name.clone()).or_insert_with(|| inst.clone());
234        }
235        (Ty::Fn { params: a, ret: ra }, Ty::Fn { params: b, ret: rb }) if a.len() == b.len() => {
236            for (x, y) in a.iter().zip(b.iter()) {
237                collect_generic_subst(x, y, generics, subst);
238            }
239            collect_generic_subst(ra, rb, generics, subst);
240        }
241        (Ty::Named { args: a, .. }, Ty::Named { args: b, .. }) if a.len() == b.len() => {
242            for (x, y) in a.iter().zip(b.iter()) {
243                collect_generic_subst(x, y, generics, subst);
244            }
245        }
246        (Ty::Option(a), Ty::Option(b))
247        | (Ty::Slice(a), Ty::Slice(b))
248        | (Ty::Ref { inner: a, .. }, Ty::Ref { inner: b, .. }) => {
249            collect_generic_subst(a, b, generics, subst);
250        }
251        (Ty::Tuple(a), Ty::Tuple(b)) if a.len() == b.len() => {
252            for (x, y) in a.iter().zip(b.iter()) {
253                collect_generic_subst(x, y, generics, subst);
254            }
255        }
256        _ => {}
257    }
258}
259
260pub fn subst_named(ty: &Ty, subst: &std::collections::BTreeMap<String, Ty>) -> Ty {
261    match ty {
262        Ty::Named { name, args } if args.is_empty() => {
263            subst.get(name).cloned().unwrap_or_else(|| ty.clone())
264        }
265        Ty::Named { name, args } => Ty::Named {
266            name: name.clone(),
267            args: args.iter().map(|a| subst_named(a, subst)).collect(),
268        },
269        Ty::Fn { params, ret } => Ty::Fn {
270            params: params.iter().map(|p| subst_named(p, subst)).collect(),
271            ret: Box::new(subst_named(ret, subst)),
272        },
273        Ty::Option(inner) => Ty::Option(Box::new(subst_named(inner, subst))),
274        Ty::Slice(inner) => Ty::Slice(Box::new(subst_named(inner, subst))),
275        Ty::Array { elem, len } => Ty::Array {
276            elem: Box::new(subst_named(elem, subst)),
277            len: *len,
278        },
279        Ty::Ref { mutable, inner } => Ty::Ref {
280            mutable: *mutable,
281            inner: Box::new(subst_named(inner, subst)),
282        },
283        Ty::Tuple(ts) => Ty::Tuple(ts.iter().map(|t| subst_named(t, subst)).collect()),
284        other => other.clone(),
285    }
286}