plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-checker/src/monomorphize.rs
| 1b220d2 | 1 | use std::collections::BTreeMap; |
| 1b220d2 | 2 | use plum_core::ast; |
| 1b220d2 | 3 | use crate::types::PlumType; |
| 1b220d2 | 4 | |
| a2eaba8 | 5 | /// A single uppercase letter (`T`, `U`, `K`, ...) is the grammar's only legal |
| 1b220d2 | 6 | /// spelling for a generic type parameter — this is how we recognize one, since |
| 1b220d2 | 7 | /// `ast::Fn` and `ast::Enum` (unlike `ast::Class`/`ast::Trait`) carry no explicit |
| 1b220d2 | 8 | /// generics declaration list. |
| 3d6f280 | 9 | pub fn isGenericParamName(name: &str) -> bool { |
| 1b220d2 | 10 | let mut chars = name.chars(); |
| 1b220d2 | 11 | match (chars.next(), chars.next()) { |
| a2eaba8 | 12 | (Some(c), None) => c.is_ascii_uppercase(), |
| 1b220d2 | 13 | _ => false, |
| 1b220d2 | 14 | } |
| 1b220d2 | 15 | } |
| 1b220d2 | 16 | |
| 1b220d2 | 17 | /// The generic parameter names introduced by a `Class`, in declaration order. |
| 3d6f280 | 18 | pub fn classGenericParams(c: &ast::Class) -> Vec<String> { |
| 1b220d2 | 19 | c.generics.iter().map(|g| g.name.clone()).collect() |
| 1b220d2 | 20 | } |
| 1b220d2 | 21 | |
| 1b220d2 | 22 | /// The generic parameter names implicitly introduced by a `Fn` — every distinct |
| a2eaba8 | 23 | /// single-uppercase-letter type name appearing in its params or return type, in |
| 1b220d2 | 24 | /// first-appearance order. |
| 3d6f280 | 25 | pub fn fnGenericParams(f: &ast::Fn) -> Vec<String> { |
| 1b220d2 | 26 | let mut names: Vec<String> = Vec::new(); |
| 1b220d2 | 27 | let mut consider = |n: &str| { |
| 3d6f280 | 28 | if isGenericParamName(n) && !names.iter().any(|x| x == n) { |
| 1b220d2 | 29 | names.push(n.to_string()); |
| 1b220d2 | 30 | } |
| 1b220d2 | 31 | }; |
| 1b220d2 | 32 | for p in &f.params { |
| 1b220d2 | 33 | match &p.ty { |
| 1b220d2 | 34 | ast::ParamType::Type(t) => consider(&t.name), |
| 1b220d2 | 35 | ast::ParamType::Variadic(t) => consider(&t.name), |
| d7e5ff4 | 36 | // TODO: closures/fn-value params don't yet participate in generic |
| d7e5ff4 | 37 | // parameter inference. |
| d7e5ff4 | 38 | ast::ParamType::Fn(_, _) => {} |
| 1b220d2 | 39 | } |
| 1b220d2 | 40 | } |
| 1b220d2 | 41 | if let Some(r) = &f.returns { |
| 1b220d2 | 42 | consider(&r.name); |
| 1b220d2 | 43 | } |
| 1b220d2 | 44 | names |
| 1b220d2 | 45 | } |
| 1b220d2 | 46 | |
| 1b220d2 | 47 | /// The generic parameter names implicitly introduced by an `Enum` — every distinct |
| a2eaba8 | 48 | /// single-uppercase-letter variant field type name, in first-appearance order. |
| 3d6f280 | 49 | pub fn enumGenericParams(e: &ast::Enum) -> Vec<String> { |
| 1b220d2 | 50 | let mut names: Vec<String> = Vec::new(); |
| 1b220d2 | 51 | for v in &e.variants { |
| 1b220d2 | 52 | for field_ty in &v.fields { |
| 3d6f280 | 53 | if isGenericParamName(field_ty) && !names.iter().any(|x| x == field_ty) { |
| 1b220d2 | 54 | names.push(field_ty.clone()); |
| 1b220d2 | 55 | } |
| 1b220d2 | 56 | } |
| 1b220d2 | 57 | } |
| 1b220d2 | 58 | names |
| 1b220d2 | 59 | } |
| 1b220d2 | 60 | |
| 1b220d2 | 61 | /// A resolved binding from a generic item's parameter names to concrete types for |
| 1b220d2 | 62 | /// one instantiation site, e.g. `{"a": Int}` for `Box(value: 5)`. |
| 1b220d2 | 63 | #[derive(Debug, Clone)] |
| 1b220d2 | 64 | pub struct Substitution(pub BTreeMap<String, PlumType>); |
| 1b220d2 | 65 | |
| 1b220d2 | 66 | impl Substitution { |
| 1b220d2 | 67 | fn get(&self, name: &str) -> Option<&PlumType> { |
| 1b220d2 | 68 | self.0.get(name) |
| 1b220d2 | 69 | } |
| 1b220d2 | 70 | } |
| 1b220d2 | 71 | |
| 1b220d2 | 72 | /// Converts a resolved concrete `PlumType` back into the `ast::Type` shape needed |
| 1b220d2 | 73 | /// to substitute into a declared field/param/return type position. Only ever |
| 1b220d2 | 74 | /// called with types resolved from a real call-site argument's inferred type, so |
| 1b220d2 | 75 | /// `TVar`/`TFun` (which never arise from a concrete argument) are an internal-error |
| 1b220d2 | 76 | /// case rather than something this needs to model. |
| 3d6f280 | 77 | fn plumTypeToAstType(t: &PlumType) -> ast::Type { |
| 1b220d2 | 78 | let name = match t { |
| 1b220d2 | 79 | PlumType::TInt => "Int".to_string(), |
| 1b220d2 | 80 | PlumType::TFloat => "Float".to_string(), |
| 1b220d2 | 81 | PlumType::TBool => "Bool".to_string(), |
| 1b220d2 | 82 | PlumType::TStr => "Str".to_string(), |
| 0000000 | 83 | PlumType::TByte => "Byte".to_string(), |
| 0000000 | 84 | PlumType::TByteSlice => "[]Byte".to_string(), |
| 1b220d2 | 85 | PlumType::TUnit => "Unit".to_string(), |
| 1b220d2 | 86 | PlumType::TNamed(n) => n.clone(), |
| d0981fb | 87 | PlumType::TVar(_) | PlumType::TFun(_, _) | PlumType::TVariadic(_) => t.to_string(), |
| 1b220d2 | 88 | }; |
| 1b220d2 | 89 | ast::Type { name, generics: vec![] } |
| 1b220d2 | 90 | } |
| 1b220d2 | 91 | |
| 3d6f280 | 92 | fn substituteType(ty: &ast::Type, subst: &Substitution) -> ast::Type { |
| 1b220d2 | 93 | if ty.generics.is_empty() { |
| 1b220d2 | 94 | if let Some(concrete) = subst.get(&ty.name) { |
| 3d6f280 | 95 | return plumTypeToAstType(concrete); |
| 1b220d2 | 96 | } |
| 1b220d2 | 97 | } |
| 1b220d2 | 98 | ast::Type { |
| 1b220d2 | 99 | name: ty.name.clone(), |
| 3d6f280 | 100 | generics: ty.generics.iter().map(|g| substituteType(g, subst)).collect(), |
| 1b220d2 | 101 | } |
| 1b220d2 | 102 | } |
| 1b220d2 | 103 | |
| 1b220d2 | 104 | /// Mangles a generic item's base name and its resolved concrete type arguments |
| 1b220d2 | 105 | /// (in the item's own generic-parameter declaration order) into the internal name |
| 1b220d2 | 106 | /// used for its specialized copy, e.g. `Box` + `[Int]` -> `"Box$Int"`. |
| 1b220d2 | 107 | pub fn mangle(base: &str, type_args: &[PlumType]) -> String { |
| 1b220d2 | 108 | let mut out = base.to_string(); |
| 1b220d2 | 109 | for t in type_args { |
| 1b220d2 | 110 | out.push('$'); |
| 1b220d2 | 111 | out.push_str(&t.to_string()); |
| 1b220d2 | 112 | } |
| 1b220d2 | 113 | out |
| 1b220d2 | 114 | } |
| 1b220d2 | 115 | |
| 1b220d2 | 116 | /// Produces a concrete, specialized copy of a generic class under `mangled_name`, |
| 1b220d2 | 117 | /// substituting every field whose declared type names one of the class's generic |
| 1b220d2 | 118 | /// parameters with its resolved concrete type. The class's own `generics` list is |
| 1b220d2 | 119 | /// cleared on the copy (it is now fully concrete). |
| 3d6f280 | 120 | pub fn specializeClass(c: &ast::Class, subst: &Substitution, mangled_name: &str) -> ast::Class { |
| 1b220d2 | 121 | ast::Class { |
| 1b220d2 | 122 | name: mangled_name.to_string(), |
| 1b220d2 | 123 | implements: c.implements.clone(), |
| 1b220d2 | 124 | generics: vec![], |
| 1b220d2 | 125 | fields: c.fields.iter().map(|f| ast::Field { |
| 1b220d2 | 126 | name: f.name.clone(), |
| 3d6f280 | 127 | ty: substituteType(&f.ty, subst), |
| 1b220d2 | 128 | }).collect(), |
| 1b220d2 | 129 | } |
| 1b220d2 | 130 | } |
| 1b220d2 | 131 | |
| 1b220d2 | 132 | /// Produces a concrete, specialized copy of a generic function (or method) under |
| 1b220d2 | 133 | /// `mangled_name`. `new_type_param` overrides the receiver-type name for a method |
| 1b220d2 | 134 | /// whose receiver class was itself specialized (e.g. a method declared on `Box` |
| 1b220d2 | 135 | /// becomes a method on `Box$Int`); pass the original `f.type_param.clone()` |
| 1b220d2 | 136 | /// unchanged for a plain free function. The body is left structurally identical |
| 1b220d2 | 137 | /// here — its own call sites are rewritten separately (Task 2), since expressions |
| 1b220d2 | 138 | /// don't carry declared-type annotations the way fields/params/return types do. |
| 3d6f280 | 139 | pub fn specializeFn(f: &ast::Fn, subst: &Substitution, mangled_name: &str, new_type_param: Option<String>) -> ast::Fn { |
| 1b220d2 | 140 | ast::Fn { |
| 1b220d2 | 141 | name: mangled_name.to_string(), |
| 1b220d2 | 142 | type_param: new_type_param, |
| 0000000 | 143 | is_extern: f.is_extern, |
| 1b220d2 | 144 | params: f.params.iter().map(|p| ast::Param { |
| 1b220d2 | 145 | name: p.name.clone(), |
| 1b220d2 | 146 | ty: match &p.ty { |
| 3d6f280 | 147 | ast::ParamType::Type(t) => ast::ParamType::Type(substituteType(t, subst)), |
| 3d6f280 | 148 | ast::ParamType::Variadic(t) => ast::ParamType::Variadic(substituteType(t, subst)), |
| 0000000 | 149 | ast::ParamType::Fn(params, ret) => ast::ParamType::Fn( |
| 0000000 | 150 | params.iter().map(|t| substituteType(t, subst)).collect(), |
| 0000000 | 151 | ret.as_ref().map(|r| Box::new(substituteType(r, subst))), |
| 0000000 | 152 | ), |
| 1b220d2 | 153 | }, |
| 1b220d2 | 154 | default: p.default.clone(), |
| 1b220d2 | 155 | }).collect(), |
| 3d6f280 | 156 | returns: f.returns.as_ref().map(|r| substituteType(r, subst)), |
| 1b220d2 | 157 | body: f.body.clone(), |
| 1b220d2 | 158 | } |
| 1b220d2 | 159 | } |
| 1b220d2 | 160 | |
| 1b220d2 | 161 | /// Produces a concrete, specialized copy of a generic enum under `mangled_name`, |
| 1b220d2 | 162 | /// substituting every variant field type name that matches one of the enum's |
| 1b220d2 | 163 | /// generic parameters with its resolved concrete type's name. |
| 2216237 | 164 | /// |
| 2216237 | 165 | /// Variant names are ALSO mangled here, with the same suffix as the enum's own |
| 2216237 | 166 | /// name (e.g. `Some` -> `Some$Int`) — even a payload-free variant like `None`. |
| 2216237 | 167 | /// This is necessary because the runtime `EnumVariants` table (built by |
| 3d6f280 | 168 | /// `buildGlobalTables`) is keyed by bare variant name globally: without this, |
| 2216237 | 169 | /// two specializations of the same generic enum would both register a variant |
| 2216237 | 170 | /// literally named `Some`, colliding in that flat table. |
| 3d6f280 | 171 | pub fn specializeEnum(e: &ast::Enum, subst: &Substitution, mangled_name: &str) -> ast::Enum { |
| 3d6f280 | 172 | let params = enumGenericParams(e); |
| 2216237 | 173 | let type_args: Vec<PlumType> = params.iter().filter_map(|p| subst.get(p).cloned()).collect(); |
| 1b220d2 | 174 | ast::Enum { |
| 1b220d2 | 175 | name: mangled_name.to_string(), |
| fbfbd7b | 176 | params: e.params.clone(), |
| 1b220d2 | 177 | variants: e.variants.iter().map(|v| ast::EnumVariant { |
| 2216237 | 178 | name: mangle(&v.name, &type_args), |
| 1b220d2 | 179 | fields: v.fields.iter().map(|f| { |
| 1b220d2 | 180 | subst.get(f).map(|t| t.to_string()).unwrap_or_else(|| f.clone()) |
| 1b220d2 | 181 | }).collect(), |
| fbfbd7b | 182 | values: v.values.clone(), |
| 1b220d2 | 183 | }).collect(), |
| 1b220d2 | 184 | } |
| 1b220d2 | 185 | } |
| 22140cf | 186 | |
| 22140cf | 187 | use std::collections::BTreeSet; |
| 22140cf | 188 | use crate::types::{TypeEnv, TypeScheme}; |
| 4fda634 | 189 | use crate::{ClassEnv, MethodEnv, EnumVariants, EnumVariantInfo, EnumParams, CheckCtx}; |
| 22140cf | 190 | |
| 22140cf | 191 | enum PendingSpecialization<'a> { |
| 22140cf | 192 | Class { base: &'a ast::Class, subst: Substitution, mangled: String }, |
| 22140cf | 193 | Fn { base: &'a ast::Fn, subst: Substitution, mangled: String, new_receiver: Option<String> }, |
| 22140cf | 194 | Enum { base: &'a ast::Enum, subst: Substitution, mangled: String }, |
| 22140cf | 195 | } |
| 22140cf | 196 | |
| 0000000 | 197 | /// True if `pat` binds `name` anywhere within it (a `Name` sub-pattern, at any |
| 0000000 | 198 | /// nesting depth inside a `Class` constructor pattern) — used by |
| 0000000 | 199 | /// `renameVarInStmt`'s `Match` case to recognize when a NESTED case pattern |
| 0000000 | 200 | /// re-shadows the name currently being renamed, in which case that nested |
| 0000000 | 201 | /// case's body refers to a different (shadowing) binding and must be left alone. |
| 0000000 | 202 | fn caseBindsName(pat: &ast::CasePattern, name: &str) -> bool { |
| 0000000 | 203 | match pat { |
| 0000000 | 204 | ast::CasePattern::Name(n) => n == name, |
| 0000000 | 205 | ast::CasePattern::Class { fields, .. } => fields.iter().any(|f| caseBindsName(f, name)), |
| 0000000 | 206 | _ => false, |
| 0000000 | 207 | } |
| 0000000 | 208 | } |
| 0000000 | 209 | |
| 0000000 | 210 | /// Renames every `Expr::Var(old)` to `Expr::Var(new)` within `block`, used by |
| 0000000 | 211 | /// `Monomorphizer::dedupLocalName` to rename a match-case/for-loop binding |
| 0000000 | 212 | /// (plus every reference to it) once its usage is known to be confined to that |
| 0000000 | 213 | /// one block — see `local_types_by_name`'s doc comment for why this is needed |
| 0000000 | 214 | /// at all. Purely syntactic (no type information needed): stops descending into |
| 0000000 | 215 | /// any NESTED scope that re-binds `old` itself (a nested `for` over the same |
| 0000000 | 216 | /// name, or a nested `match` case whose pattern binds it again), since that |
| 0000000 | 217 | /// inner scope's occurrences of `old` are a different, shadowing variable, not |
| 0000000 | 218 | /// the one being renamed. |
| 0000000 | 219 | fn renameVarInBlock(block: &mut ast::Block, old: &str, new: &str) { |
| 0000000 | 220 | for stmt in &mut block.stmts { |
| 0000000 | 221 | renameVarInStmt(stmt, old, new); |
| 0000000 | 222 | } |
| 0000000 | 223 | } |
| 0000000 | 224 | |
| 0000000 | 225 | fn renameVarInStmt(stmt: &mut ast::Stmt, old: &str, new: &str) { |
| 0000000 | 226 | match stmt { |
| 0000000 | 227 | ast::Stmt::Assign(a) => { |
| 0000000 | 228 | for v in &mut a.values { |
| 0000000 | 229 | renameVarInExpr(v, old, new); |
| 0000000 | 230 | } |
| 0000000 | 231 | for t in &mut a.targets { |
| 0000000 | 232 | match t { |
| 0000000 | 233 | ast::AssignTarget::Var(n) => { |
| 0000000 | 234 | if n == old { |
| 0000000 | 235 | *n = new.to_string(); |
| 0000000 | 236 | } |
| 0000000 | 237 | } |
| 0000000 | 238 | ast::AssignTarget::Field(obj, _) => renameVarInExpr(obj, old, new), |
| 0000000 | 239 | } |
| 0000000 | 240 | } |
| 0000000 | 241 | } |
| 0000000 | 242 | ast::Stmt::Return(Some(e)) => renameVarInExpr(e, old, new), |
| 0000000 | 243 | ast::Stmt::Return(None) | ast::Stmt::Break | ast::Stmt::Continue | ast::Stmt::Todo => {} |
| 0000000 | 244 | ast::Stmt::Assert(e) => renameVarInExpr(e, old, new), |
| 0000000 | 245 | ast::Stmt::Expr(e) => renameVarInExpr(e, old, new), |
| 0000000 | 246 | ast::Stmt::If(if_) => { |
| 0000000 | 247 | renameVarInExpr(&mut if_.condition, old, new); |
| 0000000 | 248 | renameVarInBlock(&mut if_.body, old, new); |
| 0000000 | 249 | for ei in &mut if_.else_ifs { |
| 0000000 | 250 | renameVarInExpr(&mut ei.condition, old, new); |
| 0000000 | 251 | renameVarInBlock(&mut ei.body, old, new); |
| 0000000 | 252 | } |
| 0000000 | 253 | if let Some(else_block) = &mut if_.else_ { |
| 0000000 | 254 | renameVarInBlock(else_block, old, new); |
| 0000000 | 255 | } |
| 0000000 | 256 | } |
| 0000000 | 257 | ast::Stmt::While(w) => { |
| 0000000 | 258 | renameVarInExpr(&mut w.condition, old, new); |
| 0000000 | 259 | renameVarInBlock(&mut w.body, old, new); |
| 0000000 | 260 | } |
| 0000000 | 261 | ast::Stmt::For(f) => { |
| 0000000 | 262 | renameVarInExpr(&mut f.iter, old, new); |
| 0000000 | 263 | if !f.vars.iter().any(|v| v == old) { |
| 0000000 | 264 | renameVarInBlock(&mut f.body, old, new); |
| 0000000 | 265 | } |
| 0000000 | 266 | } |
| 0000000 | 267 | ast::Stmt::Match(m) => { |
| 0000000 | 268 | for s in &mut m.subjects { |
| 0000000 | 269 | renameVarInExpr(s, old, new); |
| 0000000 | 270 | } |
| 0000000 | 271 | for case in &mut m.cases { |
| 0000000 | 272 | if !case.patterns.iter().any(|p| caseBindsName(p, old)) { |
| 0000000 | 273 | renameVarInBlock(&mut case.body, old, new); |
| 0000000 | 274 | } |
| 0000000 | 275 | } |
| 0000000 | 276 | } |
| 0000000 | 277 | } |
| 0000000 | 278 | } |
| 0000000 | 279 | |
| 0000000 | 280 | fn renameVarInExpr(expr: &mut ast::Expr, old: &str, new: &str) { |
| 0000000 | 281 | match expr { |
| 0000000 | 282 | ast::Expr::Var(n) => { |
| 0000000 | 283 | if n == old { |
| 0000000 | 284 | *n = new.to_string(); |
| 0000000 | 285 | } |
| 0000000 | 286 | } |
| 0000000 | 287 | ast::Expr::ClassCall(call) => { |
| 0000000 | 288 | for fa in &mut call.fields { |
| 0000000 | 289 | renameVarInExpr(&mut fa.value, old, new); |
| 0000000 | 290 | } |
| 0000000 | 291 | } |
| 0000000 | 292 | ast::Expr::FnCall(call) => { |
| 0000000 | 293 | for arg in &mut call.args { |
| 0000000 | 294 | renameVarInArg(arg, old, new); |
| 0000000 | 295 | } |
| 0000000 | 296 | } |
| 0000000 | 297 | ast::Expr::Attribute(attr) => { |
| 0000000 | 298 | renameVarInExpr(&mut attr.object, old, new); |
| 0000000 | 299 | if let ast::AttrKind::Method(call) = &mut attr.attr { |
| 0000000 | 300 | for arg in &mut call.args { |
| 0000000 | 301 | renameVarInArg(arg, old, new); |
| 0000000 | 302 | } |
| 0000000 | 303 | } |
| 0000000 | 304 | } |
| 0000000 | 305 | ast::Expr::Binary(b) => { renameVarInExpr(&mut b.left, old, new); renameVarInExpr(&mut b.right, old, new); } |
| 0000000 | 306 | ast::Expr::Bool(b) => { renameVarInExpr(&mut b.left, old, new); renameVarInExpr(&mut b.right, old, new); } |
| 0000000 | 307 | ast::Expr::Compare(c) => { renameVarInExpr(&mut c.left, old, new); renameVarInExpr(&mut c.right, old, new); } |
| 0000000 | 308 | ast::Expr::Not(inner) => renameVarInExpr(inner, old, new), |
| 0000000 | 309 | ast::Expr::Unary(u) => renameVarInExpr(&mut u.operand, old, new), |
| 0000000 | 310 | ast::Expr::Paren(inner) => renameVarInExpr(inner, old, new), |
| 0000000 | 311 | ast::Expr::Ternary(t) => { |
| 0000000 | 312 | renameVarInExpr(&mut t.condition, old, new); |
| 0000000 | 313 | renameVarInExpr(&mut t.then, old, new); |
| 0000000 | 314 | renameVarInExpr(&mut t.else_, old, new); |
| 0000000 | 315 | } |
| 0000000 | 316 | ast::Expr::String(s) => { |
| 0000000 | 317 | for part in &mut s.parts { |
| 0000000 | 318 | if let ast::StringPart::Interp(e) = part { |
| 0000000 | 319 | renameVarInExpr(e, old, new); |
| 0000000 | 320 | } |
| 0000000 | 321 | } |
| 0000000 | 322 | } |
| 0000000 | 323 | ast::Expr::Int(_) | ast::Expr::Float(_) | ast::Expr::Self_ | ast::Expr::TypeName(_) => {} |
| 0000000 | 324 | // Not recursed into — matches `rewriteExpr`'s identical `Closure` case |
| 0000000 | 325 | // (closure bodies are compiled/free-variable-captured separately and |
| 0000000 | 326 | // aren't otherwise touched by this pass either). A closure capturing a |
| 0000000 | 327 | // variable that gets renamed here is a known, narrow residual gap. |
| 0000000 | 328 | ast::Expr::Closure(_) => {} |
| 0000000 | 329 | } |
| 0000000 | 330 | } |
| 0000000 | 331 | |
| 0000000 | 332 | fn renameVarInArg(arg: &mut ast::Arg, old: &str, new: &str) { |
| 0000000 | 333 | match arg { |
| 0000000 | 334 | ast::Arg::Positional(e) => renameVarInExpr(e, old, new), |
| 0000000 | 335 | ast::Arg::Keyword { value, .. } => renameVarInExpr(value, old, new), |
| 0000000 | 336 | ast::Arg::Pair { value, .. } => renameVarInExpr(value, old, new), |
| 0000000 | 337 | } |
| 0000000 | 338 | } |
| 0000000 | 339 | |
| 22140cf | 340 | struct Monomorphizer<'a> { |
| 22140cf | 341 | classes_generic: BTreeMap<String, &'a ast::Class>, |
| 22140cf | 342 | fns_generic: BTreeMap<String, &'a ast::Fn>, |
| 22140cf | 343 | methods_generic_on: BTreeMap<String, Vec<&'a ast::Fn>>, |
| 0000000 | 344 | /// Same as `methods_generic_on`, but for a method declared on a generic ENUM |
| 0000000 | 345 | /// (e.g. `Result`'s `isOk`/`isErr`) rather than a generic class — a separate map |
| 0000000 | 346 | /// because the two need separate lookups keyed by their own base-name maps |
| 0000000 | 347 | /// (`enums_generic_by_name` vs `classes_generic`) at both classification and |
| 0000000 | 348 | /// specialization time. |
| 0000000 | 349 | methods_generic_on_enum: BTreeMap<String, Vec<&'a ast::Fn>>, |
| c273ea5 | 350 | /// Bare variant name (e.g. `"Some"`) -> the generic `Enum` it belongs to. Keyed |
| c273ea5 | 351 | /// by variant name because a construction site (`Some(5)`) parses as a `FnCall` |
| c273ea5 | 352 | /// whose `name` is the VARIANT, not the enum's own name. |
| c273ea5 | 353 | enums_generic_by_variant: BTreeMap<String, &'a ast::Enum>, |
| 2216237 | 354 | /// Mangled enum name -> {original variant name -> mangled variant name}, e.g. |
| 2216237 | 355 | /// `"Option$Int" -> {"Some": "Some$Int", "None": "None$Int"}`. Populated eagerly |
| 3d6f280 | 356 | /// (in `resolveEnumInstantiation`, at the moment an instantiation's concrete |
| 2216237 | 357 | /// type arguments become known) rather than waiting for the worklist to actually |
| 2216237 | 358 | /// produce that specialization — so both a construction call site and a later |
| 2216237 | 359 | /// `match` on the same specialization can rewrite variant names consistently, |
| 2216237 | 360 | /// regardless of processing order. |
| 2216237 | 361 | enum_variant_mangling: BTreeMap<String, BTreeMap<String, String>>, |
| 2216237 | 362 | /// The enum's own bare name -> the generic `Enum` — used to detect a bare |
| 2216237 | 363 | /// generic-enum-typed function param (e.g. `o: Option`), distinct from |
| 2216237 | 364 | /// `enums_generic_by_variant` (keyed by VARIANT name, used for construction |
| 2216237 | 365 | /// sites like `Some(5)`). |
| 2216237 | 366 | enums_generic_by_name: BTreeMap<String, &'a ast::Enum>, |
| 3d6f280 | 367 | /// Free functions that are NOT generic by `fnGenericParams`'s lowercase-letter |
| 2216237 | 368 | /// convention, but whose param type(s) bare-name a generic class or enum (e.g. |
| 2216237 | 369 | /// `unwrapOr(o: Option, ...)`) — such a function still needs its own |
| 2216237 | 370 | /// per-call-site specialization, since its receiver generic class/enum is |
| 2216237 | 371 | /// dropped from the monomorphized output and the bare name would otherwise |
| 2216237 | 372 | /// resolve to nothing. |
| 2216237 | 373 | fns_bare_generic: BTreeMap<String, &'a ast::Fn>, |
| 22140cf | 374 | global_env: TypeEnv, |
| 22140cf | 375 | classes: ClassEnv, |
| 22140cf | 376 | methods: MethodEnv, |
| 22140cf | 377 | enum_variants: EnumVariants, |
| 4fda634 | 378 | enum_params: EnumParams, |
| 22140cf | 379 | specialized: BTreeSet<String>, |
| 22140cf | 380 | enqueued: BTreeSet<String>, |
| 22140cf | 381 | worklist: Vec<PendingSpecialization<'a>>, |
| 22140cf | 382 | produced: Vec<ast::Item>, |
| 0000000 | 383 | /// The declared return type of the function/method currently being rewritten |
| 0000000 | 384 | /// by `rewriteFnBody` — consulted by `resolveEnumInstantiation` as a fallback |
| 0000000 | 385 | /// when a single variant construction site (e.g. `Ok(5)`) can't pin down every |
| 0000000 | 386 | /// one of the enum's generic params by itself (see its doc comment). Reset at |
| 0000000 | 387 | /// the top of every `rewriteFnBody` call; never needs saving/restoring since |
| 0000000 | 388 | /// closures aren't recursed into by this pass (see `rewriteExpr`'s `Closure` arm). |
| 0000000 | 389 | current_return_type: Option<ast::Type>, |
| 0000000 | 390 | /// (receiver name or `None` for a free function, function/method name) -> |
| 0000000 | 391 | /// (the generic enum its declared return type names, the concrete type args |
| 0000000 | 392 | /// it names them with) — for every function/method whose OWN declared return |
| 0000000 | 393 | /// type is a fully-general instantiation of a known generic enum (e.g. |
| 0000000 | 394 | /// `-> Result[Int, Str]`). Populated once up front (in `monomorphizeSource`, |
| 0000000 | 395 | /// alongside `enums_generic_by_name`) from the ORIGINAL, unmodified signatures |
| 0000000 | 396 | /// — unlike everywhere else in this file, this doesn't need the specialization |
| 0000000 | 397 | /// to have actually run yet, since the declared signature already says |
| 0000000 | 398 | /// everything needed. Consulted by `resolveCallReturnType`. |
| 0000000 | 399 | fn_return_generic_enum: BTreeMap<(Option<String>, String), (&'a ast::Enum, Vec<PlumType>)>, |
| 0000000 | 400 | /// Every local name's type as first seen in the function currently being |
| 0000000 | 401 | /// rewritten (reset per `rewriteFnBody` call, like `current_return_type`). |
| 0000000 | 402 | /// Wasm local slots are allocated once per NAME for the whole function (see |
| 0000000 | 403 | /// `plum-wasm-codegen`'s `Collector`/`compileFnBody`), not per lexical scope — |
| 0000000 | 404 | /// so two unrelated bindings that happen to share a name (e.g. `Ok(v)` in two |
| 0000000 | 405 | /// separate, non-overlapping `match` statements) would silently collide on |
| 0000000 | 406 | /// one slot if their types ever differ. `dedupLocalName` consults this to |
| 0000000 | 407 | /// catch that and rename the second, conflicting binding instead. |
| 0000000 | 408 | local_types_by_name: BTreeMap<String, PlumType>, |
| 0000000 | 409 | /// Bumped each time `dedupLocalName` needs a fresh name; part of the fresh |
| 0000000 | 410 | /// name itself, so collisions between two different renames are impossible. |
| 0000000 | 411 | rename_counter: usize, |
| 22140cf | 412 | } |
| 22140cf | 413 | |
| 22140cf | 414 | impl<'a> Monomorphizer<'a> { |
| 0000000 | 415 | /// Like `infer`, but tries `resolveCallReturnType` first — needed anywhere the |
| 0000000 | 416 | /// resulting `PlumType` will be used to look up `enum_variant_mangling` (i.e. |
| 0000000 | 417 | /// wherever a value might need its constructor-pattern names rewritten later: |
| 0000000 | 418 | /// an assignment's recorded local type, or a `match` subject's type). |
| 0000000 | 419 | fn inferConcrete(&mut self, e: &ast::Expr, env: &TypeEnv) -> PlumType { |
| 0000000 | 420 | self.resolveCallReturnType(e, env).unwrap_or_else(|| self.infer(e, env)) |
| 0000000 | 421 | } |
| 0000000 | 422 | |
| 22140cf | 423 | fn infer(&self, e: &ast::Expr, env: &TypeEnv) -> PlumType { |
| 4fda634 | 424 | let ctx = CheckCtx { classes: &self.classes, methods: &self.methods, enum_variants: &self.enum_variants, enum_params: &self.enum_params }; |
| 3d6f280 | 425 | crate::inferExpr(e, env, &ctx).unwrap_or(PlumType::TVar("_".to_string())) |
| 22140cf | 426 | } |
| 22140cf | 427 | |
| 0000000 | 428 | /// Records `name`'s first-seen type in `local_types_by_name` without |
| 0000000 | 429 | /// renaming anything — for a binding whose usage isn't cleanly bounded to a |
| 0000000 | 430 | /// single `&mut ast::Block` this pass has in hand at the binding site (e.g. |
| 0000000 | 431 | /// a plain `Assign` — its "scope" is however much of the flat function body |
| 0000000 | 432 | /// follows it, not a nested block). This still lets a LATER, cleanly-bounded |
| 0000000 | 433 | /// binding (`dedupLocalName`, from a `match` arm or `for` loop) detect a |
| 0000000 | 434 | /// conflict against it and rename itself accordingly; it just means a |
| 0000000 | 435 | /// conflict in the other direction (an `Assign` conflicting with an |
| 0000000 | 436 | /// EARLIER match-bound name) isn't caught. Real but narrower residual gap — |
| 0000000 | 437 | /// see the `local_types_by_name` doc comment. |
| 0000000 | 438 | fn seedLocalType(&mut self, name: &str, ty: &PlumType) { |
| 0000000 | 439 | self.local_types_by_name.entry(name.to_string()).or_insert_with(|| ty.clone()); |
| 0000000 | 440 | } |
| 0000000 | 441 | |
| 0000000 | 442 | /// Ensures `name` can be bound to `ty` here without colliding with a |
| 0000000 | 443 | /// DIFFERENT type already recorded for that same name elsewhere in the |
| 0000000 | 444 | /// current function (see `local_types_by_name`'s doc comment for why that's |
| 0000000 | 445 | /// otherwise unsafe). If there's no conflict, returns `name` unchanged. If |
| 0000000 | 446 | /// there IS one, mints a fresh name, renames every reference to `name` |
| 0000000 | 447 | /// within `scope` (a match case body / for-loop body — the full extent this |
| 0000000 | 448 | /// particular binding's usage can ever reach) to that fresh name, and |
| 0000000 | 449 | /// returns it for the caller to use as the actual binding name instead. |
| 0000000 | 450 | fn dedupLocalName(&mut self, name: &str, ty: &PlumType, scope: &mut ast::Block) -> String { |
| 0000000 | 451 | match self.local_types_by_name.get(name) { |
| 0000000 | 452 | None => { |
| 0000000 | 453 | self.local_types_by_name.insert(name.to_string(), ty.clone()); |
| 0000000 | 454 | name.to_string() |
| 0000000 | 455 | } |
| 0000000 | 456 | Some(existing) if existing == ty => name.to_string(), |
| 0000000 | 457 | Some(_) => { |
| 0000000 | 458 | self.rename_counter += 1; |
| 0000000 | 459 | let fresh = format!("{}$dup{}", name, self.rename_counter); |
| 0000000 | 460 | self.local_types_by_name.insert(fresh.clone(), ty.clone()); |
| 0000000 | 461 | renameVarInBlock(scope, name, &fresh); |
| 0000000 | 462 | fresh |
| 0000000 | 463 | } |
| 0000000 | 464 | } |
| 0000000 | 465 | } |
| 0000000 | 466 | |
| 22140cf | 467 | /// Rewrites a function/method body's generic call sites. When `resolve_return` |
| 22140cf | 468 | /// is set (for freshly-generated specializations, whose declared return may be |
| 22140cf | 469 | /// a generic parameter like `a` — which the current grammar can't even parse, |
| 22140cf | 470 | /// leaving `returns: None` — or a generic class), the declared return type is |
| 22140cf | 471 | /// re-derived from the concrete inferred type of the body's tail expression. |
| 22140cf | 472 | /// For an ordinary (non-generic) top-level function we only rewrite the return |
| 22140cf | 473 | /// annotation when it names a generic class used bare (e.g. `-> Box`), which |
| 22140cf | 474 | /// the body's construction site has just been specialized to a mangled name. |
| 3d6f280 | 475 | fn rewriteFnBody(&mut self, f: &mut ast::Fn, resolve_return: bool) -> Result<(), String> { |
| 0000000 | 476 | self.current_return_type = f.returns.clone(); |
| 0000000 | 477 | self.local_types_by_name.clear(); |
| 22140cf | 478 | let mut env = self.global_env.clone(); |
| 22140cf | 479 | if let Some(recv) = &f.type_param { |
| 22140cf | 480 | env.insert("self".to_string(), TypeScheme::mono(PlumType::TNamed(recv.clone()))); |
| 22140cf | 481 | } |
| 22140cf | 482 | for p in &f.params { |
| 22140cf | 483 | let ty = match &p.ty { |
| 3d6f280 | 484 | ast::ParamType::Type(t) => crate::plumTypeFromAst(t), |
| 3d6f280 | 485 | ast::ParamType::Variadic(t) => PlumType::TVariadic(Box::new(crate::plumTypeFromAst(t))), |
| d7e5ff4 | 486 | ast::ParamType::Fn(params, ret) => { |
| 3d6f280 | 487 | let param_types = params.iter().map(crate::plumTypeFromAst).collect(); |
| 3d6f280 | 488 | let ret_ty = ret.as_ref().map(|r| crate::plumTypeFromAst(r)).unwrap_or(PlumType::TUnit); |
| d7e5ff4 | 489 | PlumType::TFun(param_types, Box::new(ret_ty)) |
| d7e5ff4 | 490 | } |
| 22140cf | 491 | }; |
| 0000000 | 492 | self.local_types_by_name.insert(p.name.clone(), ty.clone()); |
| 22140cf | 493 | env.insert(p.name.clone(), TypeScheme::mono(ty)); |
| 22140cf | 494 | } |
| 0000000 | 495 | // The function's own return type, resolved for THIS specialization — used |
| 0000000 | 496 | // below to resolve a bare payload-free variant (`None`) that's the |
| 0000000 | 497 | // BODY'S TAIL EXPRESSION (implicit return), the same way `Stmt::Return` |
| 0000000 | 498 | // already does. A tail `Stmt::Expr` never goes through `Stmt::Return`'s |
| 0000000 | 499 | // own handling, so without this a trailing bare `None` is left |
| 0000000 | 500 | // unmangled and later fails the real checker's return-type unification. |
| 0000000 | 501 | let expected_ret = self.current_return_type.clone() |
| 0000000 | 502 | .map(|rt| self.resolveFieldType(&rt)) |
| 0000000 | 503 | .map(|rt| crate::plumTypeFromAst(&rt)); |
| 22140cf | 504 | let tail: Option<PlumType> = match &mut f.body { |
| 22140cf | 505 | ast::FnBody::Expr(e) => { |
| 0000000 | 506 | if let Some(expected) = &expected_ret { |
| 0000000 | 507 | self.resolveBareVariantAgainstExpected(e, expected); |
| 0000000 | 508 | } |
| 3d6f280 | 509 | self.rewriteExpr(e, &env)?; |
| 22140cf | 510 | Some(self.infer(e, &env)) |
| 22140cf | 511 | } |
| 22140cf | 512 | ast::FnBody::Block(block) => { |
| 0000000 | 513 | if let Some(expected) = &expected_ret { |
| 0000000 | 514 | if let Some(ast::Stmt::Expr(e)) = block.stmts.last_mut() { |
| 0000000 | 515 | self.resolveBareVariantAgainstExpected(e, expected); |
| 0000000 | 516 | } |
| 0000000 | 517 | } |
| 3d6f280 | 518 | self.rewriteBlock(block, &mut env)?; |
| 22140cf | 519 | match block.stmts.last() { |
| 22140cf | 520 | Some(ast::Stmt::Expr(e)) => Some(self.infer(e, &env)), |
| 22140cf | 521 | Some(ast::Stmt::Return(Some(e))) => Some(self.infer(e, &env)), |
| 22140cf | 522 | _ => None, |
| 22140cf | 523 | } |
| 22140cf | 524 | } |
| 0000000 | 525 | // No body to rewrite or infer a tail type from. |
| 0000000 | 526 | ast::FnBody::Extern => None, |
| 22140cf | 527 | }; |
| 22140cf | 528 | if let Some(t) = tail { |
| 3d6f280 | 529 | self.maybeRewriteReturn(f, &t, resolve_return); |
| 22140cf | 530 | } |
| 22140cf | 531 | Ok(()) |
| 22140cf | 532 | } |
| 22140cf | 533 | |
| 22140cf | 534 | /// Overwrites `f.returns` with a concrete type derived from the body's tail |
| c273ea5 | 535 | /// type `t` when the currently-declared return type is genuinely generic or |
| c273ea5 | 536 | /// unresolved. Never clobbers a real, concrete declared return type — even for |
| c273ea5 | 537 | /// a specialization (`resolve_return: true`) — so a generic function whose body |
| c273ea5 | 538 | /// is internally inconsistent with its concrete declared return (e.g. |
| c273ea5 | 539 | /// `wrong(x: a) -> Int = "hello"`) is left for the checker's normal |
| c273ea5 | 540 | /// return-type-mismatch logic to REJECT rather than silently rewritten (and |
| c273ea5 | 541 | /// thereby masked). The overwrite fires only when: |
| c273ea5 | 542 | /// - `f.returns` is `None` — the unparseable `-> a` generic-parameter-return |
| c273ea5 | 543 | /// case, where the grammar dropped the annotation entirely (this only ever |
| c273ea5 | 544 | /// happens for a specialization, which is the only path that can supply a |
| c273ea5 | 545 | /// concrete tail type to fill it in); or |
| c273ea5 | 546 | /// - the declared return names something still-generic: a generic-parameter |
| c273ea5 | 547 | /// letter (e.g. `-> a`) or a generic class used bare (e.g. `-> Box`). |
| c273ea5 | 548 | /// For the `Some(rt)` arm this condition is identical whether `resolve_return` |
| c273ea5 | 549 | /// is `true` or `false`; the specialization path differs only in that its tail |
| c273ea5 | 550 | /// is inferred against a resolved substitution, so a generic-parameter-letter |
| c273ea5 | 551 | /// return resolves to the specialization's concrete bound type (which the |
| c273ea5 | 552 | /// ordinary path cannot do). The unparseable-`None` fill-in is gated on |
| c273ea5 | 553 | /// `resolve_return` so an ordinary void function (`returns: None` meaning "no |
| c273ea5 | 554 | /// declared return", not "a generic return the grammar dropped") is never given |
| c273ea5 | 555 | /// a fabricated return type. Never fabricates a return from an un-inferrable |
| c273ea5 | 556 | /// (`TVar`) tail. |
| 3d6f280 | 557 | fn maybeRewriteReturn(&self, f: &mut ast::Fn, t: &PlumType, resolve_return: bool) { |
| 22140cf | 558 | if matches!(t, PlumType::TVar(_) | PlumType::TFun(_, _)) { |
| 22140cf | 559 | return; |
| 22140cf | 560 | } |
| 22140cf | 561 | let needs = match &f.returns { |
| 22140cf | 562 | None => resolve_return, |
| 22140cf | 563 | Some(rt) => { |
| 3d6f280 | 564 | isGenericParamName(&rt.name) |
| 22140cf | 565 | || self.classes_generic.contains_key(&rt.name) |
| 2216237 | 566 | || self.enums_generic_by_name.contains_key(&rt.name) |
| 22140cf | 567 | } |
| 22140cf | 568 | }; |
| 22140cf | 569 | if needs { |
| a2eaba8 | 570 | f.returns = Some(ast::Type { name: t.to_string(), generics: vec![] }); |
| 22140cf | 571 | } |
| 22140cf | 572 | } |
| 22140cf | 573 | |
| 3d6f280 | 574 | fn rewriteBlock(&mut self, block: &mut ast::Block, env: &mut TypeEnv) -> Result<(), String> { |
| 22140cf | 575 | for stmt in &mut block.stmts { |
| 3d6f280 | 576 | self.rewriteStmt(stmt, env)?; |
| 22140cf | 577 | } |
| 22140cf | 578 | Ok(()) |
| 22140cf | 579 | } |
| 22140cf | 580 | |
| 35af6cf | 581 | /// Rewrites `pat` (bare-name/constructor pattern) in place: an uppercase variant |
| 35af6cf | 582 | /// name gets mangled to its specialized form (`Some` -> `Some$Int`) if `mangling` |
| 35af6cf | 583 | /// says this position's subject is a specialized generic enum; a plain binding |
| 35af6cf | 584 | /// name is inserted into `case_env` at `ty`. Recurses into a constructor |
| 35af6cf | 585 | /// pattern's own fields (`Some(Some(v))`), looking up *that* field's own |
| 35af6cf | 586 | /// mangling table from `self.enum_variant_mangling` — a nested sub-pattern can |
| 35af6cf | 587 | /// be a specialization independent of its enclosing pattern's. |
| 3d6f280 | 588 | fn manglePattern( |
| 0000000 | 589 | &mut self, |
| 35af6cf | 590 | pat: &mut ast::CasePattern, |
| 35af6cf | 591 | ty: &PlumType, |
| 35af6cf | 592 | mangling: Option<&BTreeMap<String, String>>, |
| 35af6cf | 593 | case_env: &mut TypeEnv, |
| 0000000 | 594 | body: &mut ast::Block, |
| 35af6cf | 595 | ) { |
| 35af6cf | 596 | match pat { |
| 35af6cf | 597 | ast::CasePattern::Name(n) => { |
| 35af6cf | 598 | let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) |
| 35af6cf | 599 | && self.enum_variants.contains_key(n.as_str()); |
| 35af6cf | 600 | if is_variant { |
| 35af6cf | 601 | if let Some(table) = mangling { |
| 35af6cf | 602 | if let Some(mangled_variant) = table.get(n) { |
| 35af6cf | 603 | *n = mangled_variant.clone(); |
| 35af6cf | 604 | } |
| 35af6cf | 605 | } |
| 35af6cf | 606 | } else { |
| 0000000 | 607 | let bound = self.dedupLocalName(n, ty, body); |
| 0000000 | 608 | if bound != *n { |
| 0000000 | 609 | *n = bound.clone(); |
| 0000000 | 610 | } |
| 0000000 | 611 | case_env.insert(bound, TypeScheme::mono(ty.clone())); |
| 35af6cf | 612 | } |
| 35af6cf | 613 | } |
| 35af6cf | 614 | ast::CasePattern::Class { name, fields } => { |
| 35af6cf | 615 | if let Some(table) = mangling { |
| 35af6cf | 616 | if let Some(mangled_variant) = table.get(name) { |
| 35af6cf | 617 | *name = mangled_variant.clone(); |
| 35af6cf | 618 | } |
| 35af6cf | 619 | } |
| 35af6cf | 620 | if let Some(info) = self.enum_variants.get(name.as_str()) { |
| 35af6cf | 621 | let field_types = info.field_types.clone(); |
| 35af6cf | 622 | for (f, fty) in fields.iter_mut().zip(field_types.iter()) { |
| 0000000 | 623 | let field_mangling: Option<BTreeMap<String, String>> = match fty { |
| 0000000 | 624 | PlumType::TNamed(n) => self.enum_variant_mangling.get(n).cloned(), |
| 35af6cf | 625 | _ => None, |
| 35af6cf | 626 | }; |
| 0000000 | 627 | self.manglePattern(f, fty, field_mangling.as_ref(), case_env, body); |
| 35af6cf | 628 | } |
| 35af6cf | 629 | } |
| 35af6cf | 630 | } |
| 35af6cf | 631 | _ => {} |
| 35af6cf | 632 | } |
| 35af6cf | 633 | } |
| 35af6cf | 634 | |
| 3d6f280 | 635 | fn rewriteStmt(&mut self, stmt: &mut ast::Stmt, env: &mut TypeEnv) -> Result<(), String> { |
| 22140cf | 636 | match stmt { |
| 22140cf | 637 | ast::Stmt::Assign(a) => { |
| 47abc49 | 638 | for (target, value) in a.targets.iter_mut().zip(a.values.iter_mut()) { |
| 0000000 | 639 | // For a field target (`self.head = None`), resolve the |
| 0000000 | 640 | // value against the field's OWN declared type BEFORE the |
| 0000000 | 641 | // generic rewrite/inference below — same reasoning as |
| 0000000 | 642 | // `ClassCall`'s field values: a bare payload-free variant |
| 0000000 | 643 | // has no type of its own, but the field it's being |
| 0000000 | 644 | // written into does. |
| 0000000 | 645 | if let ast::AssignTarget::Field(object, field_name) = target { |
| 0000000 | 646 | if let PlumType::TNamed(class_name) = self.infer(object, env) { |
| 0000000 | 647 | if let Some(field_ty) = self.classes.get(&class_name) |
| 0000000 | 648 | .and_then(|fields| fields.iter().find(|(n, _)| n == field_name).map(|(_, t)| t.clone())) |
| 0000000 | 649 | { |
| 0000000 | 650 | self.resolveBareVariantAgainstExpected(value, &field_ty); |
| 0000000 | 651 | } |
| 0000000 | 652 | } |
| 0000000 | 653 | } |
| 3d6f280 | 654 | self.rewriteExpr(value, env)?; |
| 0000000 | 655 | let ty = self.inferConcrete(value, env); |
| 47abc49 | 656 | match target { |
| 47abc49 | 657 | ast::AssignTarget::Var(name) => { |
| 0000000 | 658 | self.seedLocalType(name, &ty); |
| 47abc49 | 659 | env.insert(name.clone(), TypeScheme::mono(ty)); |
| 47abc49 | 660 | } |
| 47abc49 | 661 | ast::AssignTarget::Field(object, _) => { |
| 3d6f280 | 662 | self.rewriteExpr(object, env)?; |
| 47abc49 | 663 | } |
| 47abc49 | 664 | } |
| 22140cf | 665 | } |
| 22140cf | 666 | } |
| 0000000 | 667 | ast::Stmt::Return(Some(e)) => { |
| 0000000 | 668 | // Same idea as the field-target case above, but against the |
| 0000000 | 669 | // enclosing function's own declared return type (`return |
| 0000000 | 670 | // None` inside a method returning `Option[Int]`). |
| 0000000 | 671 | if let Some(rt) = self.current_return_type.clone() { |
| 0000000 | 672 | let resolved = self.resolveFieldType(&rt); |
| 0000000 | 673 | let expected = crate::plumTypeFromAst(&resolved); |
| 0000000 | 674 | self.resolveBareVariantAgainstExpected(e, &expected); |
| 0000000 | 675 | } |
| 0000000 | 676 | self.rewriteExpr(e, env)?; |
| 0000000 | 677 | } |
| 22140cf | 678 | ast::Stmt::Return(None) => {} |
| 22140cf | 679 | ast::Stmt::If(if_) => { |
| 3d6f280 | 680 | self.rewriteExpr(&mut if_.condition, env)?; |
| 3d6f280 | 681 | self.rewriteBlock(&mut if_.body, &mut env.clone())?; |
| 22140cf | 682 | for ei in &mut if_.else_ifs { |
| 3d6f280 | 683 | self.rewriteExpr(&mut ei.condition, env)?; |
| 3d6f280 | 684 | self.rewriteBlock(&mut ei.body, &mut env.clone())?; |
| 22140cf | 685 | } |
| 22140cf | 686 | if let Some(else_block) = &mut if_.else_ { |
| 3d6f280 | 687 | self.rewriteBlock(else_block, &mut env.clone())?; |
| 22140cf | 688 | } |
| 22140cf | 689 | } |
| 22140cf | 690 | ast::Stmt::While(w) => { |
| 3d6f280 | 691 | self.rewriteExpr(&mut w.condition, env)?; |
| 3d6f280 | 692 | self.rewriteBlock(&mut w.body, &mut env.clone())?; |
| 22140cf | 693 | } |
| 22140cf | 694 | ast::Stmt::For(f) => { |
| 3d6f280 | 695 | self.rewriteExpr(&mut f.iter, env)?; |
| 22140cf | 696 | let mut inner = env.clone(); |
| 0000000 | 697 | let ast::For { vars, body, .. } = f; |
| 0000000 | 698 | for v in vars.iter_mut() { |
| 0000000 | 699 | let bound = self.dedupLocalName(v, &PlumType::TInt, body); |
| 0000000 | 700 | inner.insert(bound.clone(), TypeScheme::mono(PlumType::TInt)); |
| 0000000 | 701 | *v = bound; |
| 22140cf | 702 | } |
| 0000000 | 703 | self.rewriteBlock(body, &mut inner)?; |
| 22140cf | 704 | } |
| 3d6f280 | 705 | ast::Stmt::Expr(e) => self.rewriteExpr(e, env)?, |
| 3d6f280 | 706 | ast::Stmt::Assert(e) => self.rewriteExpr(e, env)?, |
| 22140cf | 707 | ast::Stmt::Match(m) => { |
| 22140cf | 708 | for s in &mut m.subjects { |
| 3d6f280 | 709 | self.rewriteExpr(s, env)?; |
| 22140cf | 710 | } |
| 35af6cf | 711 | // One (type, variant-mangling table) pair per subject — `match a, b` |
| 35af6cf | 712 | // needs each position's own generic-enum specialization handled |
| 35af6cf | 713 | // independently, not just the first subject's. |
| 0000000 | 714 | let subject_types: Vec<PlumType> = m.subjects.iter().map(|s| self.inferConcrete(s, env)).collect(); |
| 35af6cf | 715 | // If a subject's concrete type is a specialized generic enum, its |
| 35af6cf | 716 | // variant-name mangling table lets us rewrite that position's patterns |
| 2216237 | 717 | // (`Some`/`None` -> `Some$Int`/`None$Int`) to reference the correct |
| 2216237 | 718 | // specialization, so the checker/codegen's unmodified, bare-name-keyed |
| 2216237 | 719 | // `EnumVariants` lookup still resolves each pattern correctly. |
| 35af6cf | 720 | let variant_manglings: Vec<Option<BTreeMap<String, String>>> = subject_types |
| 35af6cf | 721 | .iter() |
| 35af6cf | 722 | .map(|ty| match ty { |
| 35af6cf | 723 | PlumType::TNamed(n) => self.enum_variant_mangling.get(n).cloned(), |
| 35af6cf | 724 | _ => None, |
| 35af6cf | 725 | }) |
| 35af6cf | 726 | .collect(); |
| 22140cf | 727 | for case in &mut m.cases { |
| 22140cf | 728 | let mut case_env = env.clone(); |
| 0000000 | 729 | let ast::Case { patterns, body } = case; |
| 35af6cf | 730 | for (pat, (subject_ty, variant_mangling)) in |
| 0000000 | 731 | patterns.iter_mut().zip(subject_types.iter().zip(variant_manglings.iter())) |
| 35af6cf | 732 | { |
| 0000000 | 733 | self.manglePattern(pat, subject_ty, variant_mangling.as_ref(), &mut case_env, body); |
| 0000000 | 734 | } |
| 0000000 | 735 | // A case body's own LAST statement, when it's a bare |
| 0000000 | 736 | // `Stmt::Expr`, is itself an implicit-return position |
| 0000000 | 737 | // whenever this whole `match` is the function's tail |
| 0000000 | 738 | // expression (`first`/`last`'s `None =>` arm ending in a |
| 0000000 | 739 | // bare `None`, for instance) — same reasoning as |
| 0000000 | 740 | // `rewriteFnBody`'s own tail-expression handling, just one |
| 0000000 | 741 | // level down through the match. Resolving against the |
| 0000000 | 742 | // function's OWN declared return type is a no-op unless |
| 0000000 | 743 | // the expression is actually a bare payload-free variant |
| 0000000 | 744 | // of that same enum family, so this is harmless even for |
| 0000000 | 745 | // a match that ISN'T in tail position. |
| 0000000 | 746 | if let Some(rt) = self.current_return_type.clone() { |
| 0000000 | 747 | let resolved = self.resolveFieldType(&rt); |
| 0000000 | 748 | let expected = crate::plumTypeFromAst(&resolved); |
| 0000000 | 749 | if let Some(ast::Stmt::Expr(e)) = body.stmts.last_mut() { |
| 0000000 | 750 | self.resolveBareVariantAgainstExpected(e, &expected); |
| 0000000 | 751 | } |
| 22140cf | 752 | } |
| 0000000 | 753 | self.rewriteBlock(body, &mut case_env)?; |
| 22140cf | 754 | } |
| 22140cf | 755 | } |
| 22140cf | 756 | ast::Stmt::Break | ast::Stmt::Continue | ast::Stmt::Todo => {} |
| 22140cf | 757 | } |
| 22140cf | 758 | Ok(()) |
| 22140cf | 759 | } |
| 22140cf | 760 | |
| 3d6f280 | 761 | fn resolveClassInstantiation(&mut self, call: &mut ast::ClassCall, env: &TypeEnv) -> Result<(), String> { |
| 22140cf | 762 | let Some(class) = self.classes_generic.get(call.type_name.as_str()).copied() else { return Ok(()) }; |
| 3d6f280 | 763 | let params = classGenericParams(class); |
| 22140cf | 764 | let mut bindings: BTreeMap<String, PlumType> = BTreeMap::new(); |
| 22140cf | 765 | for gp in ¶ms { |
| 22140cf | 766 | if let Some(field) = class.fields.iter().find(|f| f.ty.name == *gp) { |
| 22140cf | 767 | if let Some(fa) = call.fields.iter().find(|fa| fa.name == field.name) { |
| 0000000 | 768 | // A bare payload-free variant reference (`None`) carries |
| 0000000 | 769 | // no type of its own to bind a generic param FROM — skip |
| 0000000 | 770 | // it here; it gets resolved AGAINST the binding (once |
| 0000000 | 771 | // known) below instead, same as every other field. |
| 0000000 | 772 | let is_bare_variant = matches!(&fa.value, ast::Expr::TypeName(n) if self.enums_generic_by_variant.contains_key(n)); |
| 0000000 | 773 | if !is_bare_variant { |
| 0000000 | 774 | bindings.insert(gp.clone(), self.infer(&fa.value, env)); |
| 0000000 | 775 | } |
| 22140cf | 776 | } |
| 22140cf | 777 | } |
| 22140cf | 778 | } |
| 0000000 | 779 | // No field is EVER directly typed as a bare generic param for a class |
| 0000000 | 780 | // like `List[T]` (its fields are `Option[Node[T]]`/`Int`, never a bare |
| 0000000 | 781 | // `T`) — and even where one exists, constructing with a payload-free |
| 0000000 | 782 | // value (`List(head: None, ...)`) gives no VALUE to infer a type from |
| 0000000 | 783 | // regardless. Fall back to an explicit `List[Int](...)` annotation at |
| 0000000 | 784 | // the call site when field-value inference alone isn't enough. |
| 0000000 | 785 | if bindings.len() != params.len() && call.generics.len() == params.len() { |
| 0000000 | 786 | for (p, gt) in params.iter().zip(call.generics.iter()) { |
| 0000000 | 787 | bindings.entry(p.clone()).or_insert_with(|| crate::plumTypeFromAst(gt)); |
| 0000000 | 788 | } |
| 0000000 | 789 | } |
| 22140cf | 790 | if bindings.len() != params.len() { |
| 22140cf | 791 | return Err(format!( |
| 0000000 | 792 | "monomorphize: could not resolve all generic parameters for '{}' at this call site — pass them explicitly, e.g. '{}[Int](...)'", |
| 0000000 | 793 | call.type_name, call.type_name |
| 22140cf | 794 | )); |
| 22140cf | 795 | } |
| 22140cf | 796 | let type_args: Vec<PlumType> = params.iter().map(|p| bindings[p].clone()).collect(); |
| 22140cf | 797 | let mangled = mangle(&call.type_name, &type_args); |
| 0000000 | 798 | |
| 0000000 | 799 | // Now that every generic param is bound, resolve any bare |
| 0000000 | 800 | // payload-free-variant field values (`None`) against THIS class's own |
| 0000000 | 801 | // (about-to-be-specialized) field types — codegen only ever sees the |
| 0000000 | 802 | // mangled specializations (the generic template is dropped), so a |
| 0000000 | 803 | // still-bare `None` would be an unresolvable reference by the time it |
| 0000000 | 804 | // gets there. `specializeClass` is a pure function; calling it here |
| 0000000 | 805 | // ahead of the worklist actually processing this specialization is |
| 0000000 | 806 | // fine — the worklist dedups on `mangled` regardless of how many |
| 0000000 | 807 | // times it's computed. |
| 0000000 | 808 | let spec_class = specializeClass(class, &Substitution(bindings.clone()), &mangled); |
| 0000000 | 809 | for fa in &mut call.fields { |
| 0000000 | 810 | if let Some(field) = spec_class.fields.iter().find(|f| f.name == fa.name) { |
| 0000000 | 811 | let field_ty = self.resolveFieldType(&field.ty); |
| 0000000 | 812 | let expected = crate::plumTypeFromAst(&field_ty); |
| 0000000 | 813 | self.resolveBareVariantAgainstExpected(&mut fa.value, &expected); |
| 0000000 | 814 | } |
| 0000000 | 815 | } |
| 0000000 | 816 | |
| 22140cf | 817 | if !self.specialized.contains(&mangled) && !self.enqueued.contains(&mangled) { |
| 22140cf | 818 | self.enqueued.insert(mangled.clone()); |
| 0000000 | 819 | self.worklist.push(PendingSpecialization::Class { base: class, subst: Substitution(bindings.clone()), mangled: mangled.clone() }); |
| 0000000 | 820 | } |
| 0000000 | 821 | // Register this specialization's own field types and its methods' |
| 0000000 | 822 | // signatures right now — a construction site like this one can appear |
| 0000000 | 823 | // inside an ORDINARY (non-generic) function, which gets rewritten in |
| 0000000 | 824 | // the pass BEFORE the worklist above ever runs. Any later statement in |
| 0000000 | 825 | // that SAME function body (e.g. `l.get(1)` followed by a `match` on |
| 0000000 | 826 | // its result) needs `self.methods`/`self.classes` to already know |
| 0000000 | 827 | // about "List$Int" right now, not once the worklist eventually |
| 0000000 | 828 | // catches up. |
| 0000000 | 829 | if !self.classes.contains_key(&mangled) { |
| 0000000 | 830 | let field_types: Vec<(String, PlumType)> = spec_class.fields.iter() |
| 0000000 | 831 | .map(|f| (f.name.clone(), crate::plumTypeFromAst(&self.resolveFieldType(&f.ty)))) |
| 0000000 | 832 | .collect(); |
| 0000000 | 833 | self.classes.insert(mangled.clone(), field_types); |
| 22140cf | 834 | } |
| 0000000 | 835 | self.registerClassMethodSignatures(class, &mangled, &bindings); |
| 22140cf | 836 | call.type_name = mangled; |
| 22140cf | 837 | Ok(()) |
| 22140cf | 838 | } |
| 22140cf | 839 | |
| 0000000 | 840 | /// Eagerly computes and registers (into `self.methods`) the `(mangled, |
| 0000000 | 841 | /// method_name) -> TFun` signature of every method declared on `class`, |
| 0000000 | 842 | /// for the specialization named `mangled` under `bindings` — without |
| 0000000 | 843 | /// producing the actual `ast::Fn` items (that still only happens once the |
| 0000000 | 844 | /// worklist entry for this specialization is popped, avoiding duplicate |
| 0000000 | 845 | /// emission). Needed so a call site that appears in a function processed |
| 0000000 | 846 | /// BEFORE the worklist runs (see callers) can still resolve a method call |
| 0000000 | 847 | /// against this specialization immediately. |
| 0000000 | 848 | fn registerClassMethodSignatures(&mut self, class: &'a ast::Class, mangled: &str, bindings: &BTreeMap<String, PlumType>) { |
| 0000000 | 849 | let Some(methods) = self.methods_generic_on.get(class.name.as_str()).cloned() else { return }; |
| 0000000 | 850 | for method in methods { |
| 0000000 | 851 | let key = (mangled.to_string(), method.name.clone()); |
| 0000000 | 852 | if self.methods.contains_key(&key) { |
| 0000000 | 853 | continue; |
| 0000000 | 854 | } |
| 0000000 | 855 | let mut specialized_method = specializeFn(method, &Substitution(bindings.clone()), &method.name, Some(mangled.to_string())); |
| 0000000 | 856 | self.resolveFnSignature(&mut specialized_method); |
| 0000000 | 857 | let param_types: Vec<PlumType> = specialized_method.params.iter().map(|p| match &p.ty { |
| 0000000 | 858 | ast::ParamType::Type(t) => crate::plumTypeFromAst(t), |
| 0000000 | 859 | ast::ParamType::Variadic(t) => PlumType::TVariadic(Box::new(crate::plumTypeFromAst(t))), |
| 0000000 | 860 | ast::ParamType::Fn(params, ret) => { |
| 0000000 | 861 | let param_types = params.iter().map(crate::plumTypeFromAst).collect(); |
| 0000000 | 862 | let ret_ty = ret.as_ref().map(|r| crate::plumTypeFromAst(r)).unwrap_or(PlumType::TUnit); |
| 0000000 | 863 | PlumType::TFun(param_types, Box::new(ret_ty)) |
| 0000000 | 864 | } |
| 0000000 | 865 | }).collect(); |
| 0000000 | 866 | let ret = specialized_method.returns.as_ref() |
| 0000000 | 867 | .map(crate::plumTypeFromAst) |
| 0000000 | 868 | .unwrap_or(PlumType::TUnit); |
| 0000000 | 869 | self.methods.insert(key, PlumType::TFun(param_types, Box::new(ret))); |
| 0000000 | 870 | } |
| 0000000 | 871 | } |
| 0000000 | 872 | |
| 0000000 | 873 | /// If `expr` is a bare reference to a payload-free variant of a GENERIC |
| 0000000 | 874 | /// enum (`None`), and `expected` names a SPECIFIC specialization of that |
| 0000000 | 875 | /// same enum (`Option$Node$Int`), rewrites `expr`'s name to that |
| 0000000 | 876 | /// specialization's own mangled variant name (`None$Node$Int`) and |
| 0000000 | 877 | /// ensures that specialization is registered — codegen only ever knows |
| 0000000 | 878 | /// about specializations (the generic template enum is dropped entirely), |
| 0000000 | 879 | /// so an un-rewritten bare reference would be unresolvable by the time it |
| 0000000 | 880 | /// gets there. No-op if `expr` isn't a bare generic-enum variant, or |
| 0000000 | 881 | /// `expected` doesn't name a specialization of the SAME enum. |
| 0000000 | 882 | fn resolveBareVariantAgainstExpected(&mut self, expr: &mut ast::Expr, expected: &PlumType) { |
| 0000000 | 883 | let ast::Expr::TypeName(n) = expr else { return }; |
| 0000000 | 884 | let Some(e) = self.enums_generic_by_variant.get(n.as_str()).copied() else { return }; |
| 0000000 | 885 | let PlumType::TNamed(mangled) = expected else { return }; |
| 0000000 | 886 | if !mangled.starts_with(&format!("{}$", e.name)) { |
| 0000000 | 887 | return; |
| 0000000 | 888 | } |
| 0000000 | 889 | if let Some(table) = self.enum_variant_mangling.get(mangled) { |
| 0000000 | 890 | if let Some(mangled_variant) = table.get(n) { |
| 0000000 | 891 | *n = mangled_variant.clone(); |
| 0000000 | 892 | } |
| 0000000 | 893 | } |
| 0000000 | 894 | } |
| 0000000 | 895 | |
| 3d6f280 | 896 | fn resolveFnInstantiation(&mut self, call: &mut ast::FnCall, env: &TypeEnv) -> Result<(), String> { |
| 22140cf | 897 | let Some(f) = self.fns_generic.get(call.name.as_str()).copied() else { return Ok(()) }; |
| 3d6f280 | 898 | let params = fnGenericParams(f); |
| 22140cf | 899 | let mut bindings: BTreeMap<String, PlumType> = BTreeMap::new(); |
| 22140cf | 900 | for (param, arg) in f.params.iter().zip(call.args.iter()) { |
| 22140cf | 901 | let gp = match ¶m.ty { |
| 22140cf | 902 | ast::ParamType::Type(t) => t.name.clone(), |
| 22140cf | 903 | ast::ParamType::Variadic(t) => t.name.clone(), |
| d7e5ff4 | 904 | // TODO: fn-value params don't yet resolve to a generic parameter. |
| d7e5ff4 | 905 | ast::ParamType::Fn(_, _) => String::new(), |
| 22140cf | 906 | }; |
| 22140cf | 907 | if params.contains(&gp) { |
| 22140cf | 908 | let arg_expr = match arg { |
| 22140cf | 909 | ast::Arg::Positional(e) => e, |
| 22140cf | 910 | ast::Arg::Keyword { value, .. } => value, |
| 22140cf | 911 | ast::Arg::Pair { value, .. } => value, |
| 22140cf | 912 | }; |
| 22140cf | 913 | bindings.entry(gp).or_insert_with(|| self.infer(arg_expr, env)); |
| 22140cf | 914 | } |
| 22140cf | 915 | } |
| 22140cf | 916 | if bindings.len() != params.len() { |
| 22140cf | 917 | return Err(format!( |
| 22140cf | 918 | "monomorphize: could not resolve all generic parameters for '{}' at this call site", |
| 22140cf | 919 | call.name |
| 22140cf | 920 | )); |
| 22140cf | 921 | } |
| 22140cf | 922 | let type_args: Vec<PlumType> = params.iter().map(|p| bindings[p].clone()).collect(); |
| 22140cf | 923 | let mangled = mangle(&call.name, &type_args); |
| 22140cf | 924 | if !self.specialized.contains(&mangled) && !self.enqueued.contains(&mangled) { |
| 22140cf | 925 | self.enqueued.insert(mangled.clone()); |
| 22140cf | 926 | self.worklist.push(PendingSpecialization::Fn { base: f, subst: Substitution(bindings), mangled: mangled.clone(), new_receiver: None }); |
| 22140cf | 927 | } |
| 22140cf | 928 | call.name = mangled; |
| 22140cf | 929 | Ok(()) |
| 22140cf | 930 | } |
| 22140cf | 931 | |
| 2216237 | 932 | /// The bare names of any generic class or enum referenced directly (not via a |
| 2216237 | 933 | /// lowercase-letter generic parameter) in `f`'s param types — e.g. `"Option"` for |
| 2216237 | 934 | /// `unwrapOr(o: Option, default: Int) -> Int`. See `fns_bare_generic`'s doc |
| 2216237 | 935 | /// comment for why such a function needs its own specialization. |
| 3d6f280 | 936 | fn fnBareGenericRefs(&self, f: &ast::Fn) -> Vec<String> { |
| 2216237 | 937 | let mut names: Vec<String> = Vec::new(); |
| 2216237 | 938 | for p in &f.params { |
| 2216237 | 939 | let n = match &p.ty { |
| 2216237 | 940 | ast::ParamType::Type(t) => &t.name, |
| 2216237 | 941 | ast::ParamType::Variadic(t) => &t.name, |
| d7e5ff4 | 942 | // TODO: fn-value params don't yet participate in bare-generic resolution. |
| d7e5ff4 | 943 | ast::ParamType::Fn(_, _) => continue, |
| 2216237 | 944 | }; |
| 2216237 | 945 | if (self.classes_generic.contains_key(n.as_str()) || self.enums_generic_by_name.contains_key(n.as_str())) |
| 2216237 | 946 | && !names.iter().any(|x| x == n) |
| 2216237 | 947 | { |
| 2216237 | 948 | names.push(n.clone()); |
| 2216237 | 949 | } |
| 2216237 | 950 | } |
| 2216237 | 951 | names |
| 2216237 | 952 | } |
| 2216237 | 953 | |
| 2216237 | 954 | /// Resolves a call to an otherwise-ordinary function whose param type(s) |
| 2216237 | 955 | /// bare-name a generic class/enum, specializing it per call site exactly like a |
| 2216237 | 956 | /// truly-generic function — reusing the same `PendingSpecialization::Fn` |
| 3d6f280 | 957 | /// worklist entry and the unmodified `specializeFn`, whose substitution |
| 2216237 | 958 | /// mechanism already replaces any type whose bare name matches a substitution |
| 2216237 | 959 | /// key (it doesn't care whether that key came from a lowercase-letter generic |
| 2216237 | 960 | /// parameter or a bare generic class/enum reference). |
| 3d6f280 | 961 | fn resolveBareGenericFnInstantiation(&mut self, call: &mut ast::FnCall, env: &TypeEnv) -> Result<(), String> { |
| 2216237 | 962 | let Some(f) = self.fns_bare_generic.get(call.name.as_str()).copied() else { return Ok(()) }; |
| 3d6f280 | 963 | let refs = self.fnBareGenericRefs(f); |
| 2216237 | 964 | let mut bindings: BTreeMap<String, PlumType> = BTreeMap::new(); |
| 2216237 | 965 | for (param, arg) in f.params.iter().zip(call.args.iter()) { |
| 2216237 | 966 | let n = match ¶m.ty { |
| 2216237 | 967 | ast::ParamType::Type(t) => t.name.clone(), |
| 2216237 | 968 | ast::ParamType::Variadic(t) => t.name.clone(), |
| d7e5ff4 | 969 | // TODO: fn-value params don't yet resolve to a bare generic reference. |
| d7e5ff4 | 970 | ast::ParamType::Fn(_, _) => String::new(), |
| 2216237 | 971 | }; |
| 2216237 | 972 | if refs.contains(&n) { |
| 2216237 | 973 | let arg_expr = match arg { |
| 2216237 | 974 | ast::Arg::Positional(e) => e, |
| 2216237 | 975 | ast::Arg::Keyword { value, .. } => value, |
| 2216237 | 976 | ast::Arg::Pair { value, .. } => value, |
| 2216237 | 977 | }; |
| 2216237 | 978 | bindings.entry(n).or_insert_with(|| self.infer(arg_expr, env)); |
| 2216237 | 979 | } |
| 2216237 | 980 | } |
| 2216237 | 981 | if bindings.len() != refs.len() { |
| 2216237 | 982 | return Err(format!( |
| 2216237 | 983 | "monomorphize: could not resolve all generic parameters for '{}' at this call site", |
| 2216237 | 984 | call.name |
| 2216237 | 985 | )); |
| 2216237 | 986 | } |
| 2216237 | 987 | let type_args: Vec<PlumType> = refs.iter().map(|p| bindings[p].clone()).collect(); |
| 2216237 | 988 | let mangled = mangle(&call.name, &type_args); |
| 2216237 | 989 | if !self.specialized.contains(&mangled) && !self.enqueued.contains(&mangled) { |
| 2216237 | 990 | self.enqueued.insert(mangled.clone()); |
| 2216237 | 991 | self.worklist.push(PendingSpecialization::Fn { base: f, subst: Substitution(bindings), mangled: mangled.clone(), new_receiver: None }); |
| 2216237 | 992 | } |
| 2216237 | 993 | call.name = mangled; |
| 2216237 | 994 | Ok(()) |
| 2216237 | 995 | } |
| 2216237 | 996 | |
| c273ea5 | 997 | /// Resolves a construction of a generic enum's variant (e.g. `Some(5)` for |
| 2216237 | 998 | /// `enum Option = | Some(a) | None`), rewriting `call.name` from the bare |
| 2216237 | 999 | /// variant name (`Some`) to its mangled form (`Some$Int`) once the enum's own |
| 2216237 | 1000 | /// concrete instantiation is known. Mangling is eager and deterministic — it |
| 2216237 | 1001 | /// doesn't wait for the worklist to actually produce the specialized `ast::Enum` |
| 2216237 | 1002 | /// (see `enum_variant_mangling`'s doc comment). |
| c273ea5 | 1003 | /// |
| c273ea5 | 1004 | /// A variant that carries no generic fields (e.g. `None`) can't pin down the |
| c273ea5 | 1005 | /// enum's type parameters on its own, so such a construction site is left alone |
| c273ea5 | 1006 | /// here — some other construction site (e.g. `Some(5)`) is what drives the |
| 2216237 | 1007 | /// specialization. (A bare `None` used as a *value*, not a call, is |
| 2216237 | 1008 | /// `ast::Expr::TypeName` and doesn't go through this function at all — see the |
| 2216237 | 1009 | /// plan's Global Constraints for that narrower, documented residual limitation.) |
| 3d6f280 | 1010 | fn resolveEnumInstantiation(&mut self, call: &mut ast::FnCall, env: &TypeEnv) -> Result<(), String> { |
| c273ea5 | 1011 | let Some(e) = self.enums_generic_by_variant.get(call.name.as_str()).copied() else { return Ok(()) }; |
| 3d6f280 | 1012 | let params = enumGenericParams(e); |
| c273ea5 | 1013 | let Some(variant) = e.variants.iter().find(|v| v.name == call.name) else { return Ok(()) }; |
| c273ea5 | 1014 | let mut bindings: BTreeMap<String, PlumType> = BTreeMap::new(); |
| c273ea5 | 1015 | for (field_ty_name, arg) in variant.fields.iter().zip(call.args.iter()) { |
| c273ea5 | 1016 | if params.contains(field_ty_name) { |
| c273ea5 | 1017 | let arg_expr = match arg { |
| c273ea5 | 1018 | ast::Arg::Positional(e) => e, |
| c273ea5 | 1019 | ast::Arg::Keyword { value, .. } => value, |
| c273ea5 | 1020 | ast::Arg::Pair { value, .. } => value, |
| c273ea5 | 1021 | }; |
| c273ea5 | 1022 | bindings.entry(field_ty_name.clone()).or_insert_with(|| self.infer(arg_expr, env)); |
| c273ea5 | 1023 | } |
| c273ea5 | 1024 | } |
| c273ea5 | 1025 | // This single construction site couldn't pin down every generic parameter |
| 0000000 | 1026 | // by itself — e.g. `Ok(5)` only ever supplies `Result`'s `T`, never its `E` |
| 0000000 | 1027 | // (no `Ok` call site can, since `Err`'s payload is a disjoint field). Fall |
| 0000000 | 1028 | // back to the enclosing function's declared return type, if it names this |
| 0000000 | 1029 | // same enum with an explicit, fully-general `[...]` instantiation (e.g. |
| 0000000 | 1030 | // `-> Result[Int, Str]`) — that's the one other place a type this |
| 0000000 | 1031 | // construction site can't see on its own is written down on purpose. |
| 0000000 | 1032 | if bindings.len() != params.len() { |
| 0000000 | 1033 | if let Some(rt) = &self.current_return_type { |
| 0000000 | 1034 | if rt.name == e.name && rt.generics.len() == params.len() { |
| 0000000 | 1035 | for (p, gt) in params.iter().zip(rt.generics.iter()) { |
| 0000000 | 1036 | bindings.entry(p.clone()).or_insert_with(|| crate::plumTypeFromAst(gt)); |
| 0000000 | 1037 | } |
| 0000000 | 1038 | } |
| 0000000 | 1039 | } |
| 0000000 | 1040 | } |
| 0000000 | 1041 | // Still couldn't pin down every generic parameter (e.g. a payload-free |
| 0000000 | 1042 | // `None`, or no informative return-type annotation either). Leave it for |
| 0000000 | 1043 | // another site to drive. |
| c273ea5 | 1044 | if bindings.len() != params.len() { |
| c273ea5 | 1045 | return Ok(()); |
| c273ea5 | 1046 | } |
| 0000000 | 1047 | let mangled = self.ensureEnumSpecialized(e, ¶ms, bindings); |
| 0000000 | 1048 | call.name = self.enum_variant_mangling[&mangled][&variant.name].clone(); |
| 0000000 | 1049 | Ok(()) |
| 0000000 | 1050 | } |
| 0000000 | 1051 | |
| 0000000 | 1052 | /// Registers (if not already registered) the specialization of generic enum |
| 0000000 | 1053 | /// `e` at `bindings` — mangling every variant name, teaching `self.enum_variants` |
| 0000000 | 1054 | /// about each mangled variant (so `self.infer` on an already-rewritten |
| 0000000 | 1055 | /// construction site resolves correctly instead of falling back to an |
| 0000000 | 1056 | /// uninformative `TVar`), and enqueueing the specialization to actually be |
| 0000000 | 1057 | /// produced. Returns the mangled enum name. Shared by `resolveEnumInstantiation` |
| 0000000 | 1058 | /// (bindings inferred from a construction site's own args, falling back to the |
| 0000000 | 1059 | /// enclosing return type) and `resolveCallReturnType` (bindings taken directly |
| 0000000 | 1060 | /// from a callee's *own* declared return type, with no construction site at all |
| 0000000 | 1061 | /// — see its doc comment). |
| 0000000 | 1062 | fn ensureEnumSpecialized(&mut self, e: &'a ast::Enum, params: &[String], bindings: BTreeMap<String, PlumType>) -> String { |
| c273ea5 | 1063 | let type_args: Vec<PlumType> = params.iter().map(|p| bindings[p].clone()).collect(); |
| c273ea5 | 1064 | let mangled = mangle(&e.name, &type_args); |
| 2216237 | 1065 | if !self.enum_variant_mangling.contains_key(&mangled) { |
| 2216237 | 1066 | let mut table = BTreeMap::new(); |
| 2216237 | 1067 | for (tag, v) in e.variants.iter().enumerate() { |
| 2216237 | 1068 | let mangled_variant = mangle(&v.name, &type_args); |
| 2216237 | 1069 | table.insert(v.name.clone(), mangled_variant.clone()); |
| 2216237 | 1070 | let field_types: Vec<PlumType> = v.fields.iter().map(|f| { |
| 2216237 | 1071 | bindings.get(f).cloned().unwrap_or_else(|| { |
| 3d6f280 | 1072 | crate::plumTypeFromAst(&ast::Type { name: f.clone(), generics: vec![] }) |
| 2216237 | 1073 | }) |
| 2216237 | 1074 | }).collect(); |
| 2216237 | 1075 | self.enum_variants.insert(mangled_variant, EnumVariantInfo { |
| 2216237 | 1076 | enum_name: mangled.clone(), |
| 2216237 | 1077 | tag: tag as i32, |
| 2216237 | 1078 | field_types, |
| 4fda634 | 1079 | values: v.values.clone(), |
| 2216237 | 1080 | }); |
| 2216237 | 1081 | } |
| 2216237 | 1082 | self.enum_variant_mangling.insert(mangled.clone(), table); |
| 2216237 | 1083 | } |
| c273ea5 | 1084 | if !self.specialized.contains(&mangled) && !self.enqueued.contains(&mangled) { |
| c273ea5 | 1085 | self.enqueued.insert(mangled.clone()); |
| 2216237 | 1086 | self.worklist.push(PendingSpecialization::Enum { base: e, subst: Substitution(bindings), mangled: mangled.clone() }); |
| c273ea5 | 1087 | } |
| 0000000 | 1088 | mangled |
| 0000000 | 1089 | } |
| 0000000 | 1090 | |
| 0000000 | 1091 | /// Fully resolves a FIELD's declared type into something that will actually |
| 0000000 | 1092 | /// exist after monomorphization. `specializeClass`'s own field substitution |
| 0000000 | 1093 | /// only replaces a bare generic-param NAME (`T` -> `Int`) — a field declared |
| 0000000 | 1094 | /// `Option[Node[T]]` becomes `Option[Node[Int]]` this way, which is now |
| 0000000 | 1095 | /// fully CONCRETE but still names the generic TEMPLATES `Option`/`Node` |
| 0000000 | 1096 | /// directly, both of which monomorphization deletes from the output (only |
| 0000000 | 1097 | /// their mangled specializations, e.g. `Node$Int`, survive). Recursively |
| 0000000 | 1098 | /// resolves any nested generic arguments first (so `Node[Int]` inside |
| 0000000 | 1099 | /// `Option[Node[Int]]` becomes `Node$Int` before `Option[...]` itself is |
| 0000000 | 1100 | /// resolved), then — if the type names a known generic class/enum applied |
| 0000000 | 1101 | /// to arguments — mangles it to that specialization's real name and |
| 0000000 | 1102 | /// enqueues the specialization if it hasn't been already (via the same |
| 0000000 | 1103 | /// `ensureEnumSpecialized` used for enum construction sites, for enums; classes |
| 0000000 | 1104 | /// don't have an equivalent shared helper, so that half is inlined here). |
| 0000000 | 1105 | /// A field that's already concrete (no generics), or whose name isn't a |
| 0000000 | 1106 | /// known generic template, is returned unchanged (or with just its nested |
| 0000000 | 1107 | /// generics resolved) — this is ALSO called on every ordinary (non-generic) |
| 0000000 | 1108 | /// class's fields, not just specialized ones, since a plain class can |
| 0000000 | 1109 | /// perfectly well have a field like `items: List[Int]`. |
| 0000000 | 1110 | fn resolveFieldType(&mut self, ty: &ast::Type) -> ast::Type { |
| 0000000 | 1111 | if ty.generics.is_empty() { |
| 0000000 | 1112 | return ty.clone(); |
| 0000000 | 1113 | } |
| 0000000 | 1114 | let resolved_args: Vec<ast::Type> = ty.generics.iter().map(|g| self.resolveFieldType(g)).collect(); |
| 0000000 | 1115 | // A type argument that's STILL a bare single-uppercase-letter name after |
| 0000000 | 1116 | // resolving means it's a truly free type variable at this point in the |
| 0000000 | 1117 | // pipeline — e.g. `List[U]` inside `List[T]`'s own `map` method, where |
| 0000000 | 1118 | // `U` is `map`'s OWN generic param, not `List`'s `T` (already substituted |
| 0000000 | 1119 | // to a concrete type by the time this runs). Mangling/specializing |
| 0000000 | 1120 | // against a placeholder name would silently manufacture a bogus |
| 0000000 | 1121 | // `List$U` "specialization" baked from the letter U itself, so leave the |
| 0000000 | 1122 | // whole type unresolved instead — see the `map`/method-level-generics |
| 0000000 | 1123 | // gap noted in `libs/std/list.plum`. |
| 0000000 | 1124 | if resolved_args.iter().any(|a| isGenericParamName(&a.name)) { |
| 0000000 | 1125 | return ast::Type { name: ty.name.clone(), generics: resolved_args }; |
| 0000000 | 1126 | } |
| 0000000 | 1127 | let type_args: Vec<PlumType> = resolved_args.iter().map(crate::plumTypeFromAst).collect(); |
| 0000000 | 1128 | |
| 0000000 | 1129 | if let Some(class) = self.classes_generic.get(ty.name.as_str()).copied() { |
| 0000000 | 1130 | let params = classGenericParams(class); |
| 0000000 | 1131 | if params.len() == type_args.len() { |
| 0000000 | 1132 | let bindings: BTreeMap<String, PlumType> = params.into_iter().zip(type_args.iter().cloned()).collect(); |
| 0000000 | 1133 | let mangled = mangle(&ty.name, &type_args); |
| 0000000 | 1134 | if !self.specialized.contains(&mangled) && !self.enqueued.contains(&mangled) { |
| 0000000 | 1135 | self.enqueued.insert(mangled.clone()); |
| 0000000 | 1136 | self.worklist.push(PendingSpecialization::Class { base: class, subst: Substitution(bindings.clone()), mangled: mangled.clone() }); |
| 0000000 | 1137 | } |
| 0000000 | 1138 | // Register this specialization's field types right now, even |
| 0000000 | 1139 | // though its actual `Item::Class`/method output is only pushed |
| 0000000 | 1140 | // to `m.produced` once the worklist entry above is popped — |
| 0000000 | 1141 | // another method being rewritten in THIS SAME worklist round |
| 0000000 | 1142 | // (e.g. a sibling method on the class currently being |
| 0000000 | 1143 | // specialized) may need to resolve a field access against it |
| 0000000 | 1144 | // immediately, well before that later worklist entry runs. |
| 0000000 | 1145 | if !self.classes.contains_key(&mangled) { |
| 0000000 | 1146 | // Insert a placeholder BEFORE recursing into the fields below — |
| 0000000 | 1147 | // a self-referential class (`Node[T]`'s own `prev`/`next` fields |
| 0000000 | 1148 | // point back to `Option[Node[T]]`) would otherwise recurse into |
| 0000000 | 1149 | // resolving its own not-yet-registered specialization forever. |
| 0000000 | 1150 | // The recursive re-entry only needs this specialization's |
| 0000000 | 1151 | // MANGLED NAME to build its own field's type, not its fields — |
| 0000000 | 1152 | // those get filled in for real below once the recursion unwinds. |
| 0000000 | 1153 | self.classes.insert(mangled.clone(), vec![]); |
| 0000000 | 1154 | let mut spec_class = specializeClass(class, &Substitution(bindings.clone()), &mangled); |
| 0000000 | 1155 | for f in &mut spec_class.fields { |
| 0000000 | 1156 | f.ty = self.resolveFieldType(&f.ty); |
| 0000000 | 1157 | } |
| 0000000 | 1158 | self.classes.insert( |
| 0000000 | 1159 | mangled.clone(), |
| 0000000 | 1160 | spec_class.fields.iter().map(|f| (f.name.clone(), crate::plumTypeFromAst(&f.ty))).collect(), |
| 0000000 | 1161 | ); |
| 0000000 | 1162 | self.registerClassMethodSignatures(class, &mangled, &bindings); |
| 0000000 | 1163 | } |
| 0000000 | 1164 | return ast::Type { name: mangled, generics: vec![] }; |
| 0000000 | 1165 | } |
| 0000000 | 1166 | } |
| 0000000 | 1167 | if let Some(e) = self.enums_generic_by_name.get(ty.name.as_str()).copied() { |
| 0000000 | 1168 | let params = enumGenericParams(e); |
| 0000000 | 1169 | if params.len() == type_args.len() { |
| 0000000 | 1170 | let bindings: BTreeMap<String, PlumType> = params.iter().cloned().zip(type_args.iter().cloned()).collect(); |
| 0000000 | 1171 | let mangled = self.ensureEnumSpecialized(e, ¶ms, bindings); |
| 0000000 | 1172 | return ast::Type { name: mangled, generics: vec![] }; |
| 0000000 | 1173 | } |
| 0000000 | 1174 | } |
| 0000000 | 1175 | ast::Type { name: ty.name.clone(), generics: resolved_args } |
| 0000000 | 1176 | } |
| 0000000 | 1177 | |
| 0000000 | 1178 | /// Resolves every generic-with-args type in `f`'s own signature (params, |
| 0000000 | 1179 | /// return) via `resolveFieldType`, mutating `f` in place. Must run before |
| 0000000 | 1180 | /// `f` is pushed to `m.produced` — the real checker rebuilds its tables |
| 0000000 | 1181 | /// FRESH from that final AST (via `buildGlobalTables`, using the plain |
| 0000000 | 1182 | /// generics-dropping `plumTypeFromAst`), so whatever a signature still |
| 0000000 | 1183 | /// says at that point is what the checker sees; a bare unresolved `Node` |
| 0000000 | 1184 | /// or `List[Int]` left in a param would either dangle or get its generics |
| 0000000 | 1185 | /// silently dropped again. |
| 0000000 | 1186 | fn resolveFnSignature(&mut self, f: &mut ast::Fn) { |
| 0000000 | 1187 | self.resolveFnParamTypes(f); |
| 0000000 | 1188 | self.resolveFnReturnType(f); |
| 0000000 | 1189 | } |
| 0000000 | 1190 | |
| 0000000 | 1191 | /// Just the params half of `resolveFnSignature` — deliberately split out |
| 0000000 | 1192 | /// so callers can run this BEFORE `rewriteFnBody` (env seeding needs |
| 0000000 | 1193 | /// param types already flattened, e.g. `Node[T]` -> `Node$Int`, or a |
| 0000000 | 1194 | /// field access on a param would fail to resolve) while leaving |
| 0000000 | 1195 | /// `f.returns` untouched until AFTER the body's been rewritten. The body |
| 0000000 | 1196 | /// rewrite reads `self.current_return_type` (seeded from `f.returns` as |
| 0000000 | 1197 | /// originally declared, generics and all) to drive |
| 0000000 | 1198 | /// `resolveEnumInstantiation`'s fallback for construction sites that |
| 0000000 | 1199 | /// can't infer every generic param from their own arguments alone (e.g. |
| 0000000 | 1200 | /// `Err("...")` needs `Result`'s OTHER param, `T`, from the function's |
| 0000000 | 1201 | /// own `-> Result[Int, Str]` declaration) — flattening the return type |
| 0000000 | 1202 | /// up front would replace `rt.name` with an already-mangled name that |
| 0000000 | 1203 | /// fallback's own bare-template-name comparison can never match again. |
| 0000000 | 1204 | fn resolveFnParamTypes(&mut self, f: &mut ast::Fn) { |
| 0000000 | 1205 | for p in &mut f.params { |
| 0000000 | 1206 | match &mut p.ty { |
| 0000000 | 1207 | ast::ParamType::Type(t) => *t = self.resolveFieldType(t), |
| 0000000 | 1208 | ast::ParamType::Variadic(t) => *t = self.resolveFieldType(t), |
| 0000000 | 1209 | ast::ParamType::Fn(_, _) => {} |
| 0000000 | 1210 | } |
| 0000000 | 1211 | } |
| 0000000 | 1212 | } |
| 0000000 | 1213 | |
| 0000000 | 1214 | /// The returns half of `resolveFnSignature` — see `resolveFnParamTypes`'s |
| 0000000 | 1215 | /// doc comment for why this must run AFTER `rewriteFnBody`, not before. |
| 0000000 | 1216 | fn resolveFnReturnType(&mut self, f: &mut ast::Fn) { |
| 0000000 | 1217 | if let Some(r) = &mut f.returns { |
| 0000000 | 1218 | *r = self.resolveFieldType(r); |
| 0000000 | 1219 | } |
| 0000000 | 1220 | } |
| 0000000 | 1221 | |
| 0000000 | 1222 | /// A construction site (`Ok(5)`) only ever tells us about the TYPE that's being |
| 0000000 | 1223 | /// PRODUCED — it says nothing about code further downstream that CONSUMES an |
| 0000000 | 1224 | /// already-specialized generic-enum value returned from calling some other |
| 0000000 | 1225 | /// (already fully concrete, non-generic) function or method, e.g. |
| 0000000 | 1226 | /// `match parseIt() { Ok(v) => ... }` where `fun parseIt() -> Result[Int, Str]`. |
| 0000000 | 1227 | /// `self.infer` can't help there either: it's built from `buildGlobalTables`, |
| 0000000 | 1228 | /// which (like every other `PlumType` site) drops a declared type's generic |
| 0000000 | 1229 | /// args entirely (`plumTypeFromAst` maps `Result[Int, Str]` to the bare |
| 0000000 | 1230 | /// `TNamed("Result")`) — so it has no way to know this call's result is the |
| 0000000 | 1231 | /// SPECIALIZED `Result$Int$Str`, not the generic template. |
| 0000000 | 1232 | /// |
| 0000000 | 1233 | /// This resolves that one specific, common shape directly from the callee's own |
| 0000000 | 1234 | /// declaration (recorded in `fn_return_generic_enum` during classification) — |
| 0000000 | 1235 | /// bypassing `self.infer` entirely, since the answer is already fully known |
| 0000000 | 1236 | /// from the signature and doesn't depend on this call site's arguments at all. |
| 0000000 | 1237 | /// Returns `None` for anything else (an ordinary call, a call to a function |
| 0000000 | 1238 | /// whose return isn't a generic-enum instantiation, ...), meaning "fall back to |
| 0000000 | 1239 | /// `self.infer` as before." |
| 0000000 | 1240 | fn resolveCallReturnType(&mut self, e: &ast::Expr, env: &TypeEnv) -> Option<PlumType> { |
| 0000000 | 1241 | let (recv, name) = match e { |
| 0000000 | 1242 | ast::Expr::FnCall(call) => (None, call.name.clone()), |
| 0000000 | 1243 | ast::Expr::Attribute(attr) => match &attr.attr { |
| 0000000 | 1244 | ast::AttrKind::Method(call) => { |
| 0000000 | 1245 | let recv = match crate::methodReceiverName(&self.infer(&attr.object, env)) { |
| 0000000 | 1246 | Some(r) => r, |
| 0000000 | 1247 | None => return None, |
| 0000000 | 1248 | }; |
| 0000000 | 1249 | (Some(recv), call.name.clone()) |
| 0000000 | 1250 | } |
| 0000000 | 1251 | ast::AttrKind::Field(_) => return None, |
| 0000000 | 1252 | }, |
| 0000000 | 1253 | _ => return None, |
| 0000000 | 1254 | }; |
| 0000000 | 1255 | let (enum_ref, type_args) = self.fn_return_generic_enum.get(&(recv, name))?.clone(); |
| 0000000 | 1256 | let params = enumGenericParams(enum_ref); |
| 0000000 | 1257 | let bindings: BTreeMap<String, PlumType> = params.iter().cloned().zip(type_args).collect(); |
| 0000000 | 1258 | Some(PlumType::TNamed(self.ensureEnumSpecialized(enum_ref, ¶ms, bindings))) |
| c273ea5 | 1259 | } |
| c273ea5 | 1260 | |
| 3d6f280 | 1261 | fn rewriteExpr(&mut self, expr: &mut ast::Expr, env: &TypeEnv) -> Result<(), String> { |
| 22140cf | 1262 | match expr { |
| 22140cf | 1263 | ast::Expr::ClassCall(call) => { |
| 0000000 | 1264 | // Runs FIRST (before the generic per-field rewrite below): a |
| 0000000 | 1265 | // bare payload-free variant field value (`Node(..., next: |
| 0000000 | 1266 | // None)`) carries no type of its own to infer a generic |
| 0000000 | 1267 | // param from, and needs the class's OWN (about-to-be- |
| 0000000 | 1268 | // specialized) field type to resolve which specialization it |
| 0000000 | 1269 | // actually means — `resolveClassInstantiation` handles that |
| 0000000 | 1270 | // internally once it knows the full binding set. |
| 0000000 | 1271 | self.resolveClassInstantiation(call, env)?; |
| 22140cf | 1272 | for fa in &mut call.fields { |
| 3d6f280 | 1273 | self.rewriteExpr(&mut fa.value, env)?; |
| 22140cf | 1274 | } |
| 22140cf | 1275 | } |
| 22140cf | 1276 | ast::Expr::FnCall(call) => { |
| 22140cf | 1277 | for arg in &mut call.args { |
| 22140cf | 1278 | let e = match arg { |
| 22140cf | 1279 | ast::Arg::Positional(e) => e, |
| 22140cf | 1280 | ast::Arg::Keyword { value, .. } => value, |
| 22140cf | 1281 | ast::Arg::Pair { value, .. } => value, |
| 22140cf | 1282 | }; |
| 3d6f280 | 1283 | self.rewriteExpr(e, env)?; |
| 22140cf | 1284 | } |
| c273ea5 | 1285 | // A `FnCall` may name either a generic free function or a generic |
| c273ea5 | 1286 | // enum's variant; the two name spaces don't overlap (variants are |
| 2216237 | 1287 | // capitalized). Enum resolution runs first and rewrites `call.name` |
| 2216237 | 1288 | // to its mangled form when it resolves — `fns_generic` is keyed by |
| 2216237 | 1289 | // the ORIGINAL unmangled free-function names, so a rewritten variant |
| 2216237 | 1290 | // name can never accidentally match it afterward. |
| 3d6f280 | 1291 | self.resolveEnumInstantiation(call, env)?; |
| 3d6f280 | 1292 | self.resolveFnInstantiation(call, env)?; |
| 3d6f280 | 1293 | self.resolveBareGenericFnInstantiation(call, env)?; |
| 22140cf | 1294 | } |
| 22140cf | 1295 | ast::Expr::Attribute(attr) => { |
| 3d6f280 | 1296 | self.rewriteExpr(&mut attr.object, env)?; |
| 22140cf | 1297 | if let ast::AttrKind::Method(call) = &mut attr.attr { |
| 22140cf | 1298 | for arg in &mut call.args { |
| 22140cf | 1299 | let e = match arg { |
| 22140cf | 1300 | ast::Arg::Positional(e) => e, |
| 22140cf | 1301 | ast::Arg::Keyword { value, .. } => value, |
| 22140cf | 1302 | ast::Arg::Pair { value, .. } => value, |
| 22140cf | 1303 | }; |
| 3d6f280 | 1304 | self.rewriteExpr(e, env)?; |
| 22140cf | 1305 | } |
| 22140cf | 1306 | // Method dispatch on a specialized receiver needs no rewrite here: |
| 22140cf | 1307 | // once the receiver's construction site is rewritten to its mangled |
| 22140cf | 1308 | // class name, the receiver's inferred static type IS that mangled |
| 22140cf | 1309 | // name, and the specialized methods were registered under exactly |
| 22140cf | 1310 | // that (mangled receiver, method name) key when their class was |
| 22140cf | 1311 | // specialized (see the `PendingSpecialization::Class` arm below). |
| 22140cf | 1312 | } |
| 22140cf | 1313 | } |
| 3d6f280 | 1314 | ast::Expr::Binary(b) => { self.rewriteExpr(&mut b.left, env)?; self.rewriteExpr(&mut b.right, env)?; } |
| 3d6f280 | 1315 | ast::Expr::Bool(b) => { self.rewriteExpr(&mut b.left, env)?; self.rewriteExpr(&mut b.right, env)?; } |
| 0000000 | 1316 | ast::Expr::Compare(c) => { |
| 0000000 | 1317 | self.rewriteExpr(&mut c.left, env)?; |
| 0000000 | 1318 | self.rewriteExpr(&mut c.right, env)?; |
| 0000000 | 1319 | // `current != None`: `current`'s side may already be a SPECIFIC |
| 0000000 | 1320 | // enum specialization (e.g. `Option$Node$Int`) while `None` |
| 0000000 | 1321 | // itself is still bare (it carries no type of its own) — |
| 0000000 | 1322 | // resolve each side against the OTHER's type; a no-op unless |
| 0000000 | 1323 | // that side is actually a bare generic-enum variant. |
| 0000000 | 1324 | let lt = self.infer(&c.left, env); |
| 0000000 | 1325 | let rt = self.infer(&c.right, env); |
| 0000000 | 1326 | self.resolveBareVariantAgainstExpected(&mut c.left, &rt); |
| 0000000 | 1327 | self.resolveBareVariantAgainstExpected(&mut c.right, <); |
| 0000000 | 1328 | } |
| 3d6f280 | 1329 | ast::Expr::Not(inner) => self.rewriteExpr(inner, env)?, |
| 3d6f280 | 1330 | ast::Expr::Unary(u) => self.rewriteExpr(&mut u.operand, env)?, |
| 3d6f280 | 1331 | ast::Expr::Paren(inner) => self.rewriteExpr(inner, env)?, |
| 22140cf | 1332 | ast::Expr::Ternary(t) => { |
| 3d6f280 | 1333 | self.rewriteExpr(&mut t.condition, env)?; |
| 3d6f280 | 1334 | self.rewriteExpr(&mut t.then, env)?; |
| 3d6f280 | 1335 | self.rewriteExpr(&mut t.else_, env)?; |
| 22140cf | 1336 | } |
| 22140cf | 1337 | // String interpolation can embed arbitrary expressions (including generic |
| 22140cf | 1338 | // call sites), so recurse into its interpolated parts. |
| 22140cf | 1339 | ast::Expr::String(s) => { |
| 22140cf | 1340 | for part in &mut s.parts { |
| 22140cf | 1341 | if let ast::StringPart::Interp(e) = part { |
| 3d6f280 | 1342 | self.rewriteExpr(e, env)?; |
| 22140cf | 1343 | } |
| 22140cf | 1344 | } |
| 22140cf | 1345 | } |
| 22140cf | 1346 | ast::Expr::Int(_) | ast::Expr::Float(_) |
| 22140cf | 1347 | | ast::Expr::Self_ | ast::Expr::Var(_) | ast::Expr::TypeName(_) => {} |
| d7e5ff4 | 1348 | // TODO: closure bodies don't yet get rewritten for generic call sites. |
| d7e5ff4 | 1349 | ast::Expr::Closure(_) => {} |
| 22140cf | 1350 | } |
| 22140cf | 1351 | Ok(()) |
| 22140cf | 1352 | } |
| 22140cf | 1353 | } |
| 22140cf | 1354 | |
| 22140cf | 1355 | /// Runs the whole generics-monomorphization pass over `source`, producing a plain, |
| 22140cf | 1356 | /// fully-concrete `ast::Source` with every generic `Class`/`Fn`/`Enum` template |
| 22140cf | 1357 | /// replaced by zero or more mangled concrete specializations, and every remaining |
| 22140cf | 1358 | /// item's body rewritten so its call sites reference those mangled names. The |
| 3d6f280 | 1359 | /// result has no generic syntax left in it — `checkSource`/`compileSource` run |
| 22140cf | 1360 | /// on it completely unmodified. |
| 3d6f280 | 1361 | pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> { |
| 3d6f280 | 1362 | let (global_env, classes, methods, enum_variants, enum_params) = crate::buildGlobalTables(source); |
| 22140cf | 1363 | |
| 22140cf | 1364 | let mut m = Monomorphizer { |
| 22140cf | 1365 | classes_generic: BTreeMap::new(), |
| 22140cf | 1366 | fns_generic: BTreeMap::new(), |
| 22140cf | 1367 | methods_generic_on: BTreeMap::new(), |
| 0000000 | 1368 | methods_generic_on_enum: BTreeMap::new(), |
| c273ea5 | 1369 | enums_generic_by_variant: BTreeMap::new(), |
| 2216237 | 1370 | enums_generic_by_name: BTreeMap::new(), |
| 2216237 | 1371 | enum_variant_mangling: BTreeMap::new(), |
| 2216237 | 1372 | fns_bare_generic: BTreeMap::new(), |
| 22140cf | 1373 | global_env, |
| 22140cf | 1374 | classes, |
| 22140cf | 1375 | methods, |
| 22140cf | 1376 | enum_variants, |
| 4fda634 | 1377 | enum_params, |
| 22140cf | 1378 | specialized: BTreeSet::new(), |
| 22140cf | 1379 | enqueued: BTreeSet::new(), |
| 22140cf | 1380 | worklist: Vec::new(), |
| 22140cf | 1381 | produced: Vec::new(), |
| 0000000 | 1382 | current_return_type: None, |
| 0000000 | 1383 | fn_return_generic_enum: BTreeMap::new(), |
| 0000000 | 1384 | local_types_by_name: BTreeMap::new(), |
| 0000000 | 1385 | rename_counter: 0, |
| 22140cf | 1386 | }; |
| 22140cf | 1387 | |
| 22140cf | 1388 | for item in &source.items { |
| 22140cf | 1389 | match item { |
| 22140cf | 1390 | ast::Item::Class(c) if !c.generics.is_empty() => { m.classes_generic.insert(c.name.clone(), c); } |
| 3d6f280 | 1391 | ast::Item::Enum(e) if !enumGenericParams(e).is_empty() => { |
| 2216237 | 1392 | m.enums_generic_by_name.insert(e.name.clone(), e); |
| c273ea5 | 1393 | for v in &e.variants { |
| c273ea5 | 1394 | m.enums_generic_by_variant.insert(v.name.clone(), e); |
| c273ea5 | 1395 | } |
| c273ea5 | 1396 | } |
| 22140cf | 1397 | _ => {} |
| 22140cf | 1398 | } |
| 22140cf | 1399 | } |
| 0000000 | 1400 | // Any function/method (generic or not — this doesn't care either way) whose |
| 0000000 | 1401 | // OWN declared return type fully instantiates a known generic enum. Must run |
| 0000000 | 1402 | // after the loop above (needs `enums_generic_by_name` filled) but is otherwise |
| 0000000 | 1403 | // independent of every other classification pass here. |
| 0000000 | 1404 | for item in &source.items { |
| 0000000 | 1405 | if let ast::Item::Fn(f) = item { |
| 0000000 | 1406 | if let Some(rt) = &f.returns { |
| 0000000 | 1407 | if let Some(e) = m.enums_generic_by_name.get(rt.name.as_str()).copied() { |
| 0000000 | 1408 | let params = enumGenericParams(e); |
| 0000000 | 1409 | if !rt.generics.is_empty() && rt.generics.len() == params.len() { |
| 0000000 | 1410 | let type_args: Vec<PlumType> = rt.generics.iter().map(crate::plumTypeFromAst).collect(); |
| 0000000 | 1411 | m.fn_return_generic_enum.insert((f.type_param.clone(), f.name.clone()), (e, type_args)); |
| 0000000 | 1412 | } |
| 0000000 | 1413 | } |
| 0000000 | 1414 | } |
| 0000000 | 1415 | } |
| 0000000 | 1416 | } |
| 0000000 | 1417 | |
| 22140cf | 1418 | for item in &source.items { |
| 22140cf | 1419 | if let ast::Item::Fn(f) = item { |
| 0000000 | 1420 | let receiver_is_generic_class = f.type_param.as_deref().map(|r| m.classes_generic.contains_key(r)).unwrap_or(false); |
| 0000000 | 1421 | let receiver_is_generic_enum = f.type_param.as_deref().map(|r| m.enums_generic_by_name.contains_key(r)).unwrap_or(false); |
| 0000000 | 1422 | if receiver_is_generic_class { |
| 22140cf | 1423 | m.methods_generic_on.entry(f.type_param.clone().unwrap()).or_default().push(f); |
| 0000000 | 1424 | } else if receiver_is_generic_enum { |
| 0000000 | 1425 | m.methods_generic_on_enum.entry(f.type_param.clone().unwrap()).or_default().push(f); |
| 3d6f280 | 1426 | } else if f.type_param.is_none() && !fnGenericParams(f).is_empty() { |
| 22140cf | 1427 | m.fns_generic.insert(f.name.clone(), f); |
| 3d6f280 | 1428 | } else if f.type_param.is_none() && !m.fnBareGenericRefs(f).is_empty() { |
| 2216237 | 1429 | m.fns_bare_generic.insert(f.name.clone(), f); |
| 22140cf | 1430 | } |
| 22140cf | 1431 | // A method whose receiver is NOT generic is left as a regular method below, |
| 22140cf | 1432 | // even if its own params/return happen to use a bare lowercase-letter type |
| 2216237 | 1433 | // name, or bare-name a generic class/enum — those shapes are out of scope |
| 2216237 | 1434 | // for this pass; see the plan's Global Constraints. |
| 22140cf | 1435 | } |
| 22140cf | 1436 | } |
| 22140cf | 1437 | |
| 22140cf | 1438 | for item in &source.items { |
| 22140cf | 1439 | match item { |
| 0000000 | 1440 | ast::Item::Class(c) if c.generics.is_empty() => { |
| 0000000 | 1441 | let mut c2 = c.clone(); |
| 0000000 | 1442 | for f in &mut c2.fields { |
| 0000000 | 1443 | f.ty = m.resolveFieldType(&f.ty); |
| 0000000 | 1444 | } |
| 0000000 | 1445 | m.produced.push(ast::Item::Class(c2)); |
| 0000000 | 1446 | } |
| 3d6f280 | 1447 | ast::Item::Enum(e) if enumGenericParams(e).is_empty() => m.produced.push(ast::Item::Enum(e.clone())), |
| 22140cf | 1448 | ast::Item::Const(c) => m.produced.push(ast::Item::Const(c.clone())), |
| 22140cf | 1449 | ast::Item::Trait(t) => m.produced.push(ast::Item::Trait(t.clone())), |
| 22140cf | 1450 | ast::Item::Fn(f) => { |
| 0000000 | 1451 | let receiver_is_generic = f.type_param.as_deref() |
| 0000000 | 1452 | .map(|r| m.classes_generic.contains_key(r) || m.enums_generic_by_name.contains_key(r)) |
| 0000000 | 1453 | .unwrap_or(false); |
| 3d6f280 | 1454 | let is_generic_fn = f.type_param.is_none() && !fnGenericParams(f).is_empty(); |
| 2216237 | 1455 | let is_bare_generic_fn = f.type_param.is_none() && m.fns_bare_generic.contains_key(f.name.as_str()); |
| 2216237 | 1456 | if !receiver_is_generic && !is_generic_fn && !is_bare_generic_fn { |
| 22140cf | 1457 | let mut f2 = f.clone(); |
| 0000000 | 1458 | m.resolveFnParamTypes(&mut f2); |
| 3d6f280 | 1459 | m.rewriteFnBody(&mut f2, false)?; |
| 0000000 | 1460 | m.resolveFnReturnType(&mut f2); |
| 22140cf | 1461 | m.produced.push(ast::Item::Fn(f2)); |
| 22140cf | 1462 | } |
| 22140cf | 1463 | } |
| 22140cf | 1464 | _ => {} // generic Class/Enum declarations dropped here — templates only |
| 22140cf | 1465 | } |
| 22140cf | 1466 | } |
| 22140cf | 1467 | |
| 22140cf | 1468 | let mut guard = 0usize; |
| 22140cf | 1469 | while let Some(pending) = m.worklist.pop() { |
| 22140cf | 1470 | guard += 1; |
| 22140cf | 1471 | if guard > 10_000 { |
| 22140cf | 1472 | return Err("monomorphize: exceeded specialization limit (possible unbounded generic recursion)".to_string()); |
| 22140cf | 1473 | } |
| 22140cf | 1474 | match pending { |
| 22140cf | 1475 | PendingSpecialization::Class { base, subst, mangled } => { |
| 22140cf | 1476 | if !m.specialized.insert(mangled.clone()) { continue; } |
| 0000000 | 1477 | let mut spec_class = specializeClass(base, &subst, &mangled); |
| 0000000 | 1478 | // `specializeClass` only substitutes a field's bare generic-param |
| 0000000 | 1479 | // NAME (`T` -> `Int`) — a field like `Option[Node[T]]` becomes |
| 0000000 | 1480 | // `Option[Node[Int]]`, still a generic instantiation, not yet a |
| 0000000 | 1481 | // real (mangled) type. Resolve those the rest of the way now. |
| 0000000 | 1482 | for f in &mut spec_class.fields { |
| 0000000 | 1483 | f.ty = m.resolveFieldType(&f.ty); |
| 0000000 | 1484 | } |
| 22140cf | 1485 | // Register the specialized class's fields so inference inside its |
| 22140cf | 1486 | // own (and other items') bodies can resolve `receiver.field` on the |
| 22140cf | 1487 | // mangled type — `self.classes` was built from the ORIGINAL source |
| 22140cf | 1488 | // and would otherwise not know this freshly-minted class. |
| 22140cf | 1489 | m.classes.insert( |
| 22140cf | 1490 | mangled.clone(), |
| 3d6f280 | 1491 | spec_class.fields.iter().map(|f| (f.name.clone(), crate::plumTypeFromAst(&f.ty))).collect(), |
| 22140cf | 1492 | ); |
| 22140cf | 1493 | m.produced.push(ast::Item::Class(spec_class)); |
| 22140cf | 1494 | if let Some(methods) = m.methods_generic_on.get(base.name.as_str()).cloned() { |
| 22140cf | 1495 | for method in methods { |
| 3d6f280 | 1496 | let mut specialized_method = specializeFn(method, &subst, &method.name, Some(mangled.clone())); |
| 0000000 | 1497 | // Params (not returns — see `resolveFnParamTypes`'s doc |
| 0000000 | 1498 | // comment) must run before `rewriteFnBody`/registration |
| 0000000 | 1499 | // below: both read the signature's types directly off |
| 0000000 | 1500 | // this AST, and the real checker later rebuilds its own |
| 0000000 | 1501 | // tables from this exact (post-monomorphize) AST too. |
| 0000000 | 1502 | m.resolveFnParamTypes(&mut specialized_method); |
| 3d6f280 | 1503 | m.rewriteFnBody(&mut specialized_method, true)?; |
| 0000000 | 1504 | m.resolveFnReturnType(&mut specialized_method); |
| 22140cf | 1505 | // Register the specialized method's signature under its |
| 22140cf | 1506 | // (mangled receiver, method name) key so any later body that |
| 22140cf | 1507 | // dispatches to it can resolve its concrete return type. |
| 22140cf | 1508 | let param_types: Vec<PlumType> = specialized_method.params.iter().map(|p| match &p.ty { |
| 3d6f280 | 1509 | ast::ParamType::Type(t) => crate::plumTypeFromAst(t), |
| 3d6f280 | 1510 | ast::ParamType::Variadic(t) => PlumType::TVariadic(Box::new(crate::plumTypeFromAst(t))), |
| d7e5ff4 | 1511 | ast::ParamType::Fn(params, ret) => { |
| 3d6f280 | 1512 | let param_types = params.iter().map(crate::plumTypeFromAst).collect(); |
| 3d6f280 | 1513 | let ret_ty = ret.as_ref().map(|r| crate::plumTypeFromAst(r)).unwrap_or(PlumType::TUnit); |
| d7e5ff4 | 1514 | PlumType::TFun(param_types, Box::new(ret_ty)) |
| d7e5ff4 | 1515 | } |
| 22140cf | 1516 | }).collect(); |
| 22140cf | 1517 | let ret = specialized_method.returns.as_ref() |
| 0000000 | 1518 | .map(crate::plumTypeFromAst) |
| 22140cf | 1519 | .unwrap_or(PlumType::TUnit); |
| 22140cf | 1520 | m.methods.insert((mangled.clone(), specialized_method.name.clone()), PlumType::TFun(param_types, Box::new(ret))); |
| 22140cf | 1521 | m.produced.push(ast::Item::Fn(specialized_method)); |
| 22140cf | 1522 | } |
| 22140cf | 1523 | } |
| 22140cf | 1524 | } |
| 22140cf | 1525 | PendingSpecialization::Fn { base, subst, mangled, new_receiver } => { |
| 22140cf | 1526 | if !m.specialized.insert(mangled.clone()) { continue; } |
| 3d6f280 | 1527 | let mut specialized_fn = specializeFn(base, &subst, &mangled, new_receiver); |
| 0000000 | 1528 | m.resolveFnParamTypes(&mut specialized_fn); |
| 3d6f280 | 1529 | m.rewriteFnBody(&mut specialized_fn, true)?; |
| 0000000 | 1530 | m.resolveFnReturnType(&mut specialized_fn); |
| 22140cf | 1531 | // Register the specialized free function's signature so later bodies |
| 22140cf | 1532 | // can resolve calls to it during inference. |
| 22140cf | 1533 | let param_types: Vec<PlumType> = specialized_fn.params.iter().map(|p| match &p.ty { |
| 3d6f280 | 1534 | ast::ParamType::Type(t) => crate::plumTypeFromAst(t), |
| 3d6f280 | 1535 | ast::ParamType::Variadic(t) => PlumType::TVariadic(Box::new(crate::plumTypeFromAst(t))), |
| d7e5ff4 | 1536 | ast::ParamType::Fn(params, ret) => { |
| 3d6f280 | 1537 | let param_types = params.iter().map(crate::plumTypeFromAst).collect(); |
| 3d6f280 | 1538 | let ret_ty = ret.as_ref().map(|r| crate::plumTypeFromAst(r)).unwrap_or(PlumType::TUnit); |
| d7e5ff4 | 1539 | PlumType::TFun(param_types, Box::new(ret_ty)) |
| d7e5ff4 | 1540 | } |
| 22140cf | 1541 | }).collect(); |
| 22140cf | 1542 | let ret = specialized_fn.returns.as_ref() |
| 0000000 | 1543 | .map(crate::plumTypeFromAst) |
| 22140cf | 1544 | .unwrap_or(PlumType::TUnit); |
| 22140cf | 1545 | m.global_env.insert(specialized_fn.name.clone(), TypeScheme::mono(PlumType::TFun(param_types, Box::new(ret)))); |
| 22140cf | 1546 | m.produced.push(ast::Item::Fn(specialized_fn)); |
| 22140cf | 1547 | } |
| 22140cf | 1548 | PendingSpecialization::Enum { base, subst, mangled } => { |
| 22140cf | 1549 | if !m.specialized.insert(mangled.clone()) { continue; } |
| 3d6f280 | 1550 | let spec_enum = specializeEnum(base, &subst, &mangled); |
| c273ea5 | 1551 | m.produced.push(ast::Item::Enum(spec_enum)); |
| 0000000 | 1552 | if let Some(methods) = m.methods_generic_on_enum.get(base.name.as_str()).cloned() { |
| 0000000 | 1553 | for method in methods { |
| 0000000 | 1554 | let mut specialized_method = specializeFn(method, &subst, &method.name, Some(mangled.clone())); |
| 0000000 | 1555 | m.resolveFnParamTypes(&mut specialized_method); |
| 0000000 | 1556 | m.rewriteFnBody(&mut specialized_method, true)?; |
| 0000000 | 1557 | m.resolveFnReturnType(&mut specialized_method); |
| 0000000 | 1558 | let param_types: Vec<PlumType> = specialized_method.params.iter().map(|p| match &p.ty { |
| 0000000 | 1559 | ast::ParamType::Type(t) => crate::plumTypeFromAst(t), |
| 0000000 | 1560 | ast::ParamType::Variadic(t) => PlumType::TVariadic(Box::new(crate::plumTypeFromAst(t))), |
| 0000000 | 1561 | ast::ParamType::Fn(params, ret) => { |
| 0000000 | 1562 | let param_types = params.iter().map(crate::plumTypeFromAst).collect(); |
| 0000000 | 1563 | let ret_ty = ret.as_ref().map(|r| crate::plumTypeFromAst(r)).unwrap_or(PlumType::TUnit); |
| 0000000 | 1564 | PlumType::TFun(param_types, Box::new(ret_ty)) |
| 0000000 | 1565 | } |
| 0000000 | 1566 | }).collect(); |
| 0000000 | 1567 | let ret = specialized_method.returns.as_ref() |
| 0000000 | 1568 | .map(crate::plumTypeFromAst) |
| 0000000 | 1569 | .unwrap_or(PlumType::TUnit); |
| 0000000 | 1570 | m.methods.insert((mangled.clone(), specialized_method.name.clone()), PlumType::TFun(param_types, Box::new(ret))); |
| 0000000 | 1571 | m.produced.push(ast::Item::Fn(specialized_method)); |
| 0000000 | 1572 | } |
| 0000000 | 1573 | } |
| 22140cf | 1574 | } |
| 22140cf | 1575 | } |
| 22140cf | 1576 | } |
| 22140cf | 1577 | |
| 22140cf | 1578 | Ok(ast::Source { module: source.module.clone(), imports: source.imports.clone(), items: m.produced }) |
| 22140cf | 1579 | } |