plum

#treesitter#compiler#wasm

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 &params {
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 &param.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 &param.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, &params, 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, &params, 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, &params, 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, &lt);
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
}