plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
55dceee
— Peter John
2026-10-25T22:50:52+05:30
feat(codegen)!: compile erased generics; stop monomorphizing
- plum-checker/src/lib.rs +66 -25
- plum-checker/tests/checker_tests.rs +8 -0
- plum-cli/src/main.rs +7 -1
- plum-wasm-codegen/src/lib.rs +1179 -1207
- plum-wasm-codegen/tests/codegen_tests.rs +107 -7
plum-checker/src/lib.rs
CHANGED
|
@@ -25,6 +25,14 @@ pub struct TypedInfo {
|
|
|
25
25
|
pub expr_types: HashMap<usize, PlumType>,
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/// Both operands of a binary/compare operator have the same type, so an
|
|
29
|
+
/// unresolved inference variable on one side takes the other side's type.
|
|
30
|
+
fn bindAcross(a: &PlumType, b: &PlumType) {
|
|
31
|
+
let mut scratch = Binds::new();
|
|
32
|
+
bindMatch(a, b, &mut scratch);
|
|
33
|
+
bindMatch(b, a, &mut scratch);
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
fn freshVar() -> PlumType {
|
|
29
37
|
FRESH.with(|c| {
|
|
30
38
|
let n = c.get();
|
|
@@ -225,7 +233,7 @@ fn appSubst(generics: &[String], args: &[PlumType]) -> BTreeMap<String, PlumType
|
|
|
225
233
|
|
|
226
234
|
/// A variant's payload field types with the enum's generics replaced by the
|
|
227
235
|
/// type arguments of `ty` (when it is a `TApp`).
|
|
228
|
-
fn variantFields(info: &EnumVariantInfo, ty: &PlumType) -> Vec<PlumType> {
|
|
236
|
+
pub fn variantFields(info: &EnumVariantInfo, ty: &PlumType) -> Vec<PlumType> {
|
|
229
237
|
match ty {
|
|
230
238
|
PlumType::TApp(_, args) if args.len() == info.generics.len() => {
|
|
231
239
|
let s = appSubst(&info.generics, args);
|
|
@@ -288,7 +296,7 @@ fn recordType(expr: &ast::Expr, ty: &PlumType) {
|
|
|
288
296
|
}
|
|
289
297
|
|
|
290
298
|
/// Base name of a nominal type (`TNamed`/`TApp`).
|
|
291
|
-
fn nominalName(t: &PlumType) -> Option<&str> {
|
|
299
|
+
pub fn nominalName(t: &PlumType) -> Option<&str> {
|
|
292
300
|
match t {
|
|
293
301
|
PlumType::TNamed(n) | PlumType::TApp(n, _) => Some(n),
|
|
294
302
|
_ => None,
|
|
@@ -2522,6 +2530,9 @@ fn checkCallArgs(
|
|
|
2522
2530
|
format!("{} arg {}: {}", label, i, err)
|
|
2523
2531
|
}
|
|
2524
2532
|
})?;
|
|
2533
|
+
// An argument whose own type still has unresolved variables (an unused
|
|
2534
|
+
// closure parameter, an empty literal) learns them from the parameter.
|
|
2535
|
+
bindMatch(&actual, &want, binds);
|
|
2525
2536
|
}
|
|
2526
2537
|
}
|
|
2527
2538
|
Ok(())
|
|
@@ -2563,7 +2574,8 @@ fn tryParts(ty: &PlumType, ctx: &CheckCtx) -> Option<(PlumType, Option<PlumType>
|
|
|
2563
2574
|
|
|
2564
2575
|
fn inferMethodCall(
|
|
2565
2576
|
obj_ty: &PlumType,
|
|
2577
|
+
name: &str,
|
|
2566
|
-
|
|
2578
|
+
args: &[ast::Arg],
|
|
2567
2579
|
env: &TypeEnv,
|
|
2568
2580
|
ctx: &CheckCtx,
|
|
2569
2581
|
expected: Option<&PlumType>,
|
|
@@ -2575,19 +2587,19 @@ fn inferMethodCall(
|
|
|
2575
2587
|
let Some(tr) = key.strip_prefix(&prefix) else {
|
|
2576
2588
|
continue;
|
|
2577
2589
|
};
|
|
2578
|
-
let Some(sch) = env.get(&format!("{}{}:{}", TRAIT_KEY, tr,
|
|
2590
|
+
let Some(sch) = env.get(&format!("{}{}:{}", TRAIT_KEY, tr, name)) else {
|
|
2579
2591
|
continue;
|
|
2580
2592
|
};
|
|
2581
2593
|
if let PlumType::TFun(params, ret) = &*sch.body {
|
|
2582
2594
|
let mut binds = Binds::new();
|
|
2583
|
-
let label = format!("method '{}.{}'", p,
|
|
2595
|
+
let label = format!("method '{}.{}'", p, name);
|
|
2584
|
-
checkCallArgs(&label, params,
|
|
2596
|
+
checkCallArgs(&label, params, args, params.len(), env, ctx, &mut binds)?;
|
|
2585
2597
|
return Ok(substVars(ret, &binds));
|
|
2586
2598
|
}
|
|
2587
2599
|
}
|
|
2588
2600
|
return Err(format!(
|
|
2589
2601
|
"'{}' has no method '{}' (add a bound)",
|
|
2590
|
-
p,
|
|
2602
|
+
p, name
|
|
2591
2603
|
));
|
|
2592
2604
|
}
|
|
2593
2605
|
let Some(class_name) = methodReceiverName(obj_ty) else {
|
|
@@ -2597,20 +2609,24 @@ fn inferMethodCall(
|
|
|
2597
2609
|
// the "union" enum that bare-wraps it instead (`enum Number = | Int | Float`).
|
|
2598
2610
|
let class_name = if ctx
|
|
2599
2611
|
.methods
|
|
2600
|
-
.contains_key(&(class_name.
|
|
2612
|
+
.contains_key(&(class_name.to_string(), name.to_string()))
|
|
2601
2613
|
{
|
|
2602
2614
|
class_name
|
|
2603
2615
|
} else {
|
|
2604
2616
|
ctx.enum_variants
|
|
2605
2617
|
.get(class_name.as_str())
|
|
2606
|
-
.map(|info| info.enum_name.
|
|
2618
|
+
.map(|info| info.enum_name.to_string())
|
|
2607
2619
|
.filter(|enum_name| {
|
|
2608
2620
|
ctx.methods
|
|
2609
|
-
.contains_key(&(enum_name.
|
|
2621
|
+
.contains_key(&(enum_name.to_string(), name.to_string()))
|
|
2610
2622
|
})
|
|
2611
2623
|
.unwrap_or(class_name)
|
|
2612
2624
|
};
|
|
2613
|
-
let Some(sig) = ctx.methods.get(&(class_name.
|
|
2625
|
+
let Some(sig) = ctx.methods.get(&(class_name.to_string(), name.to_string())) else {
|
|
2626
|
+
// A trait-typed value only has the methods its trait declares.
|
|
2627
|
+
if isTraitName(&class_name, ctx) {
|
|
2628
|
+
return Err(format!("trait '{}' has no method '{}'", class_name, name));
|
|
2629
|
+
}
|
|
2614
2630
|
// Unmodeled method (e.g. builtin/std): allow, codegen will catch.
|
|
2615
2631
|
return Ok(PlumType::TVar("_".to_string()));
|
|
2616
2632
|
};
|
|
@@ -2632,11 +2648,11 @@ fn inferMethodCall(
|
|
|
2632
2648
|
}
|
|
2633
2649
|
let required = ctx
|
|
2634
2650
|
.min_required
|
|
2635
|
-
.get(&(Some(class_name.
|
|
2651
|
+
.get(&(Some(class_name.to_string()), name.to_string()))
|
|
2636
2652
|
.copied()
|
|
2637
2653
|
.unwrap_or(params.len());
|
|
2638
|
-
let label = format!("method '{}.{}'", class_name,
|
|
2654
|
+
let label = format!("method '{}.{}'", class_name, name);
|
|
2639
|
-
checkCallArgs(&label, ¶ms,
|
|
2655
|
+
checkCallArgs(&label, ¶ms, args, required, env, ctx, &mut binds)?;
|
|
2640
2656
|
Ok(substVars(&ret, &binds))
|
|
2641
2657
|
}
|
|
2642
2658
|
|
|
@@ -2837,7 +2853,7 @@ fn inferExprInner(
|
|
|
2837
2853
|
resolved_from_usage
|
|
2838
2854
|
.get(p)
|
|
2839
2855
|
.cloned()
|
|
2840
|
-
.unwrap_or_else(
|
|
2856
|
+
.unwrap_or_else(freshVar)
|
|
2841
2857
|
});
|
|
2842
2858
|
closure_env.insert(p.clone(), TypeScheme::mono(t.clone()));
|
|
2843
2859
|
t
|
|
@@ -2856,13 +2872,37 @@ fn inferExprInner(
|
|
|
2856
2872
|
closure_env.insert(name.clone(), TypeScheme::mono(ty));
|
|
2857
2873
|
}
|
|
2858
2874
|
}
|
|
2875
|
+
} else {
|
|
2876
|
+
// Walk every other statement (`if`, loops, `match`, plain
|
|
2877
|
+
// calls) so the types of the expressions inside are
|
|
2878
|
+
// recorded for codegen. Errors are deliberately dropped:
|
|
2879
|
+
// the closure's params may still be unresolved here.
|
|
2880
|
+
let _ = checkStmt(
|
|
2881
|
+
stmt,
|
|
2882
|
+
&mut closure_env,
|
|
2883
|
+
&PlumType::TVar("_".into()),
|
|
2884
|
+
"closure",
|
|
2885
|
+
(0, 0),
|
|
2886
|
+
ctx,
|
|
2887
|
+
);
|
|
2859
2888
|
}
|
|
2860
2889
|
}
|
|
2861
2890
|
}
|
|
2862
2891
|
let body_ty = match &cl.body.stmts.last() {
|
|
2863
2892
|
Some(ast::Stmt::Expr(e, ..)) => inferExprWith(e, &closure_env, ctx, exp_ret)?,
|
|
2864
2893
|
Some(ast::Stmt::Return(Some(e))) => inferExprWith(e, &closure_env, ctx, exp_ret)?,
|
|
2894
|
+
Some(last) => {
|
|
2895
|
+
let _ = checkStmt(
|
|
2896
|
+
last,
|
|
2897
|
+
&mut closure_env,
|
|
2898
|
+
&PlumType::TVar("_".into()),
|
|
2899
|
+
"closure",
|
|
2900
|
+
(0, 0),
|
|
2901
|
+
ctx,
|
|
2902
|
+
);
|
|
2903
|
+
PlumType::TUnit
|
|
2904
|
+
}
|
|
2865
|
-
|
|
2905
|
+
None => PlumType::TUnit,
|
|
2866
2906
|
};
|
|
2867
2907
|
Ok(PlumType::TFun(param_types, Box::new(body_ty)))
|
|
2868
2908
|
}
|
|
@@ -2876,6 +2916,7 @@ fn inferExprInner(
|
|
|
2876
2916
|
let lt = inferExpr(&b.left, env, ctx)?;
|
|
2877
2917
|
let rt = inferExpr(&b.right, env, ctx)?;
|
|
2878
2918
|
unify(<, &rt, ctx).map_err(|e| format!("binary op: {}", e))?;
|
|
2919
|
+
bindAcross(<, &rt);
|
|
2879
2920
|
if matches!(lt, PlumType::TVar(_)) && !matches!(rt, PlumType::TVar(_)) {
|
|
2880
2921
|
return Ok(rt);
|
|
2881
2922
|
}
|
|
@@ -2902,6 +2943,7 @@ fn inferExprInner(
|
|
|
2902
2943
|
return Ok(PlumType::TBool);
|
|
2903
2944
|
}
|
|
2904
2945
|
unify(<, &rt, ctx).map_err(|e| format!("compare op: {}", e))?;
|
|
2946
|
+
bindAcross(<, &rt);
|
|
2905
2947
|
Ok(PlumType::TBool)
|
|
2906
2948
|
}
|
|
2907
2949
|
ast::Expr::Ternary(t) => {
|
|
@@ -2972,14 +3014,11 @@ fn inferExprInner(
|
|
|
2972
3014
|
.methods
|
|
2973
3015
|
.contains_key(&(call.name.clone(), "init".to_string()))
|
|
2974
3016
|
{
|
|
3017
|
+
// The receiver is a temporary (never recorded); the ARGUMENTS stay
|
|
2975
|
-
|
|
3018
|
+
// the original nodes so their types are recorded for codegen.
|
|
2976
|
-
|
|
3019
|
+
let receiver = ast::Expr::TypeName(call.name.clone());
|
|
2977
|
-
attr: ast::AttrKind::Method(ast::FnCall {
|
|
2978
|
-
name: "init".to_string(),
|
|
2979
|
-
args: call.args.clone(),
|
|
2980
|
-
}),
|
|
2981
|
-
}));
|
|
2982
|
-
|
|
3020
|
+
let obj_ty = inferExprInner(&receiver, env, ctx, None)?;
|
|
3021
|
+
return inferMethodCall(&obj_ty, "init", &call.args, env, ctx, expected);
|
|
2983
3022
|
}
|
|
2984
3023
|
if let Some(info) = ctx.enum_variants.get(&call.name) {
|
|
2985
3024
|
if call.args.len() != info.field_types.len() {
|
|
@@ -3075,7 +3114,9 @@ fn inferExprInner(
|
|
|
3075
3114
|
field_name, other
|
|
3076
3115
|
)),
|
|
3077
3116
|
},
|
|
3117
|
+
ast::AttrKind::Method(call) => {
|
|
3078
|
-
|
|
3118
|
+
inferMethodCall(&obj_ty, &call.name, &call.args, env, ctx, expected)
|
|
3119
|
+
}
|
|
3079
3120
|
}
|
|
3080
3121
|
}
|
|
3081
3122
|
}
|
plum-checker/tests/checker_tests.rs
CHANGED
|
@@ -2089,6 +2089,14 @@ fn unbounded_tparam_method_error() {
|
|
|
2089
2089
|
);
|
|
2090
2090
|
}
|
|
2091
2091
|
|
|
2092
|
+
#[test]
|
|
2093
|
+
fn trait_typed_receiver_undeclared_method_error() {
|
|
2094
|
+
let src = "trait Shape =\n area() -> Float\n\nfun f(s: Shape) -> Float =\n s.bogus()\n";
|
|
2095
|
+
assert!(
|
|
2096
|
+
errText(checkSource(&parseClean(src))).contains("trait 'Shape' has no method 'bogus'")
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2092
2100
|
#[test]
|
|
2093
2101
|
fn bounded_ok() {
|
|
2094
2102
|
let src = "\
|
plum-cli/src/main.rs
CHANGED
|
@@ -181,7 +181,13 @@ fn compileToTestWasm(
|
|
|
181
181
|
anyhow::bail!(messages.join("\n"));
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
let compiled = plum_wasm_codegen::compileTestSource(&ast)
|
|
184
|
-
|
|
185
|
+
.map_err(|e| anyhow::anyhow!("codegen error: {e}"))?;
|
|
186
|
+
// Debug aid: `PLUM_DUMP_WASM=out.wasm plum test file.plum` keeps the module.
|
|
187
|
+
if let Some(path) = std::env::var_os("PLUM_DUMP_WASM") {
|
|
188
|
+
let _ = fs::write(path, &compiled.0);
|
|
189
|
+
}
|
|
190
|
+
Ok(compiled)
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
fn cmdCompile(
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -314,6 +314,11 @@ pub struct FuncSig {
|
|
|
314
314
|
/// TRAILING ones compiled from here (see `compileCallArgs`). Always
|
|
315
315
|
/// `vec![]` for an `extern fun` (the checker rejects a default there).
|
|
316
316
|
pub param_defaults: Vec<Option<ast::Expr>>,
|
|
317
|
+
/// The DECLARED (generic, un-instantiated) param types, excluding any
|
|
318
|
+
/// implicit receiver, and the declared return type. A `TParam` slot is
|
|
319
|
+
/// erased to `ref null $Obj`, so the caller boxes/unboxes against these.
|
|
320
|
+
pub decl_params: Vec<PlumType>,
|
|
321
|
+
pub decl_ret: PlumType,
|
|
317
322
|
}
|
|
318
323
|
|
|
319
324
|
/// Everything codegen needs to know about one closure *literal* found in the program.
|
|
@@ -332,20 +337,26 @@ pub struct ClosureInfo {
|
|
|
332
337
|
/// order) — assigned once all closures are discovered, alongside every other
|
|
333
338
|
/// closure's env type and the shared closure-value struct, in one `rec` group.
|
|
334
339
|
pub env_type_idx: u32,
|
|
335
|
-
/// Closure param val types (NOT including the implicit leading env pointer).
|
|
336
|
-
pub param_vts: Vec<ValType>,
|
|
337
340
|
/// Closure param plum types (for the closure body's own type env).
|
|
338
341
|
pub param_ptypes: Vec<PlumType>,
|
|
339
|
-
/// Closure return
|
|
342
|
+
/// Closure return plum type.
|
|
340
|
-
pub
|
|
343
|
+
pub ret_ptype: PlumType,
|
|
341
344
|
/// Free variables captured by value, in a stable (first-appearance) order; the
|
|
342
345
|
/// index into this vec IS the variable's field index in the env struct.
|
|
343
346
|
pub free_vars: Vec<(String, PlumType)>,
|
|
344
347
|
}
|
|
345
348
|
|
|
346
|
-
///
|
|
349
|
+
/// Closure ABI (the "erased" calling convention): EVERY closure, whatever its
|
|
347
|
-
/// signature
|
|
350
|
+
/// Plum signature, compiles to the wasm function type `(env: anyref, a1: anyref,
|
|
351
|
+
/// ..., an: anyref) -> anyref`, so a closure value is callable from any
|
|
352
|
+
/// `fn(...)`-typed slot (including a generic `fn(T) -> U` one) through a single
|
|
353
|
+
/// `call_indirect` type per arity. A primitive argument/result is boxed at that
|
|
354
|
+
/// edge (`$BoxI64`/`$BoxF64`/`$BoxI32`) and unboxed again inside the closure body
|
|
355
|
+
/// (prologue) / at the call site (result); reference values pass as-is, and a
|
|
356
|
+
/// `Unit` result is a null `anyref`. This keeps one wasm function per closure
|
|
357
|
+
/// literal and needs no per-instantiation thunk: the "thunk" work is done in the
|
|
358
|
+
/// closure's own prologue and the call site's epilogue.
|
|
348
|
-
type ClosureSigKey =
|
|
359
|
+
type ClosureSigKey = usize;
|
|
349
360
|
|
|
350
361
|
/// Global, read-only lookup tables shared by every function body being compiled.
|
|
351
362
|
pub struct CompileCtx<'a> {
|
|
@@ -361,8 +372,11 @@ pub struct CompileCtx<'a> {
|
|
|
361
372
|
/// The AST of each discovered closure literal, keyed the same way, so its body can
|
|
362
373
|
/// be compiled in a second pass after all closures are registered.
|
|
363
374
|
pub closure_asts: HashMap<usize, &'a ast::Closure>,
|
|
364
|
-
/// Closure
|
|
375
|
+
/// Closure arity -> the erased `call_indirect` function-type index.
|
|
365
376
|
pub closure_call_types: HashMap<ClosureSigKey, u32>,
|
|
377
|
+
/// Instantiated type of every expression of the (un-monomorphized) source,
|
|
378
|
+
/// keyed by expression address — from `plum_checker::typedSource`.
|
|
379
|
+
pub types: HashMap<usize, PlumType>,
|
|
366
380
|
/// Top-level function name -> global index holding its zero-capture "trampoline"
|
|
367
381
|
/// closure struct `{table_idx, env=null}`, for using a plain named function
|
|
368
382
|
/// wherever a `fn(...)`-typed value is expected (e.g. `each(double)`). Since the
|
|
@@ -377,6 +391,10 @@ pub struct CompileCtx<'a> {
|
|
|
377
391
|
/// content equality (1/0) — see the comment on its registration site for
|
|
378
392
|
/// why `==`/`!=` on `Str` needs this instead of plain `ref.eq`.
|
|
379
393
|
pub string_eq_func: u32,
|
|
394
|
+
/// Shared runtime helper `(a: ref null $Obj, b: ref null $Obj) -> i32`: `==` on
|
|
395
|
+
/// two erased (`TParam`-typed) values — identity, else by value for boxed
|
|
396
|
+
/// `Int`/`Float`/`Byte` and content for `Str`.
|
|
397
|
+
pub dyn_eq_func: u32,
|
|
380
398
|
/// Shared runtime helper `(n: i64) -> ref Str`: allocates a new `array<i8>`
|
|
381
399
|
/// holding `n`'s decimal representation, for interpolating an `Int`.
|
|
382
400
|
pub int_to_string_func: u32,
|
|
@@ -417,11 +435,17 @@ struct LocalCtx<'a> {
|
|
|
417
435
|
variadic_for_scratch: HashMap<usize, u32>,
|
|
418
436
|
func_ids: &'a HashMap<String, u32>,
|
|
419
437
|
func_sigs: &'a HashMap<String, FuncSig>,
|
|
438
|
+
types: &'a HashMap<usize, PlumType>,
|
|
439
|
+
/// Name -> the plum type each local was DECLARED with (so its wasm
|
|
440
|
+
/// representation is `plumTypeToValtype` of this), as opposed to the
|
|
441
|
+
/// type of a particular use of it.
|
|
442
|
+
local_decl_types: HashMap<String, PlumType>,
|
|
420
443
|
closures: &'a HashMap<usize, ClosureInfo>,
|
|
421
444
|
closure_call_types: &'a HashMap<ClosureSigKey, u32>,
|
|
422
445
|
named_fn_values: &'a HashMap<String, u32>,
|
|
423
446
|
string_concat_func: u32,
|
|
424
447
|
string_eq_func: u32,
|
|
448
|
+
dyn_eq_func: u32,
|
|
425
449
|
int_to_string_func: u32,
|
|
426
450
|
methods: &'a MethodEnv,
|
|
427
451
|
enum_variants: &'a EnumVariants,
|
|
@@ -433,15 +457,6 @@ struct LocalCtx<'a> {
|
|
|
433
457
|
/// statements in order, mirroring `plum-checker`'s own env evolution — needed
|
|
434
458
|
/// to resolve `Attribute`/`ClassCall` targets and pick the right load/store width.
|
|
435
459
|
type_env: RefCell<TypeEnv>,
|
|
436
|
-
/// Local name -> the exact wasm `call_indirect` signature of the closure literal
|
|
437
|
-
/// assigned to it (populated when compiling that `Stmt::Assign`, straight from
|
|
438
|
-
/// the already-correct `ClosureInfo` the discovery pass computed). Exists so
|
|
439
|
-
/// `compileClosureCall` doesn't have to re-derive the signature via
|
|
440
|
-
/// `plum_checker::inferExpr` on the closure a second time — which, unlike the
|
|
441
|
-
/// discovery pass, doesn't have `resolveClosureParamTypesFromUsage`'s fix
|
|
442
|
-
/// and would fall back to its old TVar-defaults-to-Int behavior, disagreeing
|
|
443
|
-
/// with the (now correct) signature the closure's body was actually compiled with.
|
|
444
|
-
closure_local_sigs: RefCell<HashMap<String, ClosureSigKey>>,
|
|
445
460
|
/// How many wasm structured blocks (`if`/`else`) currently lie between the
|
|
446
461
|
/// point being compiled and its NEAREST ENCLOSING LOOP's own `block`/`loop`
|
|
447
462
|
/// pair — used by `Stmt::Break`/`Stmt::Continue` to compute the right `br`
|
|
@@ -457,9 +472,15 @@ struct LocalCtx<'a> {
|
|
|
457
472
|
/// The declared return type of the function being compiled (`None` inside a
|
|
458
473
|
/// closure body) — `?` builds the OUTER function's `Err` variant from it.
|
|
459
474
|
ret_type: Option<PlumType>,
|
|
475
|
+
/// Whether this body is a closure literal's: `return` then yields an erased
|
|
476
|
+
/// (`anyref`) value per the closure ABI instead of `ret_type`'s own repr.
|
|
477
|
+
in_closure: bool,
|
|
460
478
|
/// Match-subject scratch local -> its tag-union type, for subjects whose
|
|
461
479
|
/// static type is a `TTags` (their `Name`/`Class` arms test `$Tag.id`).
|
|
462
480
|
match_subject_types: RefCell<HashMap<u32, PlumType>>,
|
|
481
|
+
/// Match-subject scratch local -> the subject's full plum type (type arguments
|
|
482
|
+
/// included), so constructor patterns can instantiate their payload types.
|
|
483
|
+
subject_ptypes: RefCell<HashMap<u32, PlumType>>,
|
|
463
484
|
}
|
|
464
485
|
|
|
465
486
|
fn fnKey(f: &ast::Fn) -> String {
|
|
@@ -492,6 +513,12 @@ fn fnWasmParamTypes(f: &ast::Fn, gc_types: &GcTypeRegistry) -> Vec<ValType> {
|
|
|
492
513
|
param_types
|
|
493
514
|
}
|
|
494
515
|
|
|
516
|
+
/// A single uppercase letter is the grammar's only spelling of a generic parameter.
|
|
517
|
+
fn isGenericName(name: &str) -> bool {
|
|
518
|
+
let mut chars = name.chars();
|
|
519
|
+
matches!((chars.next(), chars.next()), (Some(c), None) if c.is_ascii_uppercase())
|
|
520
|
+
}
|
|
521
|
+
|
|
495
522
|
fn paramTypeName(pt: &ast::ParamType) -> &str {
|
|
496
523
|
match pt {
|
|
497
524
|
ast::ParamType::Type(t) => t.name.as_str(),
|
|
@@ -531,6 +558,24 @@ thread_local! {
|
|
|
531
558
|
const { RefCell::new(std::collections::BTreeMap::new()) };
|
|
532
559
|
}
|
|
533
560
|
|
|
561
|
+
/// A variant's payload field types as instantiated by `subject_ty`'s type
|
|
562
|
+
/// arguments. A field whose argument the checker could not resolve (matching
|
|
563
|
+
/// through a bare `Parcel` annotation) keeps its declared, erased type, so a
|
|
564
|
+
/// binding of it stays a `ref null $Obj` instead of guessing a representation.
|
|
565
|
+
fn instFields(info: &EnumVariantInfo, subject_ty: &PlumType) -> Vec<PlumType> {
|
|
566
|
+
plum_checker::variantFields(info, subject_ty)
|
|
567
|
+
.into_iter()
|
|
568
|
+
.zip(info.field_types.iter())
|
|
569
|
+
.map(|(inst, decl)| match inst {
|
|
570
|
+
PlumType::TVar(_) => decl.clone(),
|
|
571
|
+
other => other,
|
|
572
|
+
})
|
|
573
|
+
.collect()
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/// Largest closure arity with a pre-registered erased `call_indirect` type.
|
|
577
|
+
const MAX_CLOSURE_ARITY: usize = 8;
|
|
578
|
+
|
|
534
579
|
/// Import key of the `plum::crash(Str)` host function in `func_ids`.
|
|
535
580
|
const CRASH_FN_KEY: &str = "__crash";
|
|
536
581
|
|
|
@@ -572,55 +617,25 @@ fn tagId(name: &str) -> i32 {
|
|
|
572
617
|
})
|
|
573
618
|
}
|
|
574
619
|
|
|
575
|
-
/// Collects every tag name
|
|
620
|
+
/// Collects every tag name appearing anywhere inside `ty` (tag-union keys and,
|
|
576
|
-
///
|
|
621
|
+
/// recursively, their payload types and every type argument).
|
|
577
|
-
fn
|
|
622
|
+
fn collectTagNamesTy(ty: &PlumType, out: &mut std::collections::BTreeSet<String>) {
|
|
578
|
-
let chars: Vec<char> = s.chars().collect();
|
|
579
|
-
let mut i = 0;
|
|
580
|
-
while i < chars.len() {
|
|
581
|
-
if chars[i] == '[' && chars.get(i + 1) != Some(&']') {
|
|
582
|
-
let start = i;
|
|
583
|
-
let mut depth = 0;
|
|
584
|
-
while i < chars.len() {
|
|
585
|
-
match chars[i] {
|
|
586
|
-
'[' => depth += 1,
|
|
587
|
-
']' => depth -= 1,
|
|
588
|
-
_ => {}
|
|
589
|
-
}
|
|
590
|
-
i += 1;
|
|
591
|
-
if depth == 0 {
|
|
592
|
-
break;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
let inner: String = chars[start + 1..i.saturating_sub(1)].iter().collect();
|
|
596
|
-
let mut item = String::new();
|
|
597
|
-
let mut d = 0;
|
|
598
|
-
let mut items = Vec::new();
|
|
599
|
-
for ch in inner.chars() {
|
|
600
|
-
|
|
623
|
+
match ty {
|
|
601
|
-
'(' | '[' => d += 1,
|
|
602
|
-
')' | ']' => d -= 1,
|
|
603
|
-
_ => {}
|
|
604
|
-
}
|
|
605
|
-
if ch == ',' && d == 0 {
|
|
606
|
-
|
|
624
|
+
PlumType::TTags(tags) => {
|
|
607
|
-
} else {
|
|
608
|
-
item.push(ch);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
items.push(item);
|
|
612
|
-
for
|
|
625
|
+
for (name, payload) in tags {
|
|
613
|
-
let name = it.split('(').next().unwrap_or("").trim().to_string();
|
|
614
|
-
if !name.is_empty() {
|
|
615
|
-
|
|
626
|
+
out.insert(name.clone());
|
|
616
|
-
}
|
|
617
|
-
if let Some(
|
|
627
|
+
if let Some(p) = payload {
|
|
618
|
-
|
|
628
|
+
collectTagNamesTy(p, out);
|
|
619
629
|
}
|
|
620
630
|
}
|
|
621
|
-
} else {
|
|
622
|
-
i += 1;
|
|
623
631
|
}
|
|
632
|
+
PlumType::TApp(_, args) => args.iter().for_each(|a| collectTagNamesTy(a, out)),
|
|
633
|
+
PlumType::TFun(ps, r) => {
|
|
634
|
+
ps.iter().for_each(|p| collectTagNamesTy(p, out));
|
|
635
|
+
collectTagNamesTy(r, out);
|
|
636
|
+
}
|
|
637
|
+
PlumType::TVariadic(i) => collectTagNamesTy(i, out),
|
|
638
|
+
_ => {}
|
|
624
639
|
}
|
|
625
640
|
}
|
|
626
641
|
|
|
@@ -636,21 +651,53 @@ fn boxTagPayload(ty: &PlumType, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
|
636
651
|
}
|
|
637
652
|
}
|
|
638
653
|
|
|
639
|
-
///
|
|
654
|
+
/// Converts the value on top of the stack from representation `from` to `to`
|
|
640
|
-
/// it flows into a slot whose wasm type is `expected` and `ty` itself is not a
|
|
641
|
-
///
|
|
655
|
+
/// (the erased-generics edge conversions): a primitive entering a reference slot
|
|
656
|
+
/// (`ref null $Obj` / `anyref`) is boxed; a reference leaving such a slot for a
|
|
657
|
+
/// primitive is cast to the matching box and unboxed; a reference leaving the
|
|
658
|
+
/// erased supertype for a concrete struct/enum/tag type is `ref.cast` down.
|
|
659
|
+
/// Identical representations and upcasts emit nothing.
|
|
642
|
-
fn
|
|
660
|
+
fn coerceVt(from: ValType, to: ValType, body: &mut Vec<u8>, g: &GcTypeRegistry) {
|
|
643
|
-
|
|
661
|
+
if from == to {
|
|
644
|
-
if rt.heap_type != HeapType::Concrete(ctx.gc_types.obj_type_idx) {
|
|
645
662
|
return;
|
|
646
663
|
}
|
|
664
|
+
match (from, to) {
|
|
665
|
+
(ValType::I64, ValType::Ref(_)) => Instruction::Call(g.box_i64_fn).encode(body),
|
|
666
|
+
(ValType::F64, ValType::Ref(_)) => Instruction::Call(g.box_f64_fn).encode(body),
|
|
667
|
+
(ValType::I32, ValType::Ref(_)) => Instruction::Call(g.box_i32_fn).encode(body),
|
|
647
|
-
|
|
668
|
+
(ValType::Ref(_), prim @ (ValType::I64 | ValType::F64 | ValType::I32)) => {
|
|
669
|
+
let idx = match prim {
|
|
648
|
-
|
|
670
|
+
ValType::I64 => g.box_i64_idx,
|
|
671
|
+
ValType::F64 => g.box_f64_idx,
|
|
672
|
+
_ => g.box_i32_idx,
|
|
673
|
+
};
|
|
674
|
+
Instruction::RefCastNonNull(HeapType::Concrete(idx)).encode(body);
|
|
675
|
+
Instruction::StructGet {
|
|
676
|
+
struct_type_index: idx,
|
|
677
|
+
field_index: fld(0),
|
|
678
|
+
}
|
|
679
|
+
.encode(body);
|
|
680
|
+
}
|
|
681
|
+
(ValType::Ref(f), ValType::Ref(t)) => {
|
|
682
|
+
// Only a downcast out of the erased supertype (`$Obj`) or an abstract
|
|
683
|
+
// ref (`anyref`) needs an instruction; anything else is an upcast.
|
|
684
|
+
let from_erased = match f.heap_type {
|
|
685
|
+
HeapType::Concrete(fi) => fi == g.obj_type_idx,
|
|
686
|
+
_ => true,
|
|
687
|
+
};
|
|
688
|
+
if let HeapType::Concrete(ti) = t.heap_type {
|
|
689
|
+
if from_erased && !matches!(f.heap_type, HeapType::Concrete(fi) if fi == ti) {
|
|
690
|
+
Instruction::RefCastNullable(t.heap_type).encode(body);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
_ => {}
|
|
649
695
|
}
|
|
650
696
|
}
|
|
651
697
|
|
|
652
|
-
/// Compiles `expr` and
|
|
698
|
+
/// Compiles `expr` and converts its value to representation `expected` (when given):
|
|
699
|
+
/// boxes a primitive flowing into an erased (`ref null $Obj`) slot, casts an
|
|
653
|
-
///
|
|
700
|
+
/// erased value down to the concrete type a slot expects.
|
|
654
701
|
fn compileExprAs(
|
|
655
702
|
expr: &ast::Expr,
|
|
656
703
|
expected: Option<ValType>,
|
|
@@ -659,15 +706,52 @@ fn compileExprAs(
|
|
|
659
706
|
state: &mut ModuleState,
|
|
660
707
|
) -> Result<(), String> {
|
|
661
708
|
compileExpr(expr, body, ctx, state)?;
|
|
662
|
-
if let Some(vt
|
|
709
|
+
if let Some(vt) = expected {
|
|
663
|
-
if rt.heap_type == HeapType::Concrete(ctx.gc_types.obj_type_idx) {
|
|
664
|
-
|
|
710
|
+
let own = plumTypeToValtype(&inferLocalType(expr, ctx));
|
|
665
|
-
|
|
711
|
+
coerceVt(own, vt, body, ctx.gc_types);
|
|
666
|
-
}
|
|
667
712
|
}
|
|
668
713
|
Ok(())
|
|
669
714
|
}
|
|
670
715
|
|
|
716
|
+
/// The variant of enum `decl` that bare-wraps the primitive type `actual`
|
|
717
|
+
/// (`enum Number = | Int | Float`), if `decl` names such an enum.
|
|
718
|
+
fn primitiveWrapVariant(decl: &PlumType, actual: &PlumType, ctx: &LocalCtx) -> Option<String> {
|
|
719
|
+
if !matches!(actual, PlumType::TInt | PlumType::TFloat) {
|
|
720
|
+
return None;
|
|
721
|
+
}
|
|
722
|
+
let enum_name = plum_checker::nominalName(decl)?;
|
|
723
|
+
ctx.enum_variants
|
|
724
|
+
.iter()
|
|
725
|
+
.find(|(_, info)| {
|
|
726
|
+
info.enum_name == enum_name
|
|
727
|
+
&& info.field_names.is_empty()
|
|
728
|
+
&& info.field_types == [actual.clone()]
|
|
729
|
+
})
|
|
730
|
+
.map(|(n, _)| n.clone())
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/// Like `compileExprAs`, for a slot whose DECLARED plum type is `decl`: a bare
|
|
734
|
+
/// `Int`/`Float` flowing into a `Number`-like union enum is wrapped in the
|
|
735
|
+
/// matching variant (the old monomorphizer's `wrapPrimitiveAgainstExpected`).
|
|
736
|
+
fn compileExprInto(
|
|
737
|
+
expr: &ast::Expr,
|
|
738
|
+
decl: &PlumType,
|
|
739
|
+
body: &mut Vec<u8>,
|
|
740
|
+
ctx: &LocalCtx,
|
|
741
|
+
state: &mut ModuleState,
|
|
742
|
+
) -> Result<(), String> {
|
|
743
|
+
let actual = inferLocalType(expr, ctx);
|
|
744
|
+
if let Some(variant) = primitiveWrapVariant(decl, &actual, ctx) {
|
|
745
|
+
if let Some(&vidx) = ctx.gc_types.variant_type_idx.get(&variant) {
|
|
746
|
+
Instruction::I32Const(typeIdOf(ctx.gc_types, &variant)).encode(body);
|
|
747
|
+
compileExpr(expr, body, ctx, state)?;
|
|
748
|
+
Instruction::StructNew(vidx).encode(body);
|
|
749
|
+
return Ok(());
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
compileExprAs(expr, Some(plumTypeToValtype(decl)), body, ctx, state)
|
|
753
|
+
}
|
|
754
|
+
|
|
671
755
|
/// Inverse of `boxTagPayload`: an `anyref` payload on the stack becomes a `ty` value.
|
|
672
756
|
fn unboxTagPayload(ty: &PlumType, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
673
757
|
let unbox = |idx: u32, body: &mut Vec<u8>| {
|
|
@@ -771,6 +855,8 @@ fn astTypeToWasm(name: &str) -> Option<ValType> {
|
|
|
771
855
|
other if other.starts_with('[') && !other.starts_with("[]") => {
|
|
772
856
|
Some(withGcTypes(|r| gcRef(r.tag_type_idx)))
|
|
773
857
|
}
|
|
858
|
+
// A generic parameter (`T`) is erased to the common `$Obj` supertype.
|
|
859
|
+
other if isGenericName(other) => Some(plumTypeToValtype(&PlumType::TParam(other.to_string()))),
|
|
774
860
|
other => Some(plumTypeToValtype(&PlumType::TNamed(other.to_string()))),
|
|
775
861
|
}
|
|
776
862
|
}
|
|
@@ -853,7 +939,8 @@ fn plumTypeToValtype(t: &PlumType) -> ValType {
|
|
|
853
939
|
// existing "not modeled as a concrete shape here" treatment.
|
|
854
940
|
PlumType::TTags(_) => withGcTypes(|r| gcRef(r.tag_type_idx)),
|
|
855
941
|
PlumType::TFun(_, _) | PlumType::TVariadic(_) => ValType::Ref(RefType::ANYREF),
|
|
942
|
+
PlumType::TParam(_) => withGcTypes(|r| gcRef(r.obj_type_idx)),
|
|
856
|
-
PlumType::TVar(_) | PlumType::
|
|
943
|
+
PlumType::TVar(_) | PlumType::TUnit => ValType::I64,
|
|
857
944
|
}
|
|
858
945
|
}
|
|
859
946
|
|
|
@@ -948,7 +1035,7 @@ fn typeIdOf(r: &GcTypeRegistry, name: &str) -> i32 {
|
|
|
948
1035
|
/// compiler intrinsics rather than compiled `todo` bodies (see
|
|
949
1036
|
/// `compileIntrinsicFnBody`).
|
|
950
1037
|
fn isArraySpecialization(name: &str) -> bool {
|
|
951
|
-
name == "Array"
|
|
1038
|
+
name == "Array"
|
|
952
1039
|
}
|
|
953
1040
|
|
|
954
1041
|
/// Resolves a plum type to the wasm-gc `ValType` its values are represented as, given
|
|
@@ -1008,7 +1095,8 @@ fn plumTypeToGcValtype(t: &PlumType, registry: &GcTypeRegistry) -> ValType {
|
|
|
1008
1095
|
// yet-meaningful placeholder in the meantime, since nothing consumes it yet.
|
|
1009
1096
|
PlumType::TTags(_) => gcRef(registry.tag_type_idx),
|
|
1010
1097
|
PlumType::TFun(_, _) | PlumType::TVariadic(_) => ValType::Ref(RefType::ANYREF),
|
|
1098
|
+
PlumType::TParam(_) => gcRef(registry.obj_type_idx),
|
|
1011
|
-
PlumType::TVar(_) | PlumType::
|
|
1099
|
+
PlumType::TVar(_) | PlumType::TUnit => ValType::I64,
|
|
1012
1100
|
}
|
|
1013
1101
|
}
|
|
1014
1102
|
|
|
@@ -1051,9 +1139,6 @@ fn buildGcTypeRegistry(
|
|
|
1051
1139
|
let mut enum_super_type_idx: HashMap<String, u32> = HashMap::new();
|
|
1052
1140
|
let mut variant_type_idx: HashMap<String, u32> = HashMap::new();
|
|
1053
1141
|
let mut array_ref_slot: Option<u32> = None;
|
|
1054
|
-
let mut erased_enum_super: HashMap<String, u32> = HashMap::new();
|
|
1055
|
-
let mut erased_variant_idx: HashMap<String, u32> = HashMap::new();
|
|
1056
|
-
let mut erased_enum_id: HashMap<String, i32> = HashMap::new();
|
|
1057
1142
|
|
|
1058
1143
|
// `Bool` is an ordinary enum now (`type Bool = | True | False` in
|
|
1059
1144
|
// `libs/std/Bool.plum`) — registered exactly like any other enum
|
|
@@ -1094,32 +1179,6 @@ fn buildGcTypeRegistry(
|
|
|
1094
1179
|
}
|
|
1095
1180
|
continue;
|
|
1096
1181
|
}
|
|
1097
|
-
// Specializations that differ only in their tag-union segments
|
|
1098
|
-
// (`Try$Int$[A]` / `Try$Int$[A,B]`) share ONE set of wasm types — every
|
|
1099
|
-
// tag value is the same `$Tag` struct — so a later one is aliased onto
|
|
1100
|
-
// the first one's slots instead of getting its own.
|
|
1101
|
-
let erased_enum = plum_checker::erasedEnumName(enum_name);
|
|
1102
|
-
if let Some(existing_super) = erased_enum_super.get(&erased_enum) {
|
|
1103
|
-
enum_super_type_idx.insert(enum_name.clone(), *existing_super);
|
|
1104
|
-
// Shares the first specialization's wasm types, and so its typeId.
|
|
1105
|
-
if let Some(first_id) = erased_enum_id.get(&erased_enum).copied() {
|
|
1106
|
-
type_ids.insert(enum_name.clone(), first_id);
|
|
1107
|
-
for vname in variant_names {
|
|
1108
|
-
type_ids.insert(vname.clone(), first_id);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
for vname in variant_names {
|
|
1112
|
-
let ev = plum_checker::erasedEnumName(vname);
|
|
1113
|
-
if let Some(idx) = erased_variant_idx.get(&ev) {
|
|
1114
|
-
variant_type_idx.insert(vname.clone(), *idx);
|
|
1115
|
-
continue;
|
|
1116
|
-
}
|
|
1117
|
-
variant_type_idx.insert(vname.clone(), slots.len() as u32);
|
|
1118
|
-
erased_variant_idx.insert(ev, slots.len() as u32);
|
|
1119
|
-
slots.push(Slot::Variant(vname.clone()));
|
|
1120
|
-
}
|
|
1121
|
-
continue;
|
|
1122
|
-
}
|
|
1123
1182
|
let this_id = match enum_name.as_str() {
|
|
1124
1183
|
"Str" => TYPE_ID_STR,
|
|
1125
1184
|
"Buffer" => TYPE_ID_BUFFER,
|
|
@@ -1133,13 +1192,10 @@ fn buildGcTypeRegistry(
|
|
|
1133
1192
|
for vname in variant_names {
|
|
1134
1193
|
type_ids.insert(vname.clone(), this_id);
|
|
1135
1194
|
}
|
|
1136
|
-
erased_enum_id.insert(erased_enum.clone(), this_id);
|
|
1137
1195
|
enum_super_type_idx.insert(enum_name.clone(), slots.len() as u32);
|
|
1138
|
-
erased_enum_super.insert(erased_enum, slots.len() as u32);
|
|
1139
1196
|
slots.push(Slot::EnumSuper(enum_name.clone()));
|
|
1140
1197
|
for vname in variant_names {
|
|
1141
1198
|
variant_type_idx.insert(vname.clone(), slots.len() as u32);
|
|
1142
|
-
erased_variant_idx.insert(plum_checker::erasedEnumName(vname), slots.len() as u32);
|
|
1143
1199
|
slots.push(Slot::Variant(vname.clone()));
|
|
1144
1200
|
}
|
|
1145
1201
|
// A RECORD-shaped enum (the `type X = ...` replacement, e.g. `enum Cat =
|
|
@@ -1347,21 +1403,6 @@ fn boxSubType(vt: ValType, obj_type_idx: u32) -> SubType {
|
|
|
1347
1403
|
}
|
|
1348
1404
|
}
|
|
1349
1405
|
|
|
1350
|
-
/// Maps an `ast::ParamType::Fn(params, ret)` to the wasm signature of the *closure
|
|
1351
|
-
/// function* it compiles to: an implicit leading `env: anyref`, then one param per
|
|
1352
|
-
/// declared param type, returning `ret`'s val type (or nothing for `Unit`).
|
|
1353
|
-
fn fnParamTypeToWasmSig(
|
|
1354
|
-
params: &[ast::Type],
|
|
1355
|
-
ret: &Option<Box<ast::Type>>,
|
|
1356
|
-
) -> (Vec<ValType>, Option<ValType>) {
|
|
1357
|
-
let mut vts = vec![ValType::Ref(RefType::ANYREF)]; // env pointer
|
|
1358
|
-
for p in params {
|
|
1359
|
-
vts.push(astTypeToWasm(&p.name).unwrap_or(ValType::I32));
|
|
1360
|
-
}
|
|
1361
|
-
let ret_vt = ret.as_ref().and_then(|t| astTypeToWasm(&t.name));
|
|
1362
|
-
(vts, ret_vt)
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
1406
|
fn blockTypeFor(result_vt: Option<ValType>) -> BlockType {
|
|
1366
1407
|
result_vt.map(BlockType::Result).unwrap_or(BlockType::Empty)
|
|
1367
1408
|
}
|
|
@@ -1414,6 +1455,16 @@ fn checkCtxOf<'a>(
|
|
|
1414
1455
|
/// already accepted by `plum_checker::checkSource`, so a failure here would indicate
|
|
1415
1456
|
/// codegen is being driven directly on unchecked input (as some tests do).
|
|
1416
1457
|
fn inferLocalType(expr: &ast::Expr, ctx: &LocalCtx) -> PlumType {
|
|
1458
|
+
// The checker's instantiated type for this exact expression node, if any;
|
|
1459
|
+
// synthesized nodes (desugarings built during codegen) fall back to
|
|
1460
|
+
// re-inference against the local type env.
|
|
1461
|
+
if let Some(t) = ctx.types.get(&(expr as *const ast::Expr as usize)) {
|
|
1462
|
+
return t.clone();
|
|
1463
|
+
}
|
|
1464
|
+
inferFallbackType(expr, ctx)
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
fn inferFallbackType(expr: &ast::Expr, ctx: &LocalCtx) -> PlumType {
|
|
1417
1468
|
let env = ctx.type_env.borrow();
|
|
1418
1469
|
let cctx = checkCtxOf(
|
|
1419
1470
|
ctx.methods,
|
|
@@ -1425,6 +1476,14 @@ fn inferLocalType(expr: &ast::Expr, ctx: &LocalCtx) -> PlumType {
|
|
|
1425
1476
|
}
|
|
1426
1477
|
|
|
1427
1478
|
pub fn compileSource(source: &ast::Source) -> Result<Vec<u8>, String> {
|
|
1479
|
+
compileSourceInner(source, &[]).map(|(bytes, _)| bytes)
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
/// Like `compileSource`, also returning the key of every compiled function
|
|
1483
|
+
/// (`name` or `Receiver::method`) — one entry per source function, since a
|
|
1484
|
+
/// generic function/method compiles exactly once however many types it is
|
|
1485
|
+
/// instantiated at.
|
|
1486
|
+
pub fn compileSourceWithFunctions(source: &ast::Source) -> Result<(Vec<u8>, Vec<String>), String> {
|
|
1428
1487
|
compileSourceInner(source, &[])
|
|
1429
1488
|
}
|
|
1430
1489
|
|
|
@@ -1446,7 +1505,7 @@ pub fn compileTestSource(source: &ast::Source) -> Result<(Vec<u8>, Vec<String>),
|
|
|
1446
1505
|
.iter()
|
|
1447
1506
|
.map(|(_, export_name)| export_name["test#".len()..].to_string())
|
|
1448
1507
|
.collect();
|
|
1449
|
-
let wasm = compileSourceInner(&desugared, &extra_exports)?;
|
|
1508
|
+
let (wasm, _) = compileSourceInner(&desugared, &extra_exports)?;
|
|
1450
1509
|
Ok((wasm, names))
|
|
1451
1510
|
}
|
|
1452
1511
|
|
|
@@ -1616,26 +1675,35 @@ fn desugarAssertStmt(stmt: &ast::Stmt) -> ast::Stmt {
|
|
|
1616
1675
|
fn compileSourceInner(
|
|
1617
1676
|
source: &ast::Source,
|
|
1618
1677
|
extra_exports: &[(String, String)],
|
|
1619
|
-
) -> Result<Vec<u8>, String> {
|
|
1678
|
+
) -> Result<(Vec<u8>, Vec<String>), String> {
|
|
1679
|
+
// The source is compiled as written (generics erased, not specialized): the
|
|
1680
|
+
// checker's per-expression instantiated types drive every box/unbox decision.
|
|
1620
|
-
let
|
|
1681
|
+
let typed = plum_checker::typedSource(source).map_err(|errs| {
|
|
1682
|
+
errs.iter()
|
|
1683
|
+
.map(|e| format!("type error: {}", e.message))
|
|
1684
|
+
.collect::<Vec<_>>()
|
|
1685
|
+
.join("\n")
|
|
1686
|
+
})?;
|
|
1687
|
+
let types = typed.expr_types;
|
|
1621
|
-
let (global_env, methods, enum_variants, enum_params) = plum_checker::
|
|
1688
|
+
let (global_env, methods, enum_variants, enum_params) = plum_checker::buildTables(source, true);
|
|
1622
1689
|
let min_required = plum_checker::buildMinRequiredArgs(source);
|
|
1623
1690
|
|
|
1624
1691
|
// Intern every tag name the program's types mention, in sorted order.
|
|
1625
1692
|
{
|
|
1626
1693
|
let mut names = std::collections::BTreeSet::new();
|
|
1627
|
-
for
|
|
1694
|
+
for info in enum_variants.values() {
|
|
1628
|
-
collectTagNames(k, &mut names);
|
|
1629
|
-
collectTagNames(&info.enum_name, &mut names);
|
|
1630
1695
|
for t in &info.field_types {
|
|
1631
|
-
|
|
1696
|
+
collectTagNamesTy(t, &mut names);
|
|
1632
1697
|
}
|
|
1633
1698
|
}
|
|
1634
1699
|
for scheme in global_env.values() {
|
|
1635
|
-
|
|
1700
|
+
collectTagNamesTy(&scheme.body, &mut names);
|
|
1636
1701
|
}
|
|
1637
1702
|
for t in methods.values() {
|
|
1638
|
-
|
|
1703
|
+
collectTagNamesTy(t, &mut names);
|
|
1704
|
+
}
|
|
1705
|
+
for t in types.values() {
|
|
1706
|
+
collectTagNamesTy(t, &mut names);
|
|
1639
1707
|
}
|
|
1640
1708
|
CURRENT_TAG_IDS.with(|c| {
|
|
1641
1709
|
*c.borrow_mut() = names
|
|
@@ -1726,6 +1794,12 @@ fn compileSourceInner(
|
|
|
1726
1794
|
params: param_types,
|
|
1727
1795
|
ret,
|
|
1728
1796
|
param_defaults: Vec::new(),
|
|
1797
|
+
decl_params: f.params.iter().map(|p| paramPlumType(&p.ty)).collect(),
|
|
1798
|
+
decl_ret: f
|
|
1799
|
+
.returns
|
|
1800
|
+
.as_ref()
|
|
1801
|
+
.map(plum_checker::plumTypeFromAstG)
|
|
1802
|
+
.unwrap_or(PlumType::TUnit),
|
|
1729
1803
|
},
|
|
1730
1804
|
);
|
|
1731
1805
|
}
|
|
@@ -1814,9 +1888,14 @@ fn compileSourceInner(
|
|
|
1814
1888
|
}
|
|
1815
1889
|
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
1816
1890
|
|
|
1891
|
+
// Closure ABI (see `ClosureSigKey`): one erased `call_indirect` type per arity,
|
|
1817
|
-
//
|
|
1892
|
+
// `(env, anyref * n) -> anyref`, shared by every closure and call site.
|
|
1818
|
-
// of the same shape shares one `call_indirect` type.
|
|
1819
1893
|
let mut closure_call_types: HashMap<ClosureSigKey, u32> = HashMap::new();
|
|
1894
|
+
for arity in 0..=MAX_CLOSURE_ARITY {
|
|
1895
|
+
let params = vec![ValType::Ref(RefType::ANYREF); arity + 1];
|
|
1896
|
+
let t = module.addType(¶ms, &[ValType::Ref(RefType::ANYREF)]);
|
|
1897
|
+
closure_call_types.insert(arity, t);
|
|
1898
|
+
}
|
|
1820
1899
|
|
|
1821
1900
|
// Register every function AND method signature up front (methods get an implicit
|
|
1822
1901
|
// leading `self: pointer` param and are keyed as "Receiver::method").
|
|
@@ -1846,23 +1925,14 @@ fn compileSourceInner(
|
|
|
1846
1925
|
params: param_types,
|
|
1847
1926
|
ret,
|
|
1848
1927
|
param_defaults,
|
|
1928
|
+
decl_params: f.params.iter().map(|p| paramPlumType(&p.ty)).collect(),
|
|
1929
|
+
decl_ret: f
|
|
1930
|
+
.returns
|
|
1931
|
+
.as_ref()
|
|
1932
|
+
.map(plum_checker::plumTypeFromAstG)
|
|
1933
|
+
.unwrap_or(PlumType::TUnit),
|
|
1849
1934
|
},
|
|
1850
1935
|
);
|
|
1851
|
-
|
|
1852
|
-
// Any `fn(...) -> ...`-typed param is callable via `call_indirect`; register
|
|
1853
|
-
// its wasm signature (leading env-ptr param included) so call sites can
|
|
1854
|
-
// resolve a consistent type index even if no matching closure literal exists.
|
|
1855
|
-
for p in &f.params {
|
|
1856
|
-
if let ast::ParamType::Fn(params, ret) = &p.ty {
|
|
1857
|
-
let (sig_params, ret_vt) = fnParamTypeToWasmSig(params, ret);
|
|
1858
|
-
let sig_key: ClosureSigKey = (sig_params.clone(), ret_vt);
|
|
1859
|
-
if !closure_call_types.contains_key(&sig_key) {
|
|
1860
|
-
let results: Vec<ValType> = ret_vt.into_iter().collect();
|
|
1861
|
-
let tidx = module.addType(&sig_params, &results);
|
|
1862
|
-
closure_call_types.insert(sig_key, tidx);
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
1936
|
}
|
|
1867
1937
|
}
|
|
1868
1938
|
|
|
@@ -1908,21 +1978,12 @@ fn compileSourceInner(
|
|
|
1908
1978
|
let mut raw_closures: Vec<RawClosure> = Vec::new();
|
|
1909
1979
|
let mut named_fn_refs: HashSet<String> = HashSet::new();
|
|
1910
1980
|
for f in &fns {
|
|
1911
|
-
let mut env = global_env.clone();
|
|
1912
|
-
if let Some(recv) = &f.type_param {
|
|
1913
|
-
env.insert(
|
|
1914
|
-
"self".to_string(),
|
|
1915
|
-
TypeScheme::mono(plum_checker::plumTypeFromName(recv)),
|
|
1916
|
-
);
|
|
1917
|
-
}
|
|
1918
1981
|
let mut locals: HashSet<String> = HashSet::new();
|
|
1919
1982
|
for p in &f.params {
|
|
1920
|
-
env.insert(p.name.clone(), TypeScheme::mono(paramPlumType(&p.ty)));
|
|
1921
1983
|
locals.insert(p.name.clone());
|
|
1922
1984
|
}
|
|
1923
1985
|
let mut walker = ClosureWalker {
|
|
1924
|
-
env,
|
|
1925
|
-
|
|
1986
|
+
types: &types,
|
|
1926
1987
|
fn_decls: &fn_decls,
|
|
1927
1988
|
found: Vec::new(),
|
|
1928
1989
|
locals,
|
|
@@ -1930,7 +1991,7 @@ fn compileSourceInner(
|
|
|
1930
1991
|
};
|
|
1931
1992
|
match &f.body {
|
|
1932
1993
|
ast::FnBody::Block(block) => walker.walkBlock(block),
|
|
1933
|
-
ast::FnBody::Expr(e) => walker.walkExpr(e
|
|
1994
|
+
ast::FnBody::Expr(e) => walker.walkExpr(e),
|
|
1934
1995
|
// `fns` excludes every `extern fun` (no body to discover closures in).
|
|
1935
1996
|
ast::FnBody::Extern => unreachable!("extern fns are excluded from `fns`"),
|
|
1936
1997
|
}
|
|
@@ -1990,18 +2051,10 @@ fn compileSourceInner(
|
|
|
1990
2051
|
let mut closures: HashMap<usize, ClosureInfo> = HashMap::new();
|
|
1991
2052
|
let mut closure_asts: HashMap<usize, &ast::Closure> = HashMap::new();
|
|
1992
2053
|
for (i, rc) in raw_closures.iter().enumerate() {
|
|
1993
|
-
let mut sig_params = vec![ValType::Ref(RefType::ANYREF)]; // env pointer
|
|
1994
|
-
sig_params.extend(rc.param_vts.iter().copied());
|
|
1995
|
-
let
|
|
2054
|
+
let arity = rc.closure.params.len();
|
|
1996
|
-
let type_idx =
|
|
2055
|
+
let type_idx = *closure_call_types
|
|
1997
|
-
|
|
2056
|
+
.get(&arity)
|
|
1998
|
-
None => {
|
|
1999
|
-
|
|
2057
|
+
.ok_or_else(|| format!("codegen: closures with {} params are not supported", arity))?;
|
|
2000
|
-
let t = module.addType(&sig_params, &results);
|
|
2001
|
-
closure_call_types.insert(sig_key, t);
|
|
2002
|
-
t
|
|
2003
|
-
}
|
|
2004
|
-
};
|
|
2005
2058
|
let func_idx = module.addFunction(type_idx, &[]);
|
|
2006
2059
|
let table_idx = module.addTableElement(func_idx);
|
|
2007
2060
|
closures.insert(
|
|
@@ -2010,9 +2063,8 @@ fn compileSourceInner(
|
|
|
2010
2063
|
func_idx,
|
|
2011
2064
|
table_idx,
|
|
2012
2065
|
env_type_idx: closure_type_indices[i + 1],
|
|
2013
|
-
param_vts: rc.param_vts.clone(),
|
|
2014
2066
|
param_ptypes: rc.param_ptypes.clone(),
|
|
2015
|
-
|
|
2067
|
+
ret_ptype: rc.ret_ptype.clone(),
|
|
2016
2068
|
free_vars: rc.free_vars.clone(),
|
|
2017
2069
|
},
|
|
2018
2070
|
);
|
|
@@ -2021,10 +2073,11 @@ fn compileSourceInner(
|
|
|
2021
2073
|
|
|
2022
2074
|
// Register a zero-capture "trampoline" closure for every top-level function
|
|
2023
2075
|
// referenced as a bare value (e.g. `each(double)`): a real wasm function with the
|
|
2024
|
-
// closure
|
|
2076
|
+
// erased closure ABI `(env, anyref...) -> anyref` that ignores its env, unboxes
|
|
2077
|
+
// each argument to the real function's parameter representation, forwards to the
|
|
2025
|
-
//
|
|
2078
|
+
// real function and boxes its result, plus a funcref-table entry for it. Since
|
|
2026
|
-
//
|
|
2079
|
+
// it never captures anything, its `{table_idx, env=null}` closure struct is a
|
|
2027
|
-
//
|
|
2080
|
+
// compile-time constant — like every payload-free enum variant, built once by
|
|
2028
2081
|
// the shared `start` function and stored in its own global.
|
|
2029
2082
|
let mut named_fn_values: HashMap<String, u32> = HashMap::new();
|
|
2030
2083
|
for name in &named_fn_refs {
|
|
@@ -2034,26 +2087,22 @@ fn compileSourceInner(
|
|
|
2034
2087
|
let real_func_idx = *func_ids
|
|
2035
2088
|
.get(name)
|
|
2036
2089
|
.expect("named fn ref must be a registered top-level function");
|
|
2037
|
-
|
|
2038
|
-
let mut sig_params = vec![ValType::Ref(RefType::ANYREF)]; // env pointer
|
|
2039
|
-
|
|
2090
|
+
let arity = sig.params.len();
|
|
2040
|
-
let sig_key: ClosureSigKey = (sig_params.clone(), sig.ret);
|
|
2041
|
-
let type_idx =
|
|
2091
|
+
let type_idx = *closure_call_types
|
|
2042
|
-
|
|
2092
|
+
.get(&arity)
|
|
2043
|
-
None => {
|
|
2044
|
-
|
|
2093
|
+
.ok_or_else(|| format!("codegen: function '{}' has too many params to use as a value", name))?;
|
|
2045
|
-
let t = module.addType(&sig_params, &results);
|
|
2046
|
-
closure_call_types.insert(sig_key, t);
|
|
2047
|
-
t
|
|
2048
|
-
}
|
|
2049
|
-
};
|
|
2050
2094
|
|
|
2051
2095
|
let mut tbody = Vec::new();
|
|
2052
2096
|
tbody.push(0u8); // no locals beyond the params already in the signature
|
|
2053
|
-
for i in
|
|
2097
|
+
for (i, vt) in sig.params.iter().enumerate() {
|
|
2054
2098
|
Instruction::LocalGet((i + 1) as u32).encode(&mut tbody); // local 0 is env, ignored
|
|
2099
|
+
coerceVt(ValType::Ref(RefType::ANYREF), *vt, &mut tbody, &gc_types);
|
|
2055
2100
|
}
|
|
2056
2101
|
Instruction::Call(real_func_idx).encode(&mut tbody);
|
|
2102
|
+
match sig.ret {
|
|
2103
|
+
Some(rvt) => coerceVt(rvt, ValType::Ref(RefType::ANYREF), &mut tbody, &gc_types),
|
|
2104
|
+
None => Instruction::RefNull(HeapType::ANY).encode(&mut tbody),
|
|
2105
|
+
}
|
|
2057
2106
|
Instruction::End.encode(&mut tbody);
|
|
2058
2107
|
let func_idx = module.addFunction(type_idx, &tbody);
|
|
2059
2108
|
let table_idx = module.addTableElement(func_idx);
|
|
@@ -2084,6 +2133,7 @@ fn compileSourceInner(
|
|
|
2084
2133
|
// (unconditionally, for simplicity) since they're cheap and self-contained.
|
|
2085
2134
|
let string_concat_func = registerStringConcatHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2086
2135
|
let string_eq_func = registerStringEqHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2136
|
+
let dyn_eq_func = registerDynEqHelper(&mut module, &gc_types, string_eq_func);
|
|
2087
2137
|
let int_to_string_func = registerIntToStringHelper(&mut module, gc_types.byte_array_type_idx);
|
|
2088
2138
|
|
|
2089
2139
|
let ctx = CompileCtx {
|
|
@@ -2097,9 +2147,11 @@ fn compileSourceInner(
|
|
|
2097
2147
|
closures,
|
|
2098
2148
|
closure_asts,
|
|
2099
2149
|
closure_call_types,
|
|
2150
|
+
types,
|
|
2100
2151
|
named_fn_values,
|
|
2101
2152
|
string_concat_func,
|
|
2102
2153
|
string_eq_func,
|
|
2154
|
+
dyn_eq_func,
|
|
2103
2155
|
int_to_string_func,
|
|
2104
2156
|
gc_types,
|
|
2105
2157
|
singleton_globals,
|
|
@@ -2163,7 +2215,14 @@ fn compileSourceInner(
|
|
|
2163
2215
|
module.addExport(export_name, ExportKind::Func, idx);
|
|
2164
2216
|
}
|
|
2165
2217
|
|
|
2218
|
+
let mut fn_keys: Vec<String> = ctx
|
|
2219
|
+
.func_ids
|
|
2220
|
+
.keys()
|
|
2221
|
+
.filter(|k| k.as_str() != CRASH_FN_KEY)
|
|
2222
|
+
.cloned()
|
|
2223
|
+
.collect();
|
|
2224
|
+
fn_keys.sort();
|
|
2166
|
-
Ok(module.finish())
|
|
2225
|
+
Ok((module.finish(), fn_keys))
|
|
2167
2226
|
}
|
|
2168
2227
|
|
|
2169
2228
|
/// `(payload) -> ref $Box*`: allocates a box with its header typeId set.
|
|
@@ -2337,6 +2396,98 @@ fn registerDynDispatchers(
|
|
|
2337
2396
|
gc_types.dyn_fns.extend(registered);
|
|
2338
2397
|
}
|
|
2339
2398
|
|
|
2399
|
+
/// Registers `__dyn_eq(a: ref null $Obj, b: ref null $Obj) -> i32`, the `==` of two
|
|
2400
|
+
/// erased (`TParam`-typed) values: reference identity first, then — for two
|
|
2401
|
+
/// values of the same `typeId` — payload equality for boxed `Int`/`Float`/`Byte`
|
|
2402
|
+
/// and byte-for-byte content equality for `Str`. Everything else (class/enum
|
|
2403
|
+
/// instances) is identity, exactly like `==` on a concrete reference type.
|
|
2404
|
+
fn registerDynEqHelper(module: &mut WasmModule, g: &GcTypeRegistry, string_eq_func: u32) -> u32 {
|
|
2405
|
+
let obj = g.obj_type_idx;
|
|
2406
|
+
let mut b = vec![0u8];
|
|
2407
|
+
let tid = |b: &mut Vec<u8>, local: u32| {
|
|
2408
|
+
Instruction::LocalGet(local).encode(b);
|
|
2409
|
+
Instruction::StructGet {
|
|
2410
|
+
struct_type_index: obj,
|
|
2411
|
+
field_index: 0,
|
|
2412
|
+
}
|
|
2413
|
+
.encode(b);
|
|
2414
|
+
};
|
|
2415
|
+
let i32r = BlockType::Result(ValType::I32);
|
|
2416
|
+
Instruction::LocalGet(0).encode(&mut b);
|
|
2417
|
+
Instruction::LocalGet(1).encode(&mut b);
|
|
2418
|
+
Instruction::RefEq.encode(&mut b);
|
|
2419
|
+
Instruction::If(i32r).encode(&mut b);
|
|
2420
|
+
Instruction::I32Const(1).encode(&mut b);
|
|
2421
|
+
Instruction::Else.encode(&mut b);
|
|
2422
|
+
// either null -> not equal
|
|
2423
|
+
Instruction::LocalGet(0).encode(&mut b);
|
|
2424
|
+
Instruction::RefIsNull.encode(&mut b);
|
|
2425
|
+
Instruction::LocalGet(1).encode(&mut b);
|
|
2426
|
+
Instruction::RefIsNull.encode(&mut b);
|
|
2427
|
+
Instruction::I32Or.encode(&mut b);
|
|
2428
|
+
Instruction::If(i32r).encode(&mut b);
|
|
2429
|
+
Instruction::I32Const(0).encode(&mut b);
|
|
2430
|
+
Instruction::Else.encode(&mut b);
|
|
2431
|
+
// different dynamic types -> not equal
|
|
2432
|
+
tid(&mut b, 0);
|
|
2433
|
+
tid(&mut b, 1);
|
|
2434
|
+
Instruction::I32Ne.encode(&mut b);
|
|
2435
|
+
Instruction::If(i32r).encode(&mut b);
|
|
2436
|
+
Instruction::I32Const(0).encode(&mut b);
|
|
2437
|
+
Instruction::Else.encode(&mut b);
|
|
2438
|
+
let mut ends = 0;
|
|
2439
|
+
for (id, box_idx, eq) in [
|
|
2440
|
+
(TYPE_ID_BOX_I64, g.box_i64_idx, Instruction::I64Eq),
|
|
2441
|
+
(TYPE_ID_BOX_F64, g.box_f64_idx, Instruction::F64Eq),
|
|
2442
|
+
(TYPE_ID_BOX_I32, g.box_i32_idx, Instruction::I32Eq),
|
|
2443
|
+
] {
|
|
2444
|
+
tid(&mut b, 0);
|
|
2445
|
+
Instruction::I32Const(id).encode(&mut b);
|
|
2446
|
+
Instruction::I32Eq.encode(&mut b);
|
|
2447
|
+
Instruction::If(i32r).encode(&mut b);
|
|
2448
|
+
for local in [0, 1] {
|
|
2449
|
+
Instruction::LocalGet(local).encode(&mut b);
|
|
2450
|
+
Instruction::RefCastNonNull(HeapType::Concrete(box_idx)).encode(&mut b);
|
|
2451
|
+
Instruction::StructGet {
|
|
2452
|
+
struct_type_index: box_idx,
|
|
2453
|
+
field_index: fld(0),
|
|
2454
|
+
}
|
|
2455
|
+
.encode(&mut b);
|
|
2456
|
+
}
|
|
2457
|
+
eq.encode(&mut b);
|
|
2458
|
+
Instruction::Else.encode(&mut b);
|
|
2459
|
+
ends += 1;
|
|
2460
|
+
}
|
|
2461
|
+
match g.class_type_idx.get("Str") {
|
|
2462
|
+
Some(&str_idx) => {
|
|
2463
|
+
tid(&mut b, 0);
|
|
2464
|
+
Instruction::I32Const(TYPE_ID_STR).encode(&mut b);
|
|
2465
|
+
Instruction::I32Eq.encode(&mut b);
|
|
2466
|
+
Instruction::If(i32r).encode(&mut b);
|
|
2467
|
+
for local in [0, 1] {
|
|
2468
|
+
Instruction::LocalGet(local).encode(&mut b);
|
|
2469
|
+
Instruction::RefCastNonNull(HeapType::Concrete(str_idx)).encode(&mut b);
|
|
2470
|
+
}
|
|
2471
|
+
Instruction::Call(string_eq_func).encode(&mut b);
|
|
2472
|
+
Instruction::Else.encode(&mut b);
|
|
2473
|
+
Instruction::I32Const(0).encode(&mut b);
|
|
2474
|
+
Instruction::End.encode(&mut b);
|
|
2475
|
+
}
|
|
2476
|
+
None => Instruction::I32Const(0).encode(&mut b),
|
|
2477
|
+
}
|
|
2478
|
+
for _ in 0..ends {
|
|
2479
|
+
Instruction::End.encode(&mut b);
|
|
2480
|
+
}
|
|
2481
|
+
// close: type-id-ne `if`, null `if`, identity `if`
|
|
2482
|
+
Instruction::End.encode(&mut b);
|
|
2483
|
+
Instruction::End.encode(&mut b);
|
|
2484
|
+
Instruction::End.encode(&mut b);
|
|
2485
|
+
Instruction::End.encode(&mut b);
|
|
2486
|
+
let obj_ref = gcRef(obj);
|
|
2487
|
+
let t = module.addType(&[obj_ref, obj_ref], &[ValType::I32]);
|
|
2488
|
+
module.addFunction(t, &b)
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2340
2491
|
/// Registers `__string_concat(a: ref Str, b: ref Str) -> ref Str`, a hand-written
|
|
2341
2492
|
/// runtime helper backing string interpolation. Unwraps both `Str` params down
|
|
2342
2493
|
/// to their raw `array<i8>`s (`unwrapStrToBytes`) — which each track their own
|
|
@@ -2938,18 +3089,19 @@ fn compileArrayIntrinsicFnBody(f: &ast::Fn) -> Option<Vec<u8>> {
|
|
|
2938
3089
|
}
|
|
2939
3090
|
}
|
|
2940
3091
|
|
|
2941
|
-
/// The `PlumType` of a declared parameter, including `fn(...)`-typed params as `TFun`
|
|
3092
|
+
/// The `PlumType` of a declared parameter, including `fn(...)`-typed params as `TFun`;
|
|
3093
|
+
/// a single-letter generic name is a `TParam`.
|
|
2942
3094
|
fn paramPlumType(pt: &ast::ParamType) -> PlumType {
|
|
2943
3095
|
match pt {
|
|
2944
|
-
ast::ParamType::Type(t) => plum_checker::
|
|
3096
|
+
ast::ParamType::Type(t) => plum_checker::plumTypeFromAstG(t),
|
|
2945
3097
|
ast::ParamType::Variadic(t) => {
|
|
2946
|
-
PlumType::TVariadic(Box::new(plum_checker::
|
|
3098
|
+
PlumType::TVariadic(Box::new(plum_checker::plumTypeFromAstG(t)))
|
|
2947
3099
|
}
|
|
2948
3100
|
ast::ParamType::Fn(params, ret) => {
|
|
2949
|
-
let param_types = params.iter().map(plum_checker::
|
|
3101
|
+
let param_types = params.iter().map(plum_checker::plumTypeFromAstG).collect();
|
|
2950
3102
|
let ret_ty = ret
|
|
2951
3103
|
.as_ref()
|
|
2952
|
-
.map(|r| plum_checker::
|
|
3104
|
+
.map(|r| plum_checker::plumTypeFromAstG(r))
|
|
2953
3105
|
.unwrap_or(PlumType::TUnit);
|
|
2954
3106
|
PlumType::TFun(param_types, Box::new(ret_ty))
|
|
2955
3107
|
}
|
|
@@ -2961,20 +3113,25 @@ fn paramPlumType(pt: &ast::ParamType) -> PlumType {
|
|
|
2961
3113
|
struct RawClosure<'a> {
|
|
2962
3114
|
ptr: usize,
|
|
2963
3115
|
closure: &'a ast::Closure,
|
|
2964
|
-
param_vts: Vec<ValType>,
|
|
2965
3116
|
param_ptypes: Vec<PlumType>,
|
|
2966
|
-
|
|
3117
|
+
ret_ptype: PlumType,
|
|
2967
3118
|
free_vars: Vec<(String, PlumType)>,
|
|
2968
3119
|
}
|
|
2969
3120
|
|
|
3121
|
+
fn argExprOf(arg: &ast::Arg) -> &ast::Expr {
|
|
3122
|
+
match arg {
|
|
3123
|
+
ast::Arg::Positional(e) => e,
|
|
3124
|
+
ast::Arg::Keyword { value, .. } => value,
|
|
3125
|
+
ast::Arg::Pair { value, .. } => value,
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
|
|
2970
|
-
/// Walks a function body
|
|
3129
|
+
/// Walks a function body to find every closure literal and the free variables it
|
|
3130
|
+
/// captures. A closure's Plum signature is the checker's instantiated `TFun` type
|
|
2971
|
-
///
|
|
3131
|
+
/// of the literal (`types`); its wasm signature is the erased closure ABI
|
|
2972
|
-
/// free variables. A closure passed directly as a `fn(...)`-typed call argument takes
|
|
2973
|
-
/// its signature from that declared param type; any other closure (e.g. one assigned to
|
|
2974
|
-
///
|
|
3132
|
+
/// (`ClosureSigKey`), independent of those types.
|
|
2975
|
-
struct ClosureWalker<'a, '
|
|
3133
|
+
struct ClosureWalker<'a, 't> {
|
|
2976
|
-
env: TypeEnv,
|
|
2977
|
-
|
|
3134
|
+
types: &'t HashMap<usize, PlumType>,
|
|
2978
3135
|
fn_decls: &'a HashMap<String, &'a ast::Fn>,
|
|
2979
3136
|
found: Vec<RawClosure<'a>>,
|
|
2980
3137
|
/// Local names bound in the function currently being walked (params, assign
|
|
@@ -2989,7 +3146,7 @@ struct ClosureWalker<'a, 'c> {
|
|
|
2989
3146
|
named_fn_refs: HashSet<String>,
|
|
2990
3147
|
}
|
|
2991
3148
|
|
|
2992
|
-
impl<'a, '
|
|
3149
|
+
impl<'a, 't> ClosureWalker<'a, 't> {
|
|
2993
3150
|
fn walkBlock(&mut self, block: &'a ast::Block) {
|
|
2994
3151
|
for s in &block.stmts {
|
|
2995
3152
|
self.walkStmt(s);
|
|
@@ -3000,27 +3157,24 @@ impl<'a, 'c> ClosureWalker<'a, 'c> {
|
|
|
3000
3157
|
match stmt {
|
|
3001
3158
|
ast::Stmt::Assign(a) => {
|
|
3002
3159
|
for (target, value) in a.targets.iter().zip(a.values.iter()) {
|
|
3003
|
-
self.walkExpr(value
|
|
3160
|
+
self.walkExpr(value);
|
|
3004
3161
|
match target {
|
|
3005
3162
|
ast::AssignTarget::Var(name) => {
|
|
3006
|
-
let ty = plum_checker::inferExpr(value, &self.env, &self.cctx)
|
|
3007
|
-
.unwrap_or(PlumType::TInt);
|
|
3008
|
-
self.env.insert(name.clone(), TypeScheme::mono(ty));
|
|
3009
3163
|
self.locals.insert(name.clone());
|
|
3010
3164
|
}
|
|
3011
3165
|
ast::AssignTarget::Field(object, _) => {
|
|
3012
|
-
self.walkExpr(object
|
|
3166
|
+
self.walkExpr(object);
|
|
3013
3167
|
}
|
|
3014
3168
|
}
|
|
3015
3169
|
}
|
|
3016
3170
|
}
|
|
3017
|
-
ast::Stmt::Return(Some(e)) => self.walkExpr(e
|
|
3171
|
+
ast::Stmt::Return(Some(e)) => self.walkExpr(e),
|
|
3018
3172
|
ast::Stmt::Return(None) => {}
|
|
3019
3173
|
ast::Stmt::If(i) => {
|
|
3020
|
-
self.walkExpr(&i.condition
|
|
3174
|
+
self.walkExpr(&i.condition);
|
|
3021
3175
|
self.walkBlock(&i.body);
|
|
3022
3176
|
for ei in &i.else_ifs {
|
|
3023
|
-
self.walkExpr(&ei.condition
|
|
3177
|
+
self.walkExpr(&ei.condition);
|
|
3024
3178
|
self.walkBlock(&ei.body);
|
|
3025
3179
|
}
|
|
3026
3180
|
if let Some(e) = &i.else_ {
|
|
@@ -3028,125 +3182,80 @@ impl<'a, 'c> ClosureWalker<'a, 'c> {
|
|
|
3028
3182
|
}
|
|
3029
3183
|
}
|
|
3030
3184
|
ast::Stmt::While(w) => {
|
|
3031
|
-
self.walkExpr(&w.condition
|
|
3185
|
+
self.walkExpr(&w.condition);
|
|
3032
3186
|
self.walkBlock(&w.body);
|
|
3033
3187
|
}
|
|
3034
3188
|
ast::Stmt::For(f) => {
|
|
3035
|
-
self.walkExpr(&f.iter
|
|
3189
|
+
self.walkExpr(&f.iter);
|
|
3036
|
-
let elem_ty = match plum_checker::inferExpr(&f.iter, &self.env, &self.cctx) {
|
|
3037
|
-
Ok(PlumType::TVariadic(elem)) => *elem,
|
|
3038
|
-
_ => PlumType::TInt,
|
|
3039
|
-
};
|
|
3040
3190
|
for v in &f.vars {
|
|
3041
|
-
self.env
|
|
3042
|
-
.insert(v.clone(), TypeScheme::mono(elem_ty.clone()));
|
|
3043
3191
|
self.locals.insert(v.clone());
|
|
3044
3192
|
}
|
|
3045
3193
|
self.walkBlock(&f.body);
|
|
3046
3194
|
}
|
|
3047
|
-
ast::Stmt::Expr(e, ..) => self.walkExpr(e
|
|
3195
|
+
ast::Stmt::Expr(e, ..) => self.walkExpr(e),
|
|
3048
|
-
ast::Stmt::Assert(c) => self.walkExpr(&c.cond
|
|
3196
|
+
ast::Stmt::Assert(c) => self.walkExpr(&c.cond),
|
|
3049
3197
|
ast::Stmt::Match(m) => {
|
|
3050
3198
|
for s in &m.subjects {
|
|
3051
|
-
self.walkExpr(s
|
|
3199
|
+
self.walkExpr(s);
|
|
3052
3200
|
}
|
|
3053
3201
|
for case in &m.cases {
|
|
3202
|
+
if let Some(g) = &case.guard {
|
|
3203
|
+
self.walkExpr(g);
|
|
3204
|
+
}
|
|
3054
3205
|
self.walkBlock(&case.body);
|
|
3055
3206
|
}
|
|
3056
3207
|
}
|
|
3057
3208
|
ast::Stmt::Break | ast::Stmt::Continue | ast::Stmt::Todo => {}
|
|
3058
|
-
ast::Stmt::Crash(e) => self.walkExpr(e
|
|
3209
|
+
ast::Stmt::Crash(e) => self.walkExpr(e),
|
|
3059
3210
|
}
|
|
3060
3211
|
}
|
|
3061
3212
|
|
|
3062
|
-
/// `expected_fn` carries the declared `fn(params) -> ret` type when this expression is
|
|
3063
|
-
/// a call argument in a `fn`-typed parameter position, giving a closure literal its
|
|
3064
|
-
/// concrete signature.
|
|
3065
|
-
fn walkExpr(&mut self, expr: &'a ast::Expr
|
|
3213
|
+
fn walkExpr(&mut self, expr: &'a ast::Expr) {
|
|
3066
3214
|
match expr {
|
|
3067
|
-
ast::Expr::Closure(cl) => self.recordClosure(
|
|
3215
|
+
ast::Expr::Closure(cl) => self.recordClosure(expr, cl),
|
|
3068
3216
|
ast::Expr::Binary(b) => {
|
|
3069
|
-
self.walkExpr(&b.left
|
|
3217
|
+
self.walkExpr(&b.left);
|
|
3070
|
-
self.walkExpr(&b.right
|
|
3218
|
+
self.walkExpr(&b.right);
|
|
3071
3219
|
}
|
|
3072
3220
|
ast::Expr::Bool(b) => {
|
|
3073
|
-
self.walkExpr(&b.left
|
|
3221
|
+
self.walkExpr(&b.left);
|
|
3074
|
-
self.walkExpr(&b.right
|
|
3222
|
+
self.walkExpr(&b.right);
|
|
3075
3223
|
}
|
|
3076
3224
|
ast::Expr::Compare(c) => {
|
|
3077
|
-
self.walkExpr(&c.left
|
|
3225
|
+
self.walkExpr(&c.left);
|
|
3078
|
-
self.walkExpr(&c.right
|
|
3226
|
+
self.walkExpr(&c.right);
|
|
3079
3227
|
}
|
|
3080
|
-
ast::Expr::Not(inner) => self.walkExpr(inner
|
|
3228
|
+
ast::Expr::Not(inner) => self.walkExpr(inner),
|
|
3081
|
-
ast::Expr::Try(inner) => self.walkExpr(inner
|
|
3229
|
+
ast::Expr::Try(inner) => self.walkExpr(inner),
|
|
3082
|
-
ast::Expr::Unary(u) => self.walkExpr(&u.operand
|
|
3230
|
+
ast::Expr::Unary(u) => self.walkExpr(&u.operand),
|
|
3083
|
-
ast::Expr::Paren(inner) => self.walkExpr(inner
|
|
3231
|
+
ast::Expr::Paren(inner) => self.walkExpr(inner),
|
|
3084
3232
|
ast::Expr::Ternary(t) => {
|
|
3085
|
-
self.walkExpr(&t.condition
|
|
3233
|
+
self.walkExpr(&t.condition);
|
|
3086
|
-
self.walkExpr(&t.then
|
|
3234
|
+
self.walkExpr(&t.then);
|
|
3087
|
-
self.walkExpr(&t.else_
|
|
3235
|
+
self.walkExpr(&t.else_);
|
|
3088
3236
|
}
|
|
3089
3237
|
ast::Expr::Elvis(e) => {
|
|
3090
|
-
self.walkExpr(&e.left
|
|
3238
|
+
self.walkExpr(&e.left);
|
|
3091
|
-
self.walkExpr(&e.right
|
|
3239
|
+
self.walkExpr(&e.right);
|
|
3092
3240
|
}
|
|
3093
3241
|
ast::Expr::FnCall(call) => {
|
|
3094
|
-
let callee = self.fn_decls.get(&call.name).copied();
|
|
3095
|
-
for (i, arg) in call.args.iter().enumerate() {
|
|
3096
|
-
|
|
3242
|
+
for arg in &call.args {
|
|
3097
|
-
let expected = callee
|
|
3098
|
-
.and_then(|f| f.params.get(i))
|
|
3099
|
-
.and_then(|p| match &p.ty {
|
|
3100
|
-
ast::ParamType::Fn(params, ret) => Some(PlumType::TFun(
|
|
3101
|
-
params.iter().map(plum_checker::plumTypeFromAst).collect(),
|
|
3102
|
-
Box::new(
|
|
3103
|
-
ret.as_deref()
|
|
3104
|
-
.map(plum_checker::plumTypeFromAst)
|
|
3105
|
-
.unwrap_or(PlumType::TUnit),
|
|
3106
|
-
),
|
|
3107
|
-
)),
|
|
3108
|
-
_ => None,
|
|
3109
|
-
});
|
|
3110
|
-
self.walkExpr(
|
|
3243
|
+
self.walkExpr(argExprOf(arg));
|
|
3111
3244
|
}
|
|
3112
3245
|
}
|
|
3113
3246
|
ast::Expr::ClassCall(call) => {
|
|
3114
3247
|
for fa in &call.fields {
|
|
3115
|
-
self.walkExpr(&fa.value
|
|
3248
|
+
self.walkExpr(&fa.value);
|
|
3249
|
+
}
|
|
3250
|
+
if let Some(sp) = &call.spread {
|
|
3251
|
+
self.walkExpr(sp);
|
|
3116
3252
|
}
|
|
3117
3253
|
}
|
|
3118
3254
|
ast::Expr::Attribute(a) => {
|
|
3119
|
-
self.walkExpr(&a.object
|
|
3255
|
+
self.walkExpr(&a.object);
|
|
3120
3256
|
if let ast::AttrKind::Method(call) = &a.attr {
|
|
3121
|
-
// Unlike the `FnCall` arm above (which can look a callee up
|
|
3122
|
-
// directly by bare name in `fn_decls`), a method's signature
|
|
3123
|
-
// must be resolved via its RECEIVER's type first — `ctx.methods`
|
|
3124
|
-
// is keyed `(class_name, method_name)`, and two unrelated
|
|
3125
|
-
// classes' methods sharing a bare name (`List.each`/`Map.each`)
|
|
3126
|
-
// would otherwise be ambiguous. Without this lookup, a closure
|
|
3127
|
-
// argument to ANY method fell back to guessing its param types
|
|
3128
|
-
// from how they're used in its OWN body (see `recordClosure`'s
|
|
3129
|
-
// fallback arm) — which silently defaults a non-numeric param
|
|
3130
|
-
// (e.g. `reduce`'s `Str` accumulator) to `Int`, miscompiling the
|
|
3131
|
-
// closure with the wrong signature entirely.
|
|
3132
|
-
let method_sig = plum_checker::inferExpr(&a.object, &self.env, &self.cctx)
|
|
3133
|
-
.ok()
|
|
3134
|
-
.and_then(|obj_ty| plum_checker::methodReceiverName(&obj_ty))
|
|
3135
|
-
.and_then(|class_name| {
|
|
3136
|
-
self.cctx
|
|
3137
|
-
.methods
|
|
3138
|
-
.get(&(class_name, call.name.clone()))
|
|
3139
|
-
.cloned()
|
|
3140
|
-
});
|
|
3141
|
-
for
|
|
3257
|
+
for arg in &call.args {
|
|
3142
|
-
let expected = match &method_sig {
|
|
3143
|
-
Some(PlumType::TFun(params, _)) => params
|
|
3144
|
-
.get(i)
|
|
3145
|
-
.cloned()
|
|
3146
|
-
.filter(|p| matches!(p, PlumType::TFun(_, _))),
|
|
3147
|
-
_ => None,
|
|
3148
|
-
};
|
|
3149
|
-
self.walkExpr(argExprOf(arg)
|
|
3258
|
+
self.walkExpr(argExprOf(arg));
|
|
3150
3259
|
}
|
|
3151
3260
|
}
|
|
3152
3261
|
}
|
|
@@ -3165,14 +3274,10 @@ impl<'a, 'c> ClosureWalker<'a, 'c> {
|
|
|
3165
3274
|
}
|
|
3166
3275
|
ast::Expr::String(s) => {
|
|
3167
3276
|
// A `"{...}"` interpolation can embed an arbitrary expression,
|
|
3168
|
-
// including a closure literal passed straight to a method call
|
|
3277
|
+
// including a closure literal passed straight to a method call.
|
|
3169
|
-
// (`"{l.any(|x| x % 2 == 0)}"`) — walk into it like any other
|
|
3170
|
-
// sub-expression, or such a closure is never discovered by this
|
|
3171
|
-
// pre-pass and codegen later traps with "closure literal was not
|
|
3172
|
-
// discovered by the discovery pre-pass".
|
|
3173
3278
|
for part in &s.parts {
|
|
3174
3279
|
if let ast::StringPart::Interp(e) = part {
|
|
3175
|
-
self.walkExpr(e
|
|
3280
|
+
self.walkExpr(e);
|
|
3176
3281
|
}
|
|
3177
3282
|
}
|
|
3178
3283
|
}
|
|
@@ -3181,301 +3286,40 @@ impl<'a, 'c> ClosureWalker<'a, 'c> {
|
|
|
3181
3286
|
}
|
|
3182
3287
|
}
|
|
3183
3288
|
|
|
3184
|
-
fn recordClosure(&mut self,
|
|
3289
|
+
fn recordClosure(&mut self, expr: &'a ast::Expr, cl: &'a ast::Closure) {
|
|
3185
|
-
let (
|
|
3290
|
+
let (param_ptypes, ret_ptype) = match self.types.get(&(expr as *const ast::Expr as usize)) {
|
|
3186
|
-
Some(PlumType::TFun(
|
|
3291
|
+
Some(PlumType::TFun(ps, r)) if ps.len() == cl.params.len() => (ps.clone(), (**r).clone()),
|
|
3187
|
-
let param_vts = params.iter().map(plumTypeToValtype).collect();
|
|
3188
|
-
let ret_vt = match *ret {
|
|
3189
|
-
PlumType::TUnit => None,
|
|
3190
|
-
other => Some(plumTypeToValtype(&other)),
|
|
3191
|
-
};
|
|
3192
|
-
(param_vts, params, ret_vt)
|
|
3193
|
-
}
|
|
3194
|
-
_ => {
|
|
3195
|
-
|
|
3292
|
+
// No usable checker type (never expected for checked source): treat
|
|
3196
|
-
// first, then called/passed on later): the checker's own closure
|
|
3197
|
-
// inference (`plum_checker::inferExpr` on `Expr::Closure`) gives
|
|
3198
|
-
|
|
3293
|
+
// every param as an Int and the result as Unit.
|
|
3199
|
-
// how the param is used in the body — `unify` is a no-op for any
|
|
3200
|
-
// `TVar` — so a param that's genuinely Float or a class/pointer
|
|
3201
|
-
// silently comes back as an unresolved `TVar`, which
|
|
3202
|
-
// `plumTypeToValtype` then defaults to `Int`. If the closure is
|
|
3203
|
-
// later called at its real (non-Int) type, the wasm function actually
|
|
3204
|
-
// compiled for its body (built from this wrong, Int-assumed signature)
|
|
3205
|
-
// won't match the `call_indirect` type the real call site expects —
|
|
3206
|
-
// a mismatch `call_indirect` only traps on at runtime, not at compile
|
|
3207
|
-
// or validation time.
|
|
3208
|
-
//
|
|
3209
|
-
// Fix: resolve each param's type from a direct usage in the body
|
|
3210
|
-
// first (see `resolveClosureParamTypesFromUsage`) — e.g. `cb = |v|
|
|
3211
|
-
// x + v` with a captured `Float` `x` resolves `v` to `Float` from that
|
|
3212
|
-
// `Binary` op — then infer the return type against an env where the
|
|
3213
|
-
// params are already bound concretely (bypassing the checker's
|
|
3214
|
-
// Closure-inference arm entirely, since it always re-binds params to
|
|
3215
|
-
// fresh TVars regardless of what's already in the env passed to it).
|
|
3216
|
-
let resolved_params = resolveClosureParamTypesFromUsage(cl, &self.env, &self.cctx);
|
|
3217
|
-
let param_ptypes: Vec<PlumType> = cl
|
|
3218
|
-
.params
|
|
3219
|
-
.iter()
|
|
3220
|
-
|
|
3294
|
+
_ => (vec![PlumType::TInt; cl.params.len()], PlumType::TUnit),
|
|
3221
|
-
.collect();
|
|
3222
|
-
let param_vts: Vec<ValType> = param_ptypes.iter().map(plumTypeToValtype).collect();
|
|
3223
|
-
|
|
3224
|
-
let mut body_env = self.env.clone();
|
|
3225
|
-
for (p, ty) in cl.params.iter().zip(param_ptypes.iter()) {
|
|
3226
|
-
body_env.insert(p.clone(), TypeScheme::mono(ty.clone()));
|
|
3227
|
-
}
|
|
3228
|
-
let ret_ty = match cl.body.stmts.last() {
|
|
3229
|
-
Some(ast::Stmt::Expr(e, ..)) => {
|
|
3230
|
-
plum_checker::inferExpr(e, &body_env, &self.cctx).ok()
|
|
3231
|
-
}
|
|
3232
|
-
Some(ast::Stmt::Return(Some(e))) => {
|
|
3233
|
-
plum_checker::inferExpr(e, &body_env, &self.cctx).ok()
|
|
3234
|
-
}
|
|
3235
|
-
_ => Some(PlumType::TUnit),
|
|
3236
|
-
};
|
|
3237
|
-
let ret_vt = match ret_ty {
|
|
3238
|
-
Some(PlumType::TUnit) => None,
|
|
3239
|
-
Some(PlumType::TVar(_)) | None => Some(ValType::I64), // unresolved: preserve prior Int-default behavior
|
|
3240
|
-
Some(other) => Some(plumTypeToValtype(&other)),
|
|
3241
|
-
};
|
|
3242
|
-
(param_vts, param_ptypes, ret_vt)
|
|
3243
|
-
}
|
|
3244
3295
|
};
|
|
3245
|
-
let free_vars = collectFreeVars(cl,
|
|
3296
|
+
let free_vars = collectFreeVars(cl, self.types, self.fn_decls);
|
|
3246
3297
|
self.found.push(RawClosure {
|
|
3247
3298
|
ptr: cl as *const ast::Closure as usize,
|
|
3248
3299
|
closure: cl,
|
|
3249
|
-
|
|
3300
|
+
param_ptypes,
|
|
3250
|
-
param_ptypes: param_ptypes.clone(),
|
|
3251
|
-
|
|
3301
|
+
ret_ptype,
|
|
3252
3302
|
free_vars,
|
|
3253
3303
|
});
|
|
3254
3304
|
|
|
3255
3305
|
// Recurse into this closure's own body to find any closure literals nested
|
|
3256
|
-
// inside it (`|v| |w| v + w`, or a closure literal used inside a `match`/`if`
|
|
3257
|
-
// within this one's body). Each nested closure gets registered exactly like a
|
|
3258
|
-
//
|
|
3306
|
+
// inside it, seeing this closure's own params as locals in scope.
|
|
3259
|
-
// is also what makes its free-variable analysis correctly capture a name from
|
|
3260
|
-
// *this* closure's scope (rather than silently missing it): once discovery
|
|
3261
|
-
// finishes, `ctx.closures` holds every closure at every depth before any body
|
|
3262
|
-
// is compiled, so the single fixed "compile each registered closure" pass in
|
|
3263
|
-
// `compileSource` already handles arbitrary nesting with no further changes.
|
|
3264
|
-
let saved_env = self.env.clone();
|
|
3265
3307
|
let saved_locals = self.locals.clone();
|
|
3266
|
-
for
|
|
3308
|
+
for p in &cl.params {
|
|
3267
|
-
self.env.insert(p.clone(), TypeScheme::mono(ty.clone()));
|
|
3268
3309
|
self.locals.insert(p.clone());
|
|
3269
3310
|
}
|
|
3270
3311
|
self.walkBlock(&cl.body);
|
|
3271
|
-
self.env = saved_env;
|
|
3272
3312
|
self.locals = saved_locals;
|
|
3273
3313
|
}
|
|
3274
3314
|
}
|
|
3275
3315
|
|
|
3276
|
-
fn argExprOf(arg: &ast::Arg) -> &ast::Expr {
|
|
3277
|
-
match arg {
|
|
3278
|
-
ast::Arg::Positional(e) => e,
|
|
3279
|
-
ast::Arg::Keyword { value, .. } => value,
|
|
3280
|
-
ast::Arg::Pair { value, .. } => value,
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
/// Resolves as many of a closure's param types as possible from how they're actually
|
|
3285
|
-
/// used in its body — e.g. `|v| x + v` with a captured `Float` `x` resolves `v` to
|
|
3286
|
-
/// `Float` from that `Binary` op, or `|v| helper(v)` where `helper`'s declared param
|
|
3287
|
-
/// type is concrete resolves `v` to that. A param never used in a way that pins down
|
|
3288
|
-
/// a concrete type simply doesn't appear in the returned map (callers fall back to
|
|
3289
|
-
/// `Int`, matching the prior default). This is intentionally a shallow, best-effort
|
|
3290
|
-
/// scan — not full unification — scoped to fixing the specific `call_indirect`
|
|
3291
|
-
/// signature-mismatch gap this exists for, not replacing the checker's inference.
|
|
3292
|
-
fn resolveClosureParamTypesFromUsage(
|
|
3293
|
-
cl: &ast::Closure,
|
|
3294
|
-
env: &TypeEnv,
|
|
3295
|
-
cctx: &plum_checker::CheckCtx,
|
|
3296
|
-
) -> HashMap<String, PlumType> {
|
|
3297
|
-
let params: std::collections::HashSet<String> = cl.params.iter().cloned().collect();
|
|
3298
|
-
let mut resolved: HashMap<String, PlumType> = HashMap::new();
|
|
3299
|
-
for stmt in &cl.body.stmts {
|
|
3300
|
-
scanStmtForParamTypes(stmt, ¶ms, env, cctx, &mut resolved);
|
|
3301
|
-
}
|
|
3302
|
-
resolved
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
fn scanStmtForParamTypes(
|
|
3306
|
-
stmt: &ast::Stmt,
|
|
3307
|
-
params: &std::collections::HashSet<String>,
|
|
3308
|
-
env: &TypeEnv,
|
|
3309
|
-
cctx: &plum_checker::CheckCtx,
|
|
3310
|
-
resolved: &mut HashMap<String, PlumType>,
|
|
3311
|
-
) {
|
|
3312
|
-
match stmt {
|
|
3313
|
-
ast::Stmt::Assign(a) => {
|
|
3314
|
-
for v in &a.values {
|
|
3315
|
-
scanExprForParamTypes(v, params, env, cctx, resolved);
|
|
3316
|
-
}
|
|
3317
|
-
}
|
|
3318
|
-
ast::Stmt::Return(Some(e)) | ast::Stmt::Expr(e, ..) => {
|
|
3319
|
-
scanExprForParamTypes(e, params, env, cctx, resolved);
|
|
3320
|
-
}
|
|
3321
|
-
ast::Stmt::Assert(c) => {
|
|
3322
|
-
scanExprForParamTypes(&c.cond, params, env, cctx, resolved);
|
|
3323
|
-
}
|
|
3324
|
-
ast::Stmt::If(i) => {
|
|
3325
|
-
scanExprForParamTypes(&i.condition, params, env, cctx, resolved);
|
|
3326
|
-
for s in &i.body.stmts {
|
|
3327
|
-
scanStmtForParamTypes(s, params, env, cctx, resolved);
|
|
3328
|
-
}
|
|
3329
|
-
for ei in &i.else_ifs {
|
|
3330
|
-
scanExprForParamTypes(&ei.condition, params, env, cctx, resolved);
|
|
3331
|
-
for s in &ei.body.stmts {
|
|
3332
|
-
scanStmtForParamTypes(s, params, env, cctx, resolved);
|
|
3333
|
-
}
|
|
3334
|
-
}
|
|
3335
|
-
if let Some(e) = &i.else_ {
|
|
3336
|
-
for s in &e.stmts {
|
|
3337
|
-
scanStmtForParamTypes(s, params, env, cctx, resolved);
|
|
3338
|
-
}
|
|
3339
|
-
}
|
|
3340
|
-
}
|
|
3341
|
-
ast::Stmt::While(w) => {
|
|
3342
|
-
scanExprForParamTypes(&w.condition, params, env, cctx, resolved);
|
|
3343
|
-
for s in &w.body.stmts {
|
|
3344
|
-
scanStmtForParamTypes(s, params, env, cctx, resolved);
|
|
3345
|
-
}
|
|
3346
|
-
}
|
|
3347
|
-
ast::Stmt::For(f) => {
|
|
3348
|
-
scanExprForParamTypes(&f.iter, params, env, cctx, resolved);
|
|
3349
|
-
for s in &f.body.stmts {
|
|
3350
|
-
scanStmtForParamTypes(s, params, env, cctx, resolved);
|
|
3351
|
-
}
|
|
3352
|
-
}
|
|
3353
|
-
_ => {}
|
|
3354
|
-
}
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
fn scanExprForParamTypes(
|
|
3358
|
-
expr: &ast::Expr,
|
|
3359
|
-
params: &std::collections::HashSet<String>,
|
|
3360
|
-
env: &TypeEnv,
|
|
3361
|
-
cctx: &plum_checker::CheckCtx,
|
|
3362
|
-
resolved: &mut HashMap<String, PlumType>,
|
|
3363
|
-
) {
|
|
3364
|
-
match expr {
|
|
3365
|
-
ast::Expr::Binary(b) => {
|
|
3366
|
-
tryResolveParamFromPair(&b.left, &b.right, params, env, cctx, resolved);
|
|
3367
|
-
scanExprForParamTypes(&b.left, params, env, cctx, resolved);
|
|
3368
|
-
scanExprForParamTypes(&b.right, params, env, cctx, resolved);
|
|
3369
|
-
}
|
|
3370
|
-
ast::Expr::Compare(c) => {
|
|
3371
|
-
tryResolveParamFromPair(&c.left, &c.right, params, env, cctx, resolved);
|
|
3372
|
-
scanExprForParamTypes(&c.left, params, env, cctx, resolved);
|
|
3373
|
-
scanExprForParamTypes(&c.right, params, env, cctx, resolved);
|
|
3374
|
-
}
|
|
3375
|
-
ast::Expr::Bool(b) => {
|
|
3376
|
-
scanExprForParamTypes(&b.left, params, env, cctx, resolved);
|
|
3377
|
-
scanExprForParamTypes(&b.right, params, env, cctx, resolved);
|
|
3378
|
-
}
|
|
3379
|
-
ast::Expr::Not(inner) => scanExprForParamTypes(inner, params, env, cctx, resolved),
|
|
3380
|
-
ast::Expr::Unary(u) => scanExprForParamTypes(&u.operand, params, env, cctx, resolved),
|
|
3381
|
-
ast::Expr::Paren(inner) => scanExprForParamTypes(inner, params, env, cctx, resolved),
|
|
3382
|
-
ast::Expr::Ternary(t) => {
|
|
3383
|
-
scanExprForParamTypes(&t.condition, params, env, cctx, resolved);
|
|
3384
|
-
scanExprForParamTypes(&t.then, params, env, cctx, resolved);
|
|
3385
|
-
scanExprForParamTypes(&t.else_, params, env, cctx, resolved);
|
|
3386
|
-
}
|
|
3387
|
-
ast::Expr::Elvis(e) => {
|
|
3388
|
-
scanExprForParamTypes(&e.left, params, env, cctx, resolved);
|
|
3389
|
-
scanExprForParamTypes(&e.right, params, env, cctx, resolved);
|
|
3390
|
-
}
|
|
3391
|
-
ast::Expr::FnCall(call) => {
|
|
3392
|
-
if let Ok(PlumType::TFun(param_types, _)) = plum_checker::lookup(env, &call.name) {
|
|
3393
|
-
for (arg, expected) in call.args.iter().zip(param_types.iter()) {
|
|
3394
|
-
let arg_expr = argExprOf(arg);
|
|
3395
|
-
if let ast::Expr::Var(n) = arg_expr {
|
|
3396
|
-
if params.contains(n)
|
|
3397
|
-
&& !resolved.contains_key(n)
|
|
3398
|
-
&& !matches!(expected, PlumType::TVar(_))
|
|
3399
|
-
{
|
|
3400
|
-
resolved.insert(n.clone(), expected.clone());
|
|
3401
|
-
}
|
|
3402
|
-
}
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
for arg in &call.args {
|
|
3406
|
-
scanExprForParamTypes(argExprOf(arg), params, env, cctx, resolved);
|
|
3407
|
-
}
|
|
3408
|
-
}
|
|
3409
|
-
ast::Expr::ClassCall(call) => {
|
|
3410
|
-
for fa in &call.fields {
|
|
3411
|
-
scanExprForParamTypes(&fa.value, params, env, cctx, resolved);
|
|
3412
|
-
}
|
|
3413
|
-
}
|
|
3414
|
-
ast::Expr::Attribute(a) => {
|
|
3415
|
-
// `c.field` on a bare, unresolved param implies `c`'s type is whichever
|
|
3416
|
-
// enum declares a variant with that field name — ambiguous if more than
|
|
3417
|
-
// one variant has a field by that name, but resolvable in the common case.
|
|
3418
|
-
if let ast::AttrKind::Field(field_name) = &a.attr {
|
|
3419
|
-
if let ast::Expr::Var(n) = &a.object {
|
|
3420
|
-
if params.contains(n) && !resolved.contains_key(n) {
|
|
3421
|
-
let mut matches = cctx.enum_variants.values().filter(|info| {
|
|
3422
|
-
info.field_names.iter().any(|fname| fname == field_name)
|
|
3423
|
-
});
|
|
3424
|
-
if let (Some(info), None) = (matches.next(), matches.next()) {
|
|
3425
|
-
resolved.insert(n.clone(), PlumType::TNamed(info.enum_name.clone()));
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
scanExprForParamTypes(&a.object, params, env, cctx, resolved);
|
|
3431
|
-
if let ast::AttrKind::Method(call) = &a.attr {
|
|
3432
|
-
for arg in &call.args {
|
|
3433
|
-
scanExprForParamTypes(argExprOf(arg), params, env, cctx, resolved);
|
|
3434
|
-
}
|
|
3435
|
-
}
|
|
3436
|
-
}
|
|
3437
|
-
_ => {}
|
|
3438
|
-
}
|
|
3439
|
-
}
|
|
3440
|
-
|
|
3441
|
-
/// If either side of a `Binary`/`Compare` operand pair is a bare reference to an
|
|
3442
|
-
/// unresolved param and the *other* side has a concrete (non-`TVar`) inferred type,
|
|
3443
|
-
/// binds the param to that type.
|
|
3444
|
-
fn tryResolveParamFromPair(
|
|
3445
|
-
left: &ast::Expr,
|
|
3446
|
-
right: &ast::Expr,
|
|
3447
|
-
params: &std::collections::HashSet<String>,
|
|
3448
|
-
env: &TypeEnv,
|
|
3449
|
-
cctx: &plum_checker::CheckCtx,
|
|
3450
|
-
resolved: &mut HashMap<String, PlumType>,
|
|
3451
|
-
) {
|
|
3452
|
-
if let ast::Expr::Var(n) = left {
|
|
3453
|
-
if params.contains(n) && !resolved.contains_key(n) {
|
|
3454
|
-
if let Ok(ty) = plum_checker::inferExpr(right, env, cctx) {
|
|
3455
|
-
if !matches!(ty, PlumType::TVar(_)) {
|
|
3456
|
-
resolved.insert(n.clone(), ty);
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
}
|
|
3460
|
-
}
|
|
3461
|
-
if let ast::Expr::Var(n) = right {
|
|
3462
|
-
if params.contains(n) && !resolved.contains_key(n) {
|
|
3463
|
-
if let Ok(ty) = plum_checker::inferExpr(left, env, cctx) {
|
|
3464
|
-
if !matches!(ty, PlumType::TVar(_)) {
|
|
3465
|
-
resolved.insert(n.clone(), ty);
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
|
|
3472
3316
|
/// Determines a closure's captured free variables: every `Var` referenced in its body
|
|
3473
3317
|
/// that is neither one of the closure's own params nor assigned locally inside the body,
|
|
3474
3318
|
/// in first-appearance order. Each free variable's type is looked up in the *enclosing*
|
|
3475
3319
|
/// scope's type env.
|
|
3476
3320
|
fn collectFreeVars(
|
|
3477
3321
|
cl: &ast::Closure,
|
|
3478
|
-
env: &
|
|
3322
|
+
env: &HashMap<usize, PlumType>,
|
|
3479
3323
|
fn_decls: &HashMap<String, &ast::Fn>,
|
|
3480
3324
|
) -> Vec<(String, PlumType)> {
|
|
3481
3325
|
let mut bound: std::collections::HashSet<String> = cl.params.iter().cloned().collect();
|
|
@@ -3545,7 +3389,7 @@ fn fvCollectRefsBlock(
|
|
|
3545
3389
|
bound: &std::collections::HashSet<String>,
|
|
3546
3390
|
seen: &mut std::collections::HashSet<String>,
|
|
3547
3391
|
free: &mut Vec<(String, PlumType)>,
|
|
3548
|
-
env: &
|
|
3392
|
+
env: &HashMap<usize, PlumType>,
|
|
3549
3393
|
fn_decls: &HashMap<String, &ast::Fn>,
|
|
3550
3394
|
) {
|
|
3551
3395
|
for s in &block.stmts {
|
|
@@ -3606,7 +3450,7 @@ fn fvCollectRefsExpr(
|
|
|
3606
3450
|
bound: &std::collections::HashSet<String>,
|
|
3607
3451
|
seen: &mut std::collections::HashSet<String>,
|
|
3608
3452
|
free: &mut Vec<(String, PlumType)>,
|
|
3609
|
-
env: &
|
|
3453
|
+
env: &HashMap<usize, PlumType>,
|
|
3610
3454
|
fn_decls: &HashMap<String, &ast::Fn>,
|
|
3611
3455
|
) {
|
|
3612
3456
|
match expr {
|
|
@@ -3616,10 +3460,22 @@ fn fvCollectRefsExpr(
|
|
|
3616
3460
|
// reference (see `named_fn_values`), not loaded from an enclosing local.
|
|
3617
3461
|
let is_named_fn_ref = fn_decls.get(name).is_some_and(|f| f.type_param.is_none());
|
|
3618
3462
|
if !is_named_fn_ref && !bound.contains(name) && seen.insert(name.clone()) {
|
|
3463
|
+
let ty = env
|
|
3464
|
+
.get(&(expr as *const ast::Expr as usize))
|
|
3465
|
+
.cloned()
|
|
3619
|
-
|
|
3466
|
+
.unwrap_or(PlumType::TInt);
|
|
3620
3467
|
free.push((name.clone(), ty));
|
|
3621
3468
|
}
|
|
3622
3469
|
}
|
|
3470
|
+
ast::Expr::Self_ => {
|
|
3471
|
+
if !bound.contains("self") && seen.insert("self".to_string()) {
|
|
3472
|
+
let ty = env
|
|
3473
|
+
.get(&(expr as *const ast::Expr as usize))
|
|
3474
|
+
.cloned()
|
|
3475
|
+
.unwrap_or(PlumType::TInt);
|
|
3476
|
+
free.push(("self".to_string(), ty));
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3623
3479
|
ast::Expr::Binary(b) => {
|
|
3624
3480
|
fvCollectRefsExpr(&b.left, bound, seen, free, env, fn_decls);
|
|
3625
3481
|
fvCollectRefsExpr(&b.right, bound, seen, free, env, fn_decls);
|
|
@@ -3654,9 +3510,19 @@ fn fvCollectRefsExpr(
|
|
|
3654
3510
|
for fa in &call.fields {
|
|
3655
3511
|
fvCollectRefsExpr(&fa.value, bound, seen, free, env, fn_decls);
|
|
3656
3512
|
}
|
|
3513
|
+
if let Some(sp) = &call.spread {
|
|
3514
|
+
fvCollectRefsExpr(sp, bound, seen, free, env, fn_decls);
|
|
3515
|
+
}
|
|
3657
3516
|
}
|
|
3517
|
+
ast::Expr::String(sx) => {
|
|
3518
|
+
for part in &sx.parts {
|
|
3519
|
+
if let ast::StringPart::Interp(e) = part {
|
|
3520
|
+
fvCollectRefsExpr(e, bound, seen, free, env, fn_decls);
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3658
|
-
ast::Expr::Attribute(a) => {
|
|
3524
|
+
ast::Expr::Attribute(a) => {
|
|
3659
|
-
fvCollectRefsExpr(&a.object, bound, seen, free, env, fn_decls);
|
|
3525
|
+
fvCollectRefsExpr(&a.object, bound, seen, free, env, fn_decls);
|
|
3660
3526
|
if let ast::AttrKind::Method(call) = &a.attr {
|
|
3661
3527
|
for arg in &call.args {
|
|
3662
3528
|
fvCollectRefsExpr(argExprOf(arg), bound, seen, free, env, fn_decls);
|
|
@@ -3690,8 +3556,9 @@ fn fvCollectRefsExpr(
|
|
|
3690
3556
|
/// its inferred type, (2) how many `ClassCall` scratch temporaries it needs, and (3)
|
|
3691
3557
|
/// the subject type for every `match` statement (for its own scratch temporary).
|
|
3692
3558
|
struct Collector<'a> {
|
|
3693
|
-
|
|
3559
|
+
/// The checker's instantiated type of every expression.
|
|
3694
|
-
|
|
3560
|
+
types: &'a HashMap<usize, PlumType>,
|
|
3561
|
+
enum_variants: &'a EnumVariants,
|
|
3695
3562
|
named: Vec<(String, PlumType)>,
|
|
3696
3563
|
named_set: std::collections::HashSet<String>,
|
|
3697
3564
|
/// One scratch-local type per subject (usually one, more for `match a, b, ...`).
|
|
@@ -3723,9 +3590,16 @@ struct Collector<'a> {
|
|
|
3723
3590
|
impl<'a> Collector<'a> {
|
|
3724
3591
|
fn bind(&mut self, name: &str, ty: PlumType) {
|
|
3725
3592
|
if self.named_set.insert(name.to_string()) {
|
|
3726
|
-
self.named.push((name.to_string(), ty
|
|
3593
|
+
self.named.push((name.to_string(), ty));
|
|
3727
3594
|
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
/// The checker's type of `expr` (`Int` if the node was never recorded).
|
|
3598
|
+
fn ty(&self, expr: &ast::Expr) -> PlumType {
|
|
3599
|
+
self.types
|
|
3728
|
-
|
|
3600
|
+
.get(&(expr as *const ast::Expr as usize))
|
|
3601
|
+
.cloned()
|
|
3602
|
+
.unwrap_or(PlumType::TInt)
|
|
3729
3603
|
}
|
|
3730
3604
|
|
|
3731
3605
|
/// Binds every `Name` sub-pattern anywhere inside `pat` (at any nesting depth) to
|
|
@@ -3748,7 +3622,7 @@ impl<'a> Collector<'a> {
|
|
|
3748
3622
|
match pat {
|
|
3749
3623
|
ast::CasePattern::Name(n) => {
|
|
3750
3624
|
let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
|
|
3751
|
-
&& self.
|
|
3625
|
+
&& self.enum_variants.contains_key(n);
|
|
3752
3626
|
if !is_variant {
|
|
3753
3627
|
self.bind(n, ty.clone());
|
|
3754
3628
|
}
|
|
@@ -3759,8 +3633,10 @@ impl<'a> Collector<'a> {
|
|
|
3759
3633
|
self.next_nested_class_slot += 1;
|
|
3760
3634
|
self.nested_class_scratch.insert(key, slot);
|
|
3761
3635
|
self.nested_class_scratch_types.push(name.clone());
|
|
3762
|
-
if let Some(info) = self.
|
|
3636
|
+
if let Some(info) = self.enum_variants.get(name) {
|
|
3637
|
+
// The payload types as instantiated by the matched value's type
|
|
3638
|
+
// arguments (`Ok(v)` against `Try[Int, E]` binds `v: Int`).
|
|
3763
|
-
let field_types =
|
|
3639
|
+
let field_types = instFields(info, ty);
|
|
3764
3640
|
for (f, fty) in fields.iter().zip(field_types.iter()) {
|
|
3765
3641
|
self.collectPattern(f, fty);
|
|
3766
3642
|
}
|
|
@@ -3783,23 +3659,7 @@ impl<'a> Collector<'a> {
|
|
|
3783
3659
|
self.walkExpr(value);
|
|
3784
3660
|
match target {
|
|
3785
3661
|
ast::AssignTarget::Var(name) => {
|
|
3786
|
-
let ty =
|
|
3662
|
+
let ty = self.ty(value);
|
|
3787
|
-
// The checker's own closure inference (`inferExpr` on
|
|
3788
|
-
// `Expr::Closure`) infers the return type by recursively
|
|
3789
|
-
// inferring the body's tail expression with each param bound
|
|
3790
|
-
// to a fresh, unconstrained `TVar` — e.g. a captured/param
|
|
3791
|
-
// attribute access (`c.age`) on a `TVar`-typed object isn't a
|
|
3792
|
-
// known class, so it errors out entirely, and this call site
|
|
3793
|
-
// then silently defaults to `TInt` — the *wrong* wasm local
|
|
3794
|
-
// width for what's actually always an `i32` pointer. All that
|
|
3795
|
-
// actually matters here is the local's wasm width, and every
|
|
3796
|
-
// closure value is an i32 pointer regardless of its
|
|
3797
|
-
// parameter/return types, so skip inference entirely.
|
|
3798
|
-
PlumType::TFun(Vec::new(), Box::new(PlumType::TUnit))
|
|
3799
|
-
} else {
|
|
3800
|
-
plum_checker::inferExpr(value, &self.env, &self.cctx)
|
|
3801
|
-
.unwrap_or(PlumType::TInt)
|
|
3802
|
-
};
|
|
3803
3663
|
self.bind(name, ty);
|
|
3804
3664
|
}
|
|
3805
3665
|
ast::AssignTarget::Field(object, _) => {
|
|
@@ -3827,8 +3687,7 @@ impl<'a> Collector<'a> {
|
|
|
3827
3687
|
}
|
|
3828
3688
|
ast::Stmt::For(f) => {
|
|
3829
3689
|
self.walkExpr(&f.iter);
|
|
3830
|
-
let iter_ty =
|
|
3690
|
+
let iter_ty = self.ty(&f.iter);
|
|
3831
|
-
.unwrap_or(PlumType::TInt);
|
|
3832
3691
|
if let PlumType::TVariadic(elem) = &iter_ty {
|
|
3833
3692
|
let idx = self.next_variadic_for_slot;
|
|
3834
3693
|
self.next_variadic_for_slot += 1;
|
|
@@ -3852,13 +3711,12 @@ impl<'a> Collector<'a> {
|
|
|
3852
3711
|
.iter()
|
|
3853
3712
|
.map(|s| {
|
|
3854
3713
|
self.walkExpr(s);
|
|
3855
|
-
|
|
3714
|
+
self.ty(s)
|
|
3856
3715
|
})
|
|
3857
3716
|
.collect();
|
|
3858
3717
|
self.match_scratch
|
|
3859
3718
|
.insert(m as *const ast::Match as usize, subject_types.clone());
|
|
3860
3719
|
for case in &m.cases {
|
|
3861
|
-
let saved = self.env.clone();
|
|
3862
3720
|
for (pat, subject_ty) in case.patterns.iter().zip(subject_types.iter()) {
|
|
3863
3721
|
self.collectPattern(pat, subject_ty);
|
|
3864
3722
|
}
|
|
@@ -3866,7 +3724,6 @@ impl<'a> Collector<'a> {
|
|
|
3866
3724
|
self.walkExpr(g);
|
|
3867
3725
|
}
|
|
3868
3726
|
self.walkBlock(&case.body);
|
|
3869
|
-
self.env = saved;
|
|
3870
3727
|
}
|
|
3871
3728
|
}
|
|
3872
3729
|
ast::Stmt::Break | ast::Stmt::Continue | ast::Stmt::Todo => {}
|
|
@@ -3906,12 +3763,8 @@ impl<'a> Collector<'a> {
|
|
|
3906
3763
|
// in each branch, not by declaring this local pre-narrowed.
|
|
3907
3764
|
ast::Expr::Try(inner) => {
|
|
3908
3765
|
let key = expr as *const ast::Expr as usize;
|
|
3766
|
+
let ty = self.ty(inner);
|
|
3909
|
-
let
|
|
3767
|
+
let name = plum_checker::nominalName(&ty).unwrap_or("").to_string();
|
|
3910
|
-
.unwrap_or(PlumType::TVar("_".to_string()));
|
|
3911
|
-
let name = match &ty {
|
|
3912
|
-
PlumType::TNamed(n) => n.clone(),
|
|
3913
|
-
_ => String::new(),
|
|
3914
|
-
};
|
|
3915
3768
|
let slot = self.next_nested_class_slot;
|
|
3916
3769
|
self.next_nested_class_slot += 1;
|
|
3917
3770
|
self.nested_class_scratch.insert(key, slot);
|
|
@@ -3931,12 +3784,8 @@ impl<'a> Collector<'a> {
|
|
|
3931
3784
|
// `return`.
|
|
3932
3785
|
ast::Expr::Elvis(e) => {
|
|
3933
3786
|
let key = expr as *const ast::Expr as usize;
|
|
3787
|
+
let ty = self.ty(&e.left);
|
|
3934
|
-
let
|
|
3788
|
+
let name = plum_checker::nominalName(&ty).unwrap_or("").to_string();
|
|
3935
|
-
.unwrap_or(PlumType::TVar("_".to_string()));
|
|
3936
|
-
let name = match &ty {
|
|
3937
|
-
PlumType::TNamed(n) => n.clone(),
|
|
3938
|
-
_ => String::new(),
|
|
3939
|
-
};
|
|
3940
3789
|
let slot = self.next_nested_class_slot;
|
|
3941
3790
|
self.next_nested_class_slot += 1;
|
|
3942
3791
|
self.nested_class_scratch.insert(key, slot);
|
|
@@ -3994,39 +3843,24 @@ fn compileFnBody(
|
|
|
3994
3843
|
) -> Result<Vec<u8>, String> {
|
|
3995
3844
|
let mut body = Vec::new();
|
|
3996
3845
|
|
|
3846
|
+
// Fallback type env (only consulted for synthesized nodes the checker never
|
|
3847
|
+
// saw): globals, `self`, and the params under their DECLARED (generic) types.
|
|
3997
3848
|
let mut base_env = ctx.global_env.clone();
|
|
3849
|
+
let mut decl_types: HashMap<String, PlumType> = HashMap::new();
|
|
3998
3850
|
if let Some(recv) = &f.type_param {
|
|
3999
|
-
base_env.insert(
|
|
4000
|
-
"self".to_string(),
|
|
4001
|
-
|
|
3851
|
+
let self_ty = plum_checker::plumTypeFromName(recv);
|
|
3852
|
+
base_env.insert("self".to_string(), TypeScheme::mono(self_ty.clone()));
|
|
4002
|
-
);
|
|
3853
|
+
decl_types.insert("self".to_string(), self_ty);
|
|
4003
3854
|
}
|
|
4004
3855
|
for p in &f.params {
|
|
4005
|
-
let ty =
|
|
3856
|
+
let ty = paramPlumType(&p.ty);
|
|
4006
|
-
ast::ParamType::Type(t) => plum_checker::plumTypeFromAst(t),
|
|
4007
|
-
ast::ParamType::Variadic(t) => {
|
|
4008
|
-
PlumType::TVariadic(Box::new(plum_checker::plumTypeFromAst(t)))
|
|
4009
|
-
}
|
|
4010
|
-
ast::ParamType::Fn(params, ret) => {
|
|
4011
|
-
let param_types = params.iter().map(plum_checker::plumTypeFromAst).collect();
|
|
4012
|
-
let ret_ty = ret
|
|
4013
|
-
.as_ref()
|
|
4014
|
-
.map(|r| plum_checker::plumTypeFromAst(r))
|
|
4015
|
-
.unwrap_or(PlumType::TUnit);
|
|
4016
|
-
PlumType::TFun(param_types, Box::new(ret_ty))
|
|
4017
|
-
}
|
|
4018
|
-
};
|
|
4019
|
-
base_env.insert(p.name.clone(), TypeScheme::mono(ty));
|
|
3857
|
+
base_env.insert(p.name.clone(), TypeScheme::mono(ty.clone()));
|
|
3858
|
+
decl_types.insert(p.name.clone(), ty);
|
|
4020
3859
|
}
|
|
4021
3860
|
|
|
4022
3861
|
let mut collector = Collector {
|
|
4023
|
-
env: base_env.clone(),
|
|
4024
|
-
cctx: checkCtxOf(
|
|
4025
|
-
|
|
3862
|
+
types: &ctx.types,
|
|
4026
|
-
|
|
3863
|
+
enum_variants: &ctx.enum_variants,
|
|
4027
|
-
&ctx.enum_params,
|
|
4028
|
-
&ctx.min_required,
|
|
4029
|
-
),
|
|
4030
3864
|
named: Vec::new(),
|
|
4031
3865
|
named_set: Default::default(),
|
|
4032
3866
|
match_scratch: HashMap::new(),
|
|
@@ -4043,6 +3877,9 @@ fn compileFnBody(
|
|
|
4043
3877
|
// `main() -> Int = each(|v| v)`), which needs construction scratch slots.
|
|
4044
3878
|
collector.walkExpr(e);
|
|
4045
3879
|
}
|
|
3880
|
+
for (name, ty) in &collector.named {
|
|
3881
|
+
decl_types.entry(name.clone()).or_insert_with(|| ty.clone());
|
|
3882
|
+
}
|
|
4046
3883
|
// ---- assign local indices: [self?][params][named...][classcall scratch...][match scratch...] ----
|
|
4047
3884
|
let mut locals: HashMap<String, u32> = HashMap::new();
|
|
4048
3885
|
let mut groups: Vec<ValType> = Vec::new();
|
|
@@ -4118,11 +3955,14 @@ fn compileFnBody(
|
|
|
4118
3955
|
variadic_for_scratch: collector.variadic_for_scratch,
|
|
4119
3956
|
func_ids: &ctx.func_ids,
|
|
4120
3957
|
func_sigs: &ctx.func_sigs,
|
|
3958
|
+
types: &ctx.types,
|
|
3959
|
+
local_decl_types: decl_types,
|
|
4121
3960
|
closures: &ctx.closures,
|
|
4122
3961
|
closure_call_types: &ctx.closure_call_types,
|
|
4123
3962
|
named_fn_values: &ctx.named_fn_values,
|
|
4124
3963
|
string_concat_func: ctx.string_concat_func,
|
|
4125
3964
|
string_eq_func: ctx.string_eq_func,
|
|
3965
|
+
dyn_eq_func: ctx.dyn_eq_func,
|
|
4126
3966
|
int_to_string_func: ctx.int_to_string_func,
|
|
4127
3967
|
methods: &ctx.methods,
|
|
4128
3968
|
enum_variants: &ctx.enum_variants,
|
|
@@ -4131,10 +3971,11 @@ fn compileFnBody(
|
|
|
4131
3971
|
gc_types: &ctx.gc_types,
|
|
4132
3972
|
singleton_globals: &ctx.singleton_globals,
|
|
4133
3973
|
type_env: RefCell::new(base_env),
|
|
4134
|
-
closure_local_sigs: RefCell::new(HashMap::new()),
|
|
4135
3974
|
loop_extra_depth: Cell::new(0),
|
|
3975
|
+
in_closure: false,
|
|
4136
|
-
ret_type: f.returns.as_ref().map(
|
|
3976
|
+
ret_type: f.returns.as_ref().map(plum_checker::plumTypeFromAstG),
|
|
4137
3977
|
match_subject_types: RefCell::new(HashMap::new()),
|
|
3978
|
+
subject_ptypes: RefCell::new(HashMap::new()),
|
|
4138
3979
|
};
|
|
4139
3980
|
|
|
4140
3981
|
let result_vt = retTypeToWasm(f.returns.as_ref());
|
|
@@ -4382,47 +4223,36 @@ fn compileStmt(
|
|
|
4382
4223
|
for (target, value) in a.targets.iter().zip(a.values.iter()) {
|
|
4383
4224
|
match target {
|
|
4384
4225
|
ast::AssignTarget::Var(name) => {
|
|
4385
|
-
// See the matching comment in `Collector::walkStmt`: the checker's
|
|
4386
|
-
// closure inference is unreliable (can error out entirely depending
|
|
4387
|
-
// on the body), but every closure value is an i32 pointer regardless
|
|
4388
|
-
// of its real signature, so don't bother inferring it at all here.
|
|
4389
|
-
let vty = if matches!(value, ast::Expr::Closure(_)) {
|
|
4390
|
-
PlumType::TFun(Vec::new(), Box::new(PlumType::TUnit))
|
|
4391
|
-
} else {
|
|
4392
|
-
|
|
4226
|
+
let vty = inferLocalType(value, ctx);
|
|
4393
|
-
};
|
|
4394
4227
|
compileExpr(value, body, ctx, state)?;
|
|
4395
4228
|
let idx = ctx
|
|
4396
4229
|
.locals
|
|
4397
4230
|
.get(name)
|
|
4398
4231
|
.copied()
|
|
4399
4232
|
.ok_or_else(|| format!("undeclared local '{}'", name))?;
|
|
4233
|
+
// Convert to the representation the local was declared with
|
|
4234
|
+
// (differs only across an erased/instantiated edge).
|
|
4235
|
+
if let Some(decl) = ctx.local_decl_types.get(name) {
|
|
4236
|
+
coerceVt(
|
|
4237
|
+
plumTypeToValtype(&vty),
|
|
4238
|
+
plumTypeToValtype(decl),
|
|
4239
|
+
body,
|
|
4240
|
+
ctx.gc_types,
|
|
4241
|
+
);
|
|
4242
|
+
}
|
|
4400
4243
|
Instruction::LocalSet(idx).encode(body);
|
|
4401
4244
|
ctx.type_env
|
|
4402
4245
|
.borrow_mut()
|
|
4403
4246
|
.insert(name.clone(), TypeScheme::mono(vty));
|
|
4404
|
-
// If this assigns a closure *literal*, remember its exact, already-
|
|
4405
|
-
// correct signature (computed by the discovery pass) so a later call
|
|
4406
|
-
// to it doesn't have to re-derive one — see `closure_local_sigs`.
|
|
4407
|
-
if let ast::Expr::Closure(cl) = value {
|
|
4408
|
-
let key = cl.as_ref() as *const ast::Closure as usize;
|
|
4409
|
-
if let Some(info) = ctx.closures.get(&key) {
|
|
4410
|
-
let mut sig_params = vec![ValType::Ref(RefType::ANYREF)];
|
|
4411
|
-
sig_params.extend(info.param_vts.iter().copied());
|
|
4412
|
-
ctx.closure_local_sigs
|
|
4413
|
-
.borrow_mut()
|
|
4414
|
-
.insert(name.clone(), (sig_params, info.ret_vt));
|
|
4415
|
-
}
|
|
4416
|
-
}
|
|
4417
4247
|
}
|
|
4418
4248
|
ast::AssignTarget::Field(object, field_name) => {
|
|
4419
4249
|
let obj_ty = inferLocalType(object, ctx);
|
|
4420
|
-
let class_name = match &obj_ty {
|
|
4250
|
+
let class_name = match plum_checker::nominalName(&obj_ty) {
|
|
4421
|
-
|
|
4251
|
+
Some(n) => n.to_string(),
|
|
4422
|
-
|
|
4252
|
+
None => {
|
|
4423
4253
|
return Err(format!(
|
|
4424
4254
|
"codegen: cannot assign field '{}' on non-class type {}",
|
|
4425
|
-
field_name,
|
|
4255
|
+
field_name, obj_ty
|
|
4426
4256
|
))
|
|
4427
4257
|
}
|
|
4428
4258
|
};
|
|
@@ -4456,7 +4286,7 @@ fn compileStmt(
|
|
|
4456
4286
|
)
|
|
4457
4287
|
})?;
|
|
4458
4288
|
compileExpr(object, body, ctx, state)?;
|
|
4459
|
-
|
|
4289
|
+
compileExprInto(value, ¶ms[field_idx].1, body, ctx, state)?;
|
|
4460
4290
|
Instruction::StructSet {
|
|
4461
4291
|
struct_type_index: super_type_idx,
|
|
4462
4292
|
field_index: fld(field_idx),
|
|
@@ -4487,7 +4317,7 @@ fn compileStmt(
|
|
|
4487
4317
|
compileExpr(object, body, ctx, state)?;
|
|
4488
4318
|
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx))
|
|
4489
4319
|
.encode(body);
|
|
4490
|
-
|
|
4320
|
+
compileExprInto(value, &info.field_types[field_idx], body, ctx, state)?;
|
|
4491
4321
|
Instruction::StructSet {
|
|
4492
4322
|
struct_type_index: variant_type_idx,
|
|
4493
4323
|
field_index: fld(field_idx),
|
|
@@ -4500,10 +4330,23 @@ fn compileStmt(
|
|
|
4500
4330
|
}
|
|
4501
4331
|
}
|
|
4502
4332
|
ast::Stmt::Return(Some(e)) => {
|
|
4333
|
+
match &ctx.ret_type {
|
|
4334
|
+
Some(rt) => compileExprInto(e, rt, body, ctx, state)?,
|
|
4335
|
+
None => compileExpr(e, body, ctx, state)?,
|
|
4336
|
+
}
|
|
4337
|
+
if ctx.in_closure {
|
|
4338
|
+
// Closure ABI: the result leaves as an `anyref`.
|
|
4503
|
-
|
|
4339
|
+
let rt_vt = ctx.ret_type.as_ref().map(plumTypeToValtype);
|
|
4340
|
+
if let Some(vt) = rt_vt {
|
|
4341
|
+
coerceVt(vt, ValType::Ref(RefType::ANYREF), body, ctx.gc_types);
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4504
4344
|
Instruction::Return.encode(body);
|
|
4505
4345
|
}
|
|
4506
4346
|
ast::Stmt::Return(None) => {
|
|
4347
|
+
if ctx.in_closure {
|
|
4348
|
+
Instruction::RefNull(HeapType::ANY).encode(body);
|
|
4349
|
+
}
|
|
4507
4350
|
Instruction::Return.encode(body);
|
|
4508
4351
|
}
|
|
4509
4352
|
ast::Stmt::If(if_) => {
|
|
@@ -4681,12 +4524,13 @@ fn compileStmt(
|
|
|
4681
4524
|
fn exprHasResult(expr: &ast::Expr, ctx: &LocalCtx) -> bool {
|
|
4682
4525
|
match expr {
|
|
4683
4526
|
ast::Expr::FnCall(call) => {
|
|
4684
|
-
if ctx.locals.contains_key(&call.name)
|
|
4527
|
+
if ctx.locals.contains_key(&call.name)
|
|
4528
|
+
&& matches!(
|
|
4529
|
+
ctx.local_decl_types.get(&call.name),
|
|
4685
|
-
|
|
4530
|
+
Some(PlumType::TFun(_, _))
|
|
4686
|
-
|
|
4531
|
+
)
|
|
4687
|
-
|
|
4532
|
+
{
|
|
4688
|
-
|
|
4533
|
+
return !closureCallReturnsUnit(&call.name, ctx);
|
|
4689
|
-
}
|
|
4690
4534
|
}
|
|
4691
4535
|
ctx.func_sigs
|
|
4692
4536
|
.get(&call.name)
|
|
@@ -4695,17 +4539,38 @@ fn exprHasResult(expr: &ast::Expr, ctx: &LocalCtx) -> bool {
|
|
|
4695
4539
|
}
|
|
4696
4540
|
ast::Expr::Attribute(attr) => match &attr.attr {
|
|
4697
4541
|
ast::AttrKind::Method(call) => {
|
|
4542
|
+
let obj_ty = inferLocalType(&attr.object, ctx);
|
|
4543
|
+
// A call on an erased or trait-typed receiver dispatches through a
|
|
4544
|
+
// trait method whose declared return type decides.
|
|
4545
|
+
let trait_owner = match &obj_ty {
|
|
4546
|
+
PlumType::TParam(_) => dynOwnerForMethod(&call.name, call.args.len(), ctx),
|
|
4547
|
+
t => plum_checker::nominalName(t)
|
|
4548
|
+
.filter(|n| {
|
|
4549
|
+
ctx.gc_types.class_type_idx.get(*n)
|
|
4550
|
+
== Some(&ctx.gc_types.obj_type_idx)
|
|
4551
|
+
})
|
|
4552
|
+
.map(|n| {
|
|
4553
|
+
if ctx.methods.contains_key(&(n.to_string(), call.name.clone())) {
|
|
4554
|
+
n.to_string()
|
|
4555
|
+
} else {
|
|
4556
|
+
dynOwnerForMethod(&call.name, call.args.len(), ctx)
|
|
4557
|
+
.unwrap_or_else(|| n.to_string())
|
|
4558
|
+
}
|
|
4559
|
+
}),
|
|
4560
|
+
};
|
|
4561
|
+
if let Some(owner) = trait_owner {
|
|
4562
|
+
return match ctx.methods.get(&(owner, call.name.clone())) {
|
|
4563
|
+
Some(PlumType::TFun(_, ret)) => !matches!(**ret, PlumType::TUnit),
|
|
4564
|
+
_ => true,
|
|
4565
|
+
};
|
|
4566
|
+
}
|
|
4698
4567
|
// `methodReceiverName` (not a bare `TNamed` match) so this also
|
|
4699
4568
|
// covers a Unit-returning method called in statement position on
|
|
4700
4569
|
// a BUILTIN primitive receiver (`Int`/`Float`/`Bool`/`Str`/`Byte`/
|
|
4701
4570
|
// `[]Byte`) — e.g. `self.data.set(...)` on a `[]Byte` field — not
|
|
4702
|
-
// just an ordinary class. Without this, such a call was wrongly
|
|
4703
|
-
// assumed to leave a value on the stack, emitting a `Drop` with
|
|
4704
|
-
//
|
|
4571
|
+
// just an ordinary class.
|
|
4705
|
-
if let Some(class_name) =
|
|
4706
|
-
|
|
4572
|
+
if let Some(class_name) = plum_checker::methodReceiverName(&obj_ty) {
|
|
4707
|
-
{
|
|
4708
|
-
let key =
|
|
4573
|
+
let (_, key, _) = resolveMethodKey(&class_name, &call.name, ctx);
|
|
4709
4574
|
ctx.func_sigs
|
|
4710
4575
|
.get(&key)
|
|
4711
4576
|
.map(|s| s.ret.is_some())
|
|
@@ -4902,6 +4767,9 @@ fn compileMatch(
|
|
|
4902
4767
|
let subject_ty = inferLocalType(subject, ctx);
|
|
4903
4768
|
let subject_vt = plumTypeToValtype(&subject_ty);
|
|
4904
4769
|
let scratch_local = ctx.match_scratch_base + base_slot + i as u32;
|
|
4770
|
+
ctx.subject_ptypes
|
|
4771
|
+
.borrow_mut()
|
|
4772
|
+
.insert(scratch_local, subject_ty.clone());
|
|
4905
4773
|
if matches!(subject_ty, PlumType::TTags(_)) {
|
|
4906
4774
|
ctx.match_subject_types
|
|
4907
4775
|
.borrow_mut()
|
|
@@ -5297,6 +5165,11 @@ fn compileVariantConstructorArm(
|
|
|
5297
5165
|
)
|
|
5298
5166
|
})?;
|
|
5299
5167
|
let field_types = info.field_types.clone();
|
|
5168
|
+
// Payload types instantiated by the subject's type arguments.
|
|
5169
|
+
let inst_types = match ctx.subject_ptypes.borrow().get(&scratch_local) {
|
|
5170
|
+
Some(sty) => instFields(info, sty),
|
|
5171
|
+
None => field_types.clone(),
|
|
5172
|
+
};
|
|
5300
5173
|
|
|
5301
5174
|
// This pattern's own narrowly-typed scratch local (declared with variant's exact
|
|
5302
5175
|
// concrete ref type — see `nested_class_scratch_types`), distinct from
|
|
@@ -5323,6 +5196,7 @@ fn compileVariantConstructorArm(
|
|
|
5323
5196
|
compileFieldPatterns(
|
|
5324
5197
|
fields,
|
|
5325
5198
|
&field_types,
|
|
5199
|
+
&inst_types,
|
|
5326
5200
|
0,
|
|
5327
5201
|
narrow_local,
|
|
5328
5202
|
variant_idx,
|
|
@@ -5372,6 +5246,7 @@ fn compileVariantConstructorArm(
|
|
|
5372
5246
|
fn compileFieldPatterns(
|
|
5373
5247
|
fields: &[ast::CasePattern],
|
|
5374
5248
|
field_types: &[PlumType],
|
|
5249
|
+
inst_types: &[PlumType],
|
|
5375
5250
|
fpos: usize,
|
|
5376
5251
|
container_local: u32,
|
|
5377
5252
|
container_type_idx: u32,
|
|
@@ -5388,7 +5263,12 @@ fn compileFieldPatterns(
|
|
|
5388
5263
|
return on_match(body, state);
|
|
5389
5264
|
}
|
|
5390
5265
|
let pat = &fields[fpos];
|
|
5266
|
+
// The field's DECLARED type fixes its stored representation (an erased `T` slot
|
|
5267
|
+
// is a `ref null $Obj`); its INSTANTIATED type is what a binding/pattern sees.
|
|
5391
|
-
let
|
|
5268
|
+
let decl_ty = &field_types[fpos];
|
|
5269
|
+
let field_ty = &inst_types[fpos];
|
|
5270
|
+
let decl_vt = plumTypeToValtype(decl_ty);
|
|
5271
|
+
let inst_vt = plumTypeToValtype(field_ty);
|
|
5392
5272
|
|
|
5393
5273
|
if let PlumType::TTags(tags) = field_ty {
|
|
5394
5274
|
if isTagPattern(pat) {
|
|
@@ -5402,6 +5282,7 @@ fn compileFieldPatterns(
|
|
|
5402
5282
|
field_index: fld(fpos),
|
|
5403
5283
|
}
|
|
5404
5284
|
.encode(b);
|
|
5285
|
+
coerceVt(decl_vt, inst_vt, b, ctx.gc_types);
|
|
5405
5286
|
},
|
|
5406
5287
|
result_vt,
|
|
5407
5288
|
body,
|
|
@@ -5411,6 +5292,7 @@ fn compileFieldPatterns(
|
|
|
5411
5292
|
compileFieldPatterns(
|
|
5412
5293
|
fields,
|
|
5413
5294
|
field_types,
|
|
5295
|
+
inst_types,
|
|
5414
5296
|
fpos + 1,
|
|
5415
5297
|
container_local,
|
|
5416
5298
|
container_type_idx,
|
|
@@ -5443,6 +5325,7 @@ fn compileFieldPatterns(
|
|
|
5443
5325
|
ast::CasePattern::Wildcard => compileFieldPatterns(
|
|
5444
5326
|
fields,
|
|
5445
5327
|
field_types,
|
|
5328
|
+
inst_types,
|
|
5446
5329
|
fpos + 1,
|
|
5447
5330
|
container_local,
|
|
5448
5331
|
container_type_idx,
|
|
@@ -5471,6 +5354,7 @@ fn compileFieldPatterns(
|
|
|
5471
5354
|
field_index: fld(fpos),
|
|
5472
5355
|
}
|
|
5473
5356
|
.encode(body);
|
|
5357
|
+
coerceVt(decl_vt, inst_vt, body, ctx.gc_types);
|
|
5474
5358
|
Instruction::LocalSet(idx).encode(body);
|
|
5475
5359
|
ctx.type_env
|
|
5476
5360
|
.borrow_mut()
|
|
@@ -5478,6 +5362,7 @@ fn compileFieldPatterns(
|
|
|
5478
5362
|
compileFieldPatterns(
|
|
5479
5363
|
fields,
|
|
5480
5364
|
field_types,
|
|
5365
|
+
inst_types,
|
|
5481
5366
|
fpos + 1,
|
|
5482
5367
|
container_local,
|
|
5483
5368
|
container_type_idx,
|
|
@@ -5497,7 +5382,7 @@ fn compileFieldPatterns(
|
|
|
5497
5382
|
ctx.enum_variants
|
|
5498
5383
|
.get(n)
|
|
5499
5384
|
.ok_or_else(|| format!("codegen: unknown enum variant '{}'", n))?;
|
|
5500
|
-
if !matches!(
|
|
5385
|
+
if !matches!(decl_vt, ValType::Ref(_)) {
|
|
5501
5386
|
return Err(format!(
|
|
5502
5387
|
"codegen: enum tag pattern '{}' against a non-enum field",
|
|
5503
5388
|
n
|
|
@@ -5517,6 +5402,7 @@ fn compileFieldPatterns(
|
|
|
5517
5402
|
compileFieldPatterns(
|
|
5518
5403
|
fields,
|
|
5519
5404
|
field_types,
|
|
5405
|
+
inst_types,
|
|
5520
5406
|
fpos + 1,
|
|
5521
5407
|
container_local,
|
|
5522
5408
|
container_type_idx,
|
|
@@ -5543,7 +5429,7 @@ fn compileFieldPatterns(
|
|
|
5543
5429
|
Ok(())
|
|
5544
5430
|
}
|
|
5545
5431
|
ast::CasePattern::Int(n) => {
|
|
5546
|
-
if
|
|
5432
|
+
if inst_vt != ValType::I64 {
|
|
5547
5433
|
return Err("codegen: integer match pattern against a non-Int field".to_string());
|
|
5548
5434
|
}
|
|
5549
5435
|
Instruction::LocalGet(container_local).encode(body);
|
|
@@ -5552,12 +5438,14 @@ fn compileFieldPatterns(
|
|
|
5552
5438
|
field_index: fld(fpos),
|
|
5553
5439
|
}
|
|
5554
5440
|
.encode(body);
|
|
5441
|
+
coerceVt(decl_vt, inst_vt, body, ctx.gc_types);
|
|
5555
5442
|
Instruction::I64Const(*n).encode(body);
|
|
5556
5443
|
Instruction::I64Eq.encode(body);
|
|
5557
5444
|
Instruction::If(blockTypeFor(result_vt)).encode(body);
|
|
5558
5445
|
compileFieldPatterns(
|
|
5559
5446
|
fields,
|
|
5560
5447
|
field_types,
|
|
5448
|
+
inst_types,
|
|
5561
5449
|
fpos + 1,
|
|
5562
5450
|
container_local,
|
|
5563
5451
|
container_type_idx,
|
|
@@ -5597,7 +5485,7 @@ fn compileFieldPatterns(
|
|
|
5597
5485
|
.enum_variants
|
|
5598
5486
|
.get(name)
|
|
5599
5487
|
.ok_or_else(|| format!("codegen: unknown enum variant '{}'", name))?;
|
|
5600
|
-
if !matches!(
|
|
5488
|
+
if !matches!(decl_vt, ValType::Ref(_)) {
|
|
5601
5489
|
return Err(format!(
|
|
5602
5490
|
"codegen: constructor pattern '{}' against a non-enum field",
|
|
5603
5491
|
name
|
|
@@ -5624,6 +5512,7 @@ fn compileFieldPatterns(
|
|
|
5624
5512
|
})?;
|
|
5625
5513
|
let nested_local = ctx.nested_class_scratch_base + slot;
|
|
5626
5514
|
let inner_field_types = info.field_types.clone();
|
|
5515
|
+
let inner_inst_types = instFields(info, field_ty);
|
|
5627
5516
|
|
|
5628
5517
|
Instruction::LocalGet(container_local).encode(body);
|
|
5629
5518
|
Instruction::StructGet {
|
|
@@ -5645,6 +5534,7 @@ fn compileFieldPatterns(
|
|
|
5645
5534
|
compileFieldPatterns(
|
|
5646
5535
|
inner_fields,
|
|
5647
5536
|
&inner_field_types,
|
|
5537
|
+
&inner_inst_types,
|
|
5648
5538
|
0,
|
|
5649
5539
|
nested_local,
|
|
5650
5540
|
inner_variant_idx,
|
|
@@ -5659,6 +5549,7 @@ fn compileFieldPatterns(
|
|
|
5659
5549
|
compileFieldPatterns(
|
|
5660
5550
|
fields,
|
|
5661
5551
|
field_types,
|
|
5552
|
+
inst_types,
|
|
5662
5553
|
fpos + 1,
|
|
5663
5554
|
container_local,
|
|
5664
5555
|
container_type_idx,
|
|
@@ -5712,7 +5603,16 @@ fn compileExpr(
|
|
|
5712
5603
|
}
|
|
5713
5604
|
ast::Expr::Var(name) => {
|
|
5714
5605
|
match ctx.locals.get(name.as_str()) {
|
|
5606
|
+
Some(idx) => {
|
|
5715
|
-
|
|
5607
|
+
Instruction::LocalGet(*idx).encode(body);
|
|
5608
|
+
// The local holds its DECLARED representation; this use site's
|
|
5609
|
+
// type may differ across an erased edge (a generic `T` slot read
|
|
5610
|
+
// at its instantiated type).
|
|
5611
|
+
if let Some(decl) = ctx.local_decl_types.get(name.as_str()) {
|
|
5612
|
+
let want = plumTypeToValtype(&inferLocalType(expr, ctx));
|
|
5613
|
+
coerceVt(plumTypeToValtype(decl), want, body, ctx.gc_types);
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5716
5616
|
// Not a local: a bare reference to a top-level function used as a
|
|
5717
5617
|
// value (e.g. `each(double)`) — push its zero-capture trampoline
|
|
5718
5618
|
// closure's (pre-built by the `start` function) global.
|
|
@@ -5739,12 +5639,12 @@ fn compileExpr(
|
|
|
5739
5639
|
// inside an expression here for the first time.
|
|
5740
5640
|
ast::Expr::Try(inner) => {
|
|
5741
5641
|
let inner_ty = inferLocalType(inner, ctx);
|
|
5742
|
-
let enum_name = match &inner_ty {
|
|
5642
|
+
let enum_name = match plum_checker::nominalName(&inner_ty) {
|
|
5743
|
-
|
|
5643
|
+
Some(n) => n.to_string(),
|
|
5744
|
-
|
|
5644
|
+
None => {
|
|
5745
5645
|
return Err(format!(
|
|
5746
5646
|
"codegen: '?' requires a Try value, found {}",
|
|
5747
|
-
|
|
5647
|
+
inner_ty
|
|
5748
5648
|
))
|
|
5749
5649
|
}
|
|
5750
5650
|
};
|
|
@@ -5781,13 +5681,16 @@ fn compileExpr(
|
|
|
5781
5681
|
failure_name
|
|
5782
5682
|
)
|
|
5783
5683
|
})?;
|
|
5684
|
+
// The payload slot is declared at its generic type (`T` -> `$Obj`); the
|
|
5685
|
+
// value this expression yields is at its instantiated type.
|
|
5784
|
-
let
|
|
5686
|
+
let raw_success_vt = plumTypeToValtype(
|
|
5785
5687
|
&success_info
|
|
5786
5688
|
.field_types
|
|
5787
5689
|
.first()
|
|
5788
5690
|
.cloned()
|
|
5789
5691
|
.unwrap_or(PlumType::TUnit),
|
|
5790
5692
|
);
|
|
5693
|
+
let success_vt = plumTypeToValtype(&inferLocalType(expr, ctx));
|
|
5791
5694
|
|
|
5792
5695
|
let key = expr as *const ast::Expr as usize;
|
|
5793
5696
|
let slot = *ctx
|
|
@@ -5802,8 +5705,8 @@ fn compileExpr(
|
|
|
5802
5705
|
Instruction::If(BlockType::Result(success_vt)).encode(body);
|
|
5803
5706
|
// Failure branch: take the inner `Err` payload and build the OUTER
|
|
5804
5707
|
// function's `Err` variant from it (the Ok types may differ).
|
|
5805
|
-
let outer_err = match
|
|
5708
|
+
let outer_err = match ctx.ret_type.as_ref().and_then(plum_checker::nominalName) {
|
|
5806
|
-
Some(
|
|
5709
|
+
Some(outer_enum) if outer_enum != enum_name => {
|
|
5807
5710
|
plum_checker::tryOperatorVariants(outer_enum, &cctx)
|
|
5808
5711
|
.map(|(_, _, fname, _)| fname)
|
|
5809
5712
|
}
|
|
@@ -5848,6 +5751,7 @@ fn compileExpr(
|
|
|
5848
5751
|
field_index: fld(0),
|
|
5849
5752
|
}
|
|
5850
5753
|
.encode(body);
|
|
5754
|
+
coerceVt(raw_success_vt, success_vt, body, ctx.gc_types);
|
|
5851
5755
|
}
|
|
5852
5756
|
Instruction::End.encode(body);
|
|
5853
5757
|
}
|
|
@@ -5858,12 +5762,12 @@ fn compileExpr(
|
|
|
5858
5762
|
// the field, same as `Try`'s own success branch.
|
|
5859
5763
|
ast::Expr::Elvis(e) => {
|
|
5860
5764
|
let left_ty = inferLocalType(&e.left, ctx);
|
|
5861
|
-
let enum_name = match &left_ty {
|
|
5765
|
+
let enum_name = match plum_checker::nominalName(&left_ty) {
|
|
5862
|
-
|
|
5766
|
+
Some(n) => n.to_string(),
|
|
5863
|
-
|
|
5767
|
+
None => {
|
|
5864
5768
|
return Err(format!(
|
|
5865
5769
|
"codegen: '??' requires a Try value, found {}",
|
|
5866
|
-
|
|
5770
|
+
left_ty
|
|
5867
5771
|
))
|
|
5868
5772
|
}
|
|
5869
5773
|
};
|
|
@@ -5900,13 +5804,14 @@ fn compileExpr(
|
|
|
5900
5804
|
failure_name
|
|
5901
5805
|
)
|
|
5902
5806
|
})?;
|
|
5903
|
-
let
|
|
5807
|
+
let raw_success_vt = plumTypeToValtype(
|
|
5904
5808
|
&success_info
|
|
5905
5809
|
.field_types
|
|
5906
5810
|
.first()
|
|
5907
5811
|
.cloned()
|
|
5908
5812
|
.unwrap_or(PlumType::TUnit),
|
|
5909
5813
|
);
|
|
5814
|
+
let success_vt = plumTypeToValtype(&inferLocalType(expr, ctx));
|
|
5910
5815
|
|
|
5911
5816
|
let key = expr as *const ast::Expr as usize;
|
|
5912
5817
|
let slot = *ctx
|
|
@@ -5919,7 +5824,7 @@ fn compileExpr(
|
|
|
5919
5824
|
Instruction::LocalTee(scratch_local).encode(body);
|
|
5920
5825
|
Instruction::RefTestNonNull(HeapType::Concrete(failure_idx)).encode(body);
|
|
5921
5826
|
Instruction::If(BlockType::Result(success_vt)).encode(body);
|
|
5922
|
-
|
|
5827
|
+
compileExprAs(&e.right, Some(success_vt), body, ctx, state)?;
|
|
5923
5828
|
Instruction::Else.encode(body);
|
|
5924
5829
|
Instruction::LocalGet(scratch_local).encode(body);
|
|
5925
5830
|
Instruction::RefCastNonNull(HeapType::Concrete(success_idx)).encode(body);
|
|
@@ -5929,6 +5834,7 @@ fn compileExpr(
|
|
|
5929
5834
|
field_index: fld(0),
|
|
5930
5835
|
}
|
|
5931
5836
|
.encode(body);
|
|
5837
|
+
coerceVt(raw_success_vt, success_vt, body, ctx.gc_types);
|
|
5932
5838
|
}
|
|
5933
5839
|
Instruction::End.encode(body);
|
|
5934
5840
|
}
|
|
@@ -6030,6 +5936,23 @@ fn compileExpr(
|
|
|
6030
5936
|
compileExpr(&c.left, body, ctx, state)?;
|
|
6031
5937
|
compileExpr(&c.right, body, ctx, state)?;
|
|
6032
5938
|
match left_ty {
|
|
5939
|
+
// Two erased values (`T == T` inside a generic body): the runtime
|
|
5940
|
+
// helper compares boxed primitives and `Str`s by value.
|
|
5941
|
+
PlumType::TParam(_) => match &c.op {
|
|
5942
|
+
ast::CmpOp::Eq => {
|
|
5943
|
+
Instruction::Call(ctx.dyn_eq_func).encode(body);
|
|
5944
|
+
}
|
|
5945
|
+
ast::CmpOp::Neq | ast::CmpOp::NotEq2 => {
|
|
5946
|
+
Instruction::Call(ctx.dyn_eq_func).encode(body);
|
|
5947
|
+
Instruction::I32Eqz.encode(body);
|
|
5948
|
+
}
|
|
5949
|
+
other => {
|
|
5950
|
+
return Err(format!(
|
|
5951
|
+
"codegen: '{:?}' is not supported between generic values",
|
|
5952
|
+
other
|
|
5953
|
+
))
|
|
5954
|
+
}
|
|
5955
|
+
},
|
|
6033
5956
|
PlumType::TFloat => {
|
|
6034
5957
|
match c.op {
|
|
6035
5958
|
ast::CmpOp::Lt => Instruction::F64Lt,
|
|
@@ -6095,12 +6018,12 @@ fn compileExpr(
|
|
|
6095
6018
|
pushBoolRefFromI32Flag(body, ctx);
|
|
6096
6019
|
}
|
|
6097
6020
|
ast::Expr::Ternary(t) => {
|
|
6098
|
-
let result_vt = plumTypeToValtype(&inferLocalType(
|
|
6021
|
+
let result_vt = plumTypeToValtype(&inferLocalType(expr, ctx));
|
|
6099
6022
|
compileBoolConditionAsI32(&t.condition, body, ctx, state)?;
|
|
6100
6023
|
Instruction::If(BlockType::Result(result_vt)).encode(body);
|
|
6101
|
-
|
|
6024
|
+
compileExprAs(&t.then, Some(result_vt), body, ctx, state)?;
|
|
6102
6025
|
Instruction::Else.encode(body);
|
|
6103
|
-
|
|
6026
|
+
compileExprAs(&t.else_, Some(result_vt), body, ctx, state)?;
|
|
6104
6027
|
Instruction::End.encode(body);
|
|
6105
6028
|
}
|
|
6106
6029
|
ast::Expr::FnCall(call) => {
|
|
@@ -6108,8 +6031,8 @@ fn compileExpr(
|
|
|
6108
6031
|
// dispatched via `call_indirect` — not a direct `Call` to a named function.
|
|
6109
6032
|
let is_closure_call = ctx.locals.contains_key(&call.name)
|
|
6110
6033
|
&& matches!(
|
|
6111
|
-
|
|
6034
|
+
ctx.local_decl_types.get(&call.name),
|
|
6112
|
-
PlumType::TFun(_, _)
|
|
6035
|
+
Some(PlumType::TFun(_, _))
|
|
6113
6036
|
);
|
|
6114
6037
|
// `Int(x)`/`Float(x)`/`Byte(x)` is the primitive numeric CAST
|
|
6115
6038
|
// special case just below UNLESS `call.name` is ALSO a
|
|
@@ -6125,12 +6048,7 @@ fn compileExpr(
|
|
|
6125
6048
|
// numeric-cast call site in the stdlib (`Float(self)`,
|
|
6126
6049
|
// `Int(y)`, ...) keeps working unchanged.
|
|
6127
6050
|
let single_arg_ty = if call.args.len() == 1 {
|
|
6128
|
-
let arg_expr = match &call.args[0] {
|
|
6129
|
-
ast::Arg::Positional(e) => e,
|
|
6130
|
-
ast::Arg::Keyword { value, .. } => value,
|
|
6131
|
-
ast::Arg::Pair { value, .. } => value,
|
|
6132
|
-
};
|
|
6133
|
-
Some(inferLocalType(
|
|
6051
|
+
Some(inferLocalType(argExprOf(&call.args[0]), ctx))
|
|
6134
6052
|
} else {
|
|
6135
6053
|
None
|
|
6136
6054
|
};
|
|
@@ -6144,11 +6062,7 @@ fn compileExpr(
|
|
|
6144
6062
|
&& !is_same_type_variant_wrap
|
|
6145
6063
|
&& single_arg_ty.is_some()
|
|
6146
6064
|
{
|
|
6147
|
-
let arg_expr =
|
|
6065
|
+
let arg_expr = argExprOf(&call.args[0]);
|
|
6148
|
-
ast::Arg::Positional(e) => e,
|
|
6149
|
-
ast::Arg::Keyword { value, .. } => value,
|
|
6150
|
-
ast::Arg::Pair { value, .. } => value,
|
|
6151
|
-
};
|
|
6152
6066
|
let arg_ty = single_arg_ty.expect("just checked is_some");
|
|
6153
6067
|
compileExpr(arg_expr, body, ctx, state)?;
|
|
6154
6068
|
match (call.name.as_str(), &arg_ty) {
|
|
@@ -6167,32 +6081,9 @@ fn compileExpr(
|
|
|
6167
6081
|
_ => {}
|
|
6168
6082
|
}
|
|
6169
6083
|
} else if is_closure_call {
|
|
6170
|
-
compileClosureCall(call, body, ctx, state)?;
|
|
6084
|
+
compileClosureCall(call, expr, body, ctx, state)?;
|
|
6171
|
-
// `Type(1, 2, 3)` — mirrors `plum-checker`'s own `inferExpr` handling of
|
|
6172
|
-
//
|
|
6085
|
+
// `Bad(x)` / `Bad()` — an uppercase name the checker typed as a tag
|
|
6173
|
-
// equivalent static method-call expression so the existing
|
|
6174
|
-
// `AttrKind::Method` codegen (below) handles it without duplication.
|
|
6175
|
-
// Checked BEFORE the ordinary positional-variant-construction case just
|
|
6176
|
-
// below — see the matching comment in `plum-checker`'s `inferExpr` for
|
|
6177
|
-
//
|
|
6086
|
+
// union is a tag literal, even when a declared variant shares its name.
|
|
6178
|
-
} else if ctx
|
|
6179
|
-
.enum_variants
|
|
6180
|
-
.get(&call.name)
|
|
6181
|
-
.is_some_and(|info| info.enum_name == call.name)
|
|
6182
|
-
&& ctx
|
|
6183
|
-
.methods
|
|
6184
|
-
.contains_key(&(call.name.clone(), "init".to_string()))
|
|
6185
|
-
{
|
|
6186
|
-
let synthetic = ast::Expr::Attribute(Box::new(ast::AttributeExpr {
|
|
6187
|
-
object: ast::Expr::TypeName(call.name.clone()),
|
|
6188
|
-
attr: ast::AttrKind::Method(ast::FnCall {
|
|
6189
|
-
name: "init".to_string(),
|
|
6190
|
-
args: call.args.clone(),
|
|
6191
|
-
}),
|
|
6192
|
-
}));
|
|
6193
|
-
compileExpr(&synthetic, body, ctx, state)?;
|
|
6194
|
-
} else if let Some(info) = ctx.enum_variants.get(&call.name) {
|
|
6195
|
-
compileVariantConstruction(info, call, expr, body, ctx, state)?;
|
|
6196
6087
|
} else if !ctx.func_ids.contains_key(&call.name)
|
|
6197
6088
|
&& call.args.len() <= 1
|
|
6198
6089
|
&& call
|
|
@@ -6202,12 +6093,7 @@ fn compileExpr(
|
|
|
6202
6093
|
.is_some_and(|c| c.is_uppercase() || c == plum_checker::TAG_MARKER)
|
|
6203
6094
|
&& matches!(inferLocalType(expr, ctx), PlumType::TTags(_))
|
|
6204
6095
|
{
|
|
6205
|
-
// `Bad(x)` / `Bad()` — an undeclared uppercase name is a tag literal.
|
|
6206
|
-
let payload = call.args.first().map(
|
|
6096
|
+
let payload = call.args.first().map(argExprOf);
|
|
6207
|
-
ast::Arg::Positional(e) => e,
|
|
6208
|
-
ast::Arg::Keyword { value, .. } => value,
|
|
6209
|
-
ast::Arg::Pair { value, .. } => value,
|
|
6210
|
-
});
|
|
6211
6097
|
compileTagLiteral(
|
|
6212
6098
|
plum_checker::tagLiteralName(&call.name),
|
|
6213
6099
|
payload,
|
|
@@ -6215,69 +6101,49 @@ fn compileExpr(
|
|
|
6215
6101
|
ctx,
|
|
6216
6102
|
state,
|
|
6217
6103
|
)?;
|
|
6104
|
+
// `Type(1, 2, 3)` — mirrors `plum-checker`'s own `inferExpr` handling of
|
|
6105
|
+
// the same case: sugar for `Type.init(1, 2, 3)`, compiled as that
|
|
6106
|
+
// equivalent static method call (so the existing method-call codegen
|
|
6107
|
+
// handles it without duplication). Checked BEFORE the ordinary
|
|
6108
|
+
// positional-variant-construction case just below — see the matching
|
|
6109
|
+
// comment in `plum-checker`'s `inferExpr` for why a record-shaped
|
|
6110
|
+
// enum's own name needs this precedence.
|
|
6218
|
-
} else
|
|
6111
|
+
} else if ctx
|
|
6112
|
+
.enum_variants
|
|
6113
|
+
.get(&call.name)
|
|
6219
|
-
|
|
6114
|
+
.is_some_and(|info| info.enum_name == call.name)
|
|
6220
|
-
|
|
6115
|
+
&& ctx
|
|
6221
|
-
|
|
6116
|
+
.methods
|
|
6222
|
-
|
|
6117
|
+
.contains_key(&(call.name.clone(), "init".to_string()))
|
|
6223
|
-
ast::Arg::Pair { value, .. } => value,
|
|
6224
|
-
|
|
6118
|
+
{
|
|
6225
|
-
}
|
|
6226
|
-
let
|
|
6119
|
+
let object = ast::Expr::TypeName(call.name.clone());
|
|
6227
|
-
let
|
|
6120
|
+
let init_call = ast::FnCall {
|
|
6228
|
-
PlumType::TFun(params, _) => match params.last() {
|
|
6229
|
-
Some(PlumType::TVariadic(elem)) => {
|
|
6230
|
-
|
|
6121
|
+
name: "init".to_string(),
|
|
6231
|
-
}
|
|
6232
|
-
|
|
6122
|
+
args: call.args.clone(),
|
|
6233
|
-
},
|
|
6234
|
-
_ => None,
|
|
6235
6123
|
};
|
|
6124
|
+
compileMethodCall(
|
|
6125
|
+
&object,
|
|
6126
|
+
&init_call,
|
|
6236
|
-
|
|
6127
|
+
&PlumType::TNamed(call.name.clone()),
|
|
6128
|
+
expr,
|
|
6129
|
+
body,
|
|
6130
|
+
ctx,
|
|
6131
|
+
state,
|
|
6132
|
+
)?;
|
|
6133
|
+
} else if let Some(info) = ctx.enum_variants.get(&call.name) {
|
|
6134
|
+
compileVariantConstruction(info, call, expr, body, ctx, state)?;
|
|
6135
|
+
} else {
|
|
6136
|
+
let sig = ctx
|
|
6237
6137
|
.func_sigs
|
|
6238
6138
|
.get(&call.name)
|
|
6239
|
-
.map(|s| s.params.clone())
|
|
6240
|
-
.unwrap_or_default();
|
|
6241
|
-
match variadic_split {
|
|
6242
|
-
Some((elem_ty, fixed_count)) => {
|
|
6243
|
-
|
|
6139
|
+
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
6244
|
-
|
|
6140
|
+
compileCallArgs(&call.name, &call.args, sig, body, ctx, state)?;
|
|
6245
|
-
}
|
|
6246
|
-
let trailing: Vec<&ast::Expr> =
|
|
6247
|
-
call.args.iter().skip(fixed_count).map(argExprOf).collect();
|
|
6248
|
-
let elem_vt = plumTypeToValtype(&elem_ty);
|
|
6249
|
-
let array_type_idx = *ctx
|
|
6250
|
-
.gc_types
|
|
6251
|
-
.variadic_array_type_idx
|
|
6252
|
-
.get(&elem_vt)
|
|
6253
|
-
.ok_or_else(|| "internal codegen error: no variadic array type registered for this elem type".to_string())?;
|
|
6254
|
-
for arg_expr in &trailing {
|
|
6255
|
-
compileExprAs(arg_expr, Some(elem_vt), body, ctx, state)?;
|
|
6256
|
-
}
|
|
6257
|
-
Instruction::ArrayNewFixed {
|
|
6258
|
-
array_type_index: array_type_idx,
|
|
6259
|
-
array_size: trailing.len() as u32,
|
|
6260
|
-
}
|
|
6261
|
-
.encode(body);
|
|
6262
|
-
|
|
6263
|
-
|
|
6141
|
+
let func_idx = ctx
|
|
6264
|
-
|
|
6142
|
+
.func_ids
|
|
6265
|
-
|
|
6143
|
+
.get(&call.name)
|
|
6266
|
-
|
|
6144
|
+
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
6267
|
-
|
|
6145
|
+
Instruction::Call(*func_idx).encode(body);
|
|
6268
|
-
}
|
|
6269
|
-
None => {
|
|
6270
|
-
for (i, arg) in call.args.iter().enumerate() {
|
|
6271
|
-
compileExprAs(argExprOf(arg), sig_params.get(i).copied(), body, ctx, state)?;
|
|
6272
|
-
}
|
|
6273
|
-
|
|
6146
|
+
coerceCallResult(&sig.decl_ret, expr, body, ctx);
|
|
6274
|
-
let func_idx = ctx
|
|
6275
|
-
.func_ids
|
|
6276
|
-
.get(&call.name)
|
|
6277
|
-
.ok_or_else(|| format!("unknown function '{}'", call.name))?;
|
|
6278
|
-
Instruction::Call(*func_idx).encode(body);
|
|
6279
|
-
}
|
|
6280
|
-
}
|
|
6281
6147
|
}
|
|
6282
6148
|
}
|
|
6283
6149
|
ast::Expr::Self_ => {
|
|
@@ -6290,6 +6156,11 @@ fn compileExpr(
|
|
|
6290
6156
|
}
|
|
6291
6157
|
ast::Expr::TypeName(n) => {
|
|
6292
6158
|
match ctx.enum_variants.get(n) {
|
|
6159
|
+
// A bare uppercase name the checker typed as a tag union is a tag
|
|
6160
|
+
// literal, even when a declared variant shares its name.
|
|
6161
|
+
Some(_) if matches!(inferLocalType(expr, ctx), PlumType::TTags(_)) => {
|
|
6162
|
+
compileTagLiteral(plum_checker::tagLiteralName(n), None, body, ctx, state)?
|
|
6163
|
+
}
|
|
6293
6164
|
Some(info) if info.field_types.is_empty() => {
|
|
6294
6165
|
let global_idx = *ctx.singleton_globals.get(n)
|
|
6295
6166
|
.unwrap_or_else(|| panic!("internal codegen error: payload-free variant '{}' has no singleton global", n));
|
|
@@ -6349,14 +6220,20 @@ fn compileExpr(
|
|
|
6349
6220
|
&& call.spread.is_none()
|
|
6350
6221
|
&& matches!(ctx.methods.get(&(call.type_name.clone(), "init".to_string())), Some(PlumType::TFun(p, _)) if initCallableWithNoArgs(p)) =>
|
|
6351
6222
|
{
|
|
6352
|
-
let synthetic = ast::Expr::Attribute(Box::new(ast::AttributeExpr {
|
|
6353
|
-
|
|
6223
|
+
let object = ast::Expr::TypeName(call.type_name.clone());
|
|
6354
|
-
|
|
6224
|
+
let init_call = ast::FnCall {
|
|
6355
|
-
|
|
6225
|
+
name: "init".to_string(),
|
|
6356
|
-
|
|
6226
|
+
args: vec![],
|
|
6357
|
-
|
|
6227
|
+
};
|
|
6228
|
+
compileMethodCall(
|
|
6229
|
+
&object,
|
|
6230
|
+
&init_call,
|
|
6231
|
+
&PlumType::TNamed(call.type_name.clone()),
|
|
6232
|
+
expr,
|
|
6233
|
+
body,
|
|
6234
|
+
ctx,
|
|
6235
|
+
state,
|
|
6358
|
-
|
|
6236
|
+
)?;
|
|
6359
|
-
compileExpr(&synthetic, body, ctx, state)?;
|
|
6360
6237
|
}
|
|
6361
6238
|
// A NAMED-payload enum variant construction (`Circle(radius: 5)`, as
|
|
6362
6239
|
// opposed to the positional/generic-payload form `Next(T)` — constructed
|
|
@@ -6391,13 +6268,7 @@ fn compileExpr(
|
|
|
6391
6268
|
Instruction::I32Const(typeIdOf(ctx.gc_types, &call.type_name)).encode(body);
|
|
6392
6269
|
for (field_idx, field_name) in field_names.iter().enumerate() {
|
|
6393
6270
|
match call.fields.iter().find(|fa| &fa.name == field_name) {
|
|
6394
|
-
Some(fa) => compileExprAs(
|
|
6395
|
-
&fa.value,
|
|
6396
|
-
|
|
6271
|
+
Some(fa) => compileExprInto(&fa.value, &info.field_types[field_idx], body, ctx, state)?,
|
|
6397
|
-
body,
|
|
6398
|
-
ctx,
|
|
6399
|
-
state,
|
|
6400
|
-
)?,
|
|
6401
6272
|
// `Circle(..c, radius: 10)` — a field not overridden is read
|
|
6402
6273
|
// straight off the spread source instead (checker only proved
|
|
6403
6274
|
// `c` is SOME value of the owning enum, restricted to a bare
|
|
@@ -6440,305 +6311,10 @@ fn compileExpr(
|
|
|
6440
6311
|
let obj_ty = inferLocalType(&attr.object, ctx);
|
|
6441
6312
|
match &attr.attr {
|
|
6442
6313
|
ast::AttrKind::Field(field_name) => {
|
|
6443
|
-
// `Str` is an ordinary class (`data: Buffer`) with no name of
|
|
6444
|
-
// its own carried in `PlumType::TStr` — resolve it via the
|
|
6445
|
-
// literal name "Str", same as `TNamed` does via its own name.
|
|
6446
|
-
let class_name = match &obj_ty {
|
|
6447
|
-
PlumType::TNamed(n) => n.clone(),
|
|
6448
|
-
PlumType::TStr => "Str".to_string(),
|
|
6449
|
-
other => {
|
|
6450
|
-
return Err(format!(
|
|
6451
|
-
"codegen: cannot access field '{}' on non-class type {}",
|
|
6452
|
-
field_name, other
|
|
6453
|
-
))
|
|
6454
|
-
}
|
|
6455
|
-
};
|
|
6456
|
-
// Fall back to a discriminant enum's shared params, declared
|
|
6457
|
-
// directly on the enum's SUPERTYPE (see `buildGcTypeRegistry`'s
|
|
6458
|
-
// `EnumSuper` arm) — no `ref.cast` to any particular variant
|
|
6459
|
-
// needed, since every variant has the exact same field list as
|
|
6460
|
-
// the supertype itself. This ALSO covers a record-shaped enum's
|
|
6461
|
-
// own field (the `type X = ...` replacement) via the unique-
|
|
6462
|
-
// owning-variant fallback below, since its one variant is
|
|
6463
|
-
// trivially its own unique owner of every field it declares.
|
|
6464
|
-
match ctx.enum_params.get(&class_name) {
|
|
6465
|
-
Some(params) => {
|
|
6466
|
-
let field_idx = params
|
|
6467
|
-
.iter()
|
|
6468
|
-
.position(|(n, _)| n == field_name)
|
|
6469
|
-
.ok_or_else(|| {
|
|
6470
|
-
format!(
|
|
6471
|
-
"codegen: no field '{}' on enum '{}'",
|
|
6472
|
-
field_name, class_name
|
|
6473
|
-
)
|
|
6474
|
-
})?;
|
|
6475
|
-
let super_type_idx = *ctx
|
|
6476
|
-
.gc_types
|
|
6477
|
-
.enum_super_type_idx
|
|
6478
|
-
.get(&class_name)
|
|
6479
|
-
.ok_or_else(|| {
|
|
6480
|
-
format!(
|
|
6481
|
-
"codegen: enum '{}' missing from the GC type registry",
|
|
6482
|
-
class_name
|
|
6483
|
-
)
|
|
6484
|
-
})?;
|
|
6485
|
-
|
|
6314
|
+
compileFieldRead(&attr.object, field_name, &obj_ty, expr, body, ctx, state)?
|
|
6486
|
-
Instruction::StructGet {
|
|
6487
|
-
struct_type_index: super_type_idx,
|
|
6488
|
-
field_index: fld(field_idx),
|
|
6489
|
-
}
|
|
6490
|
-
.encode(body);
|
|
6491
|
-
}
|
|
6492
|
-
// Not a discriminant enum either: `class_name` is an
|
|
6493
|
-
// ordinary enum, and `plum-checker`'s `inferExpr` already
|
|
6494
|
-
// verified exactly ONE of its variants declares a field
|
|
6495
|
-
// with this name (ambiguity across sibling variants is a
|
|
6496
|
-
// checker error, never reaches codegen). Compile `.field`
|
|
6497
|
-
// as a checked downcast to that one variant's own
|
|
6498
|
-
// concrete struct type (same `ref.cast` idiom `match`
|
|
6499
|
-
// pattern-matching already uses, e.g. `compileCasePositions`),
|
|
6500
|
-
// then read the field off it — traps at runtime if the
|
|
6501
|
-
// value turns out to be a different variant, so no static
|
|
6502
|
-
// proof the value IS that variant is required here.
|
|
6503
|
-
None => {
|
|
6504
|
-
let (variant_name, info) = ctx
|
|
6505
|
-
.enum_variants
|
|
6506
|
-
.iter()
|
|
6507
|
-
.find(|(_, info)| {
|
|
6508
|
-
info.enum_name == class_name
|
|
6509
|
-
&& info.field_names.iter().any(|n| n == field_name)
|
|
6510
|
-
})
|
|
6511
|
-
.ok_or_else(|| {
|
|
6512
|
-
format!(
|
|
6513
|
-
"codegen: no field '{}' on enum '{}'",
|
|
6514
|
-
field_name, class_name
|
|
6515
|
-
)
|
|
6516
|
-
})?;
|
|
6517
|
-
let field_idx = info
|
|
6518
|
-
.field_names
|
|
6519
|
-
.iter()
|
|
6520
|
-
.position(|n| n == field_name)
|
|
6521
|
-
.expect("just found by this field name");
|
|
6522
|
-
let variant_type_idx = *ctx
|
|
6523
|
-
.gc_types
|
|
6524
|
-
.variant_type_idx
|
|
6525
|
-
.get(variant_name)
|
|
6526
|
-
.ok_or_else(|| {
|
|
6527
|
-
format!(
|
|
6528
|
-
"codegen: variant '{}' missing from the GC type registry",
|
|
6529
|
-
variant_name
|
|
6530
|
-
)
|
|
6531
|
-
})?;
|
|
6532
|
-
compileExpr(&attr.object, body, ctx, state)?;
|
|
6533
|
-
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx))
|
|
6534
|
-
.encode(body);
|
|
6535
|
-
Instruction::StructGet {
|
|
6536
|
-
struct_type_index: variant_type_idx,
|
|
6537
|
-
field_index: fld(field_idx),
|
|
6538
|
-
}
|
|
6539
|
-
.encode(body);
|
|
6540
|
-
}
|
|
6541
|
-
}
|
|
6542
6315
|
}
|
|
6543
6316
|
ast::AttrKind::Method(call) => {
|
|
6544
|
-
let class_name = match &obj_ty {
|
|
6545
|
-
PlumType::TNamed(n) => n.clone(),
|
|
6546
|
-
// Builtin primitive types (`Int`/`Float`/`Bool`/`Str`) declare
|
|
6547
|
-
// methods the same way classes do (`type Int = fun ...` in
|
|
6548
|
-
// `libs/std`) — they're just never `TNamed`, so map them back
|
|
6549
|
-
// to the receiver name `ctx.func_ids`/`ctx.methods` use.
|
|
6550
|
-
PlumType::TInt => "Int".to_string(),
|
|
6551
|
-
PlumType::TFloat => "Float".to_string(),
|
|
6552
|
-
PlumType::TBool => "Bool".to_string(),
|
|
6553
|
-
PlumType::TStr => "Str".to_string(),
|
|
6554
|
-
PlumType::TByte => "Byte".to_string(),
|
|
6555
|
-
PlumType::TByteSlice => "ByteSlice".to_string(),
|
|
6556
|
-
other => {
|
|
6557
|
-
return Err(format!(
|
|
6558
|
-
"codegen: cannot call method '{}' on non-class type {}",
|
|
6559
|
-
call.name, other
|
|
6560
|
-
))
|
|
6561
|
-
}
|
|
6562
|
-
};
|
|
6563
|
-
// A method call on a trait-typed value goes through the trait's
|
|
6564
|
-
// `$dyn$<Trait>$<method>` dispatcher (reads the receiver's typeId).
|
|
6565
|
-
let is_trait_recv = ctx.gc_types.class_type_idx.get(&class_name)
|
|
6566
|
-
== Some(&ctx.gc_types.obj_type_idx)
|
|
6567
|
-
&& ctx.methods.contains_key(&(
|
|
6568
|
-
class_name.clone(),
|
|
6569
|
-
plum_checker::TRAIT_MARKER.to_string(),
|
|
6570
|
-
));
|
|
6571
|
-
// A method the receiver's own trait doesn't declare (e.g. a
|
|
6572
|
-
// `T: ToStr` bound's `toStr` on a `Shape`-typed value inside a
|
|
6573
|
-
// specialized generic) dispatches through whichever trait does.
|
|
6574
|
-
let dyn_owner = if is_trait_recv {
|
|
6575
|
-
if ctx.gc_types.dyn_fns.contains_key(&(class_name.clone(), call.name.clone())) {
|
|
6576
|
-
Some(class_name.clone())
|
|
6577
|
-
} else {
|
|
6578
|
-
ctx.gc_types
|
|
6579
|
-
.dyn_fns
|
|
6580
|
-
.keys()
|
|
6581
|
-
.find(|(_, m)| *m == call.name)
|
|
6582
|
-
.map(|(t, _)| t.clone())
|
|
6583
|
-
}
|
|
6584
|
-
} else {
|
|
6585
|
-
None
|
|
6586
|
-
};
|
|
6587
|
-
if is_trait_recv && dyn_owner.is_none() {
|
|
6588
|
-
// No implementor of this method exists at all: unreachable at
|
|
6589
|
-
// runtime, but the call must still type-check structurally.
|
|
6590
|
-
Instruction::Unreachable.encode(body);
|
|
6591
|
-
return Ok(());
|
|
6592
|
-
}
|
|
6593
|
-
if let Some(&dyn_idx) = dyn_owner.as_ref().and_then(|t| {
|
|
6594
|
-
ctx.gc_types
|
|
6595
|
-
.dyn_fns
|
|
6596
|
-
.get(&(t.clone(), call.name.clone()))
|
|
6597
|
-
}) {
|
|
6598
|
-
let owner = dyn_owner.clone().unwrap_or_default();
|
|
6599
|
-
let param_tys: Vec<PlumType> =
|
|
6600
|
-
match ctx.methods.get(&(owner, call.name.clone())) {
|
|
6601
|
-
Some(PlumType::TFun(ps, _)) => ps.clone(),
|
|
6602
|
-
_ => Vec::new(),
|
|
6603
|
-
};
|
|
6604
|
-
compileExpr(&attr.object, body, ctx, state)?;
|
|
6605
|
-
for (i, arg) in call.args.iter().enumerate() {
|
|
6606
|
-
let e = match arg {
|
|
6607
|
-
ast::Arg::Positional(e) => e,
|
|
6608
|
-
ast::Arg::Keyword { value, .. } => value,
|
|
6609
|
-
ast::Arg::Pair { value, .. } => value,
|
|
6610
|
-
};
|
|
6611
|
-
compileExprAs(e, param_tys.get(i).map(plumTypeToValtype), body, ctx, state)?;
|
|
6612
|
-
}
|
|
6613
|
-
Instruction::Call(dyn_idx).encode(body);
|
|
6614
|
-
return Ok(());
|
|
6615
|
-
}
|
|
6616
|
-
let direct_key = format!("{}::{}", class_name, call.name);
|
|
6617
|
-
// Fallback: a primitive/class receiver's method might be
|
|
6618
|
-
// defined once on the "union" enum that bare-wraps it
|
|
6619
|
-
// instead (`enum Number = | Int | Float`, e.g. `abs`
|
|
6620
|
-
// defined on `Number` rather than duplicated per
|
|
6621
|
-
// primitive) — `class_name` (e.g. "Int") is itself a
|
|
6622
|
-
// registered enum-variant NAME in that case (see
|
|
6623
|
-
// `buildGlobalTables`'s bare-wrap conditions), so look up
|
|
6624
|
-
// its owning enum and retry there. `box_variant_idx` is
|
|
6625
|
-
// this variant's own concrete GC struct type index, used
|
|
6626
|
-
// below to box the raw `self` value into it before the
|
|
6627
|
-
// call (the wrap-enum's method expects a boxed `Number`,
|
|
6628
|
-
// not a bare `i64`/`f64`).
|
|
6629
|
-
let wrap_fallback = (!ctx.func_ids.contains_key(&direct_key)).then(|| {
|
|
6630
|
-
ctx.enum_variants.get(class_name.as_str()).and_then(|info| {
|
|
6631
|
-
let wrap_key = format!("{}::{}", info.enum_name, call.name);
|
|
6632
|
-
ctx.func_ids.contains_key(&wrap_key).then(|| {
|
|
6633
|
-
let variant_idx = *ctx.gc_types.variant_type_idx.get(class_name.as_str())
|
|
6634
|
-
.expect("class_name is a registered enum variant, checked above");
|
|
6635
|
-
(info.enum_name.clone(), wrap_key, variant_idx)
|
|
6636
|
-
})
|
|
6637
|
-
})
|
|
6638
|
-
}).flatten();
|
|
6639
|
-
let (dispatch_name, key, box_variant_idx) = match wrap_fallback {
|
|
6640
|
-
Some((enum_name, wrap_key, variant_idx)) => {
|
|
6641
|
-
(enum_name, wrap_key, Some(variant_idx))
|
|
6642
|
-
}
|
|
6643
|
-
None => (class_name.clone(), direct_key, None),
|
|
6644
|
-
};
|
|
6645
|
-
let func_idx = *ctx.func_ids.get(&key).ok_or_else(|| {
|
|
6646
|
-
format!("codegen: unknown method '{}.{}'", class_name, call.name)
|
|
6647
|
-
})?;
|
|
6648
|
-
// A "static"-style call (`Bool.parse("true")`, `Float.fromStr("3.14")`)
|
|
6649
|
-
// on a self-less method declared inside a `type`/`enum` body — its
|
|
6650
|
-
// receiver is a bare reference to the type's own name, not a real
|
|
6651
|
-
// value. `attr.object`'s only possible resolution to exactly
|
|
6652
|
-
// `TNamed(class_name)` via a bare `TypeName` is this pattern (a real
|
|
6653
|
-
// enum-variant/const reference resolves to some OTHER concrete type,
|
|
6654
|
-
// per the checker's `inferExpr`). Every method still reserves a
|
|
6655
|
-
// leading self slot in its wasm signature regardless of whether its
|
|
6656
|
-
// Plum source declares a `self` param (see `fnWasmParamTypes`), so
|
|
6657
|
-
// something of the right type must still be pushed — the body simply
|
|
6658
|
-
// never reads it (no `self` binding exists for it to read).
|
|
6659
|
-
let is_static_call =
|
|
6660
|
-
matches!(&attr.object, ast::Expr::TypeName(n) if *n == class_name);
|
|
6661
|
-
if is_static_call {
|
|
6662
|
-
// Use `class_name`'s wasm type (via `astTypeToWasm`, exactly
|
|
6663
|
-
// like `fnWasmParamTypes` computed the callee's actual self
|
|
6664
|
-
// slot type), NOT `obj_ty` — for a builtin primitive receiver
|
|
6665
|
-
// (`Int.fromStr`), `obj_ty` is `TNamed("Int")` (the checker's
|
|
6666
|
-
// bare-`TypeName` fallback doesn't know about primitives), whose
|
|
6667
|
-
// `plumTypeToValtype` would wrongly resolve to a GC ref instead
|
|
6668
|
-
// of `i64`.
|
|
6669
|
-
let vt = astTypeToWasm(&dispatch_name).unwrap_or(ValType::I32);
|
|
6670
|
-
pushSelfPlaceholder(vt, body);
|
|
6671
|
-
} else {
|
|
6672
|
-
if box_variant_idx.is_some() {
|
|
6673
|
-
// The wrap-enum variant's header typeId goes below its payload.
|
|
6674
|
-
Instruction::I32Const(typeIdOf(ctx.gc_types, &class_name)).encode(body);
|
|
6675
|
-
}
|
|
6676
|
-
compileExpr(&attr.object, body, ctx, state)?; // push self
|
|
6677
|
-
if let Some(variant_idx) = box_variant_idx {
|
|
6678
|
-
// Dispatching to the wrap-enum's method (see
|
|
6679
|
-
// `wrap_fallback` above) — its `self` param is
|
|
6680
|
-
// typed as the ENUM (`Number`), a boxed GC struct,
|
|
6681
|
-
// not the bare primitive/class value just pushed.
|
|
6682
|
-
Instruction::StructNew(variant_idx).encode(body);
|
|
6683
|
-
}
|
|
6684
|
-
}
|
|
6685
|
-
|
|
6686
|
-
fn argExprOf(arg: &ast::Arg) -> &ast::Expr {
|
|
6687
|
-
match arg {
|
|
6688
|
-
ast::Arg::Positional(e) => e,
|
|
6689
|
-
ast::Arg::Keyword { value, .. } => value,
|
|
6690
|
-
ast::Arg::Pair { value, .. } => value,
|
|
6691
|
-
}
|
|
6692
|
-
}
|
|
6693
|
-
// A trailing `...T` param (e.g. `add(self, values: ...T)`) packs its
|
|
6694
|
-
// trailing args into one GC array, exactly like a plain function call
|
|
6695
|
-
// (see the `Expr::FnCall` variadic-call arm) — `call.args` doesn't
|
|
6696
|
-
// include `self`, matching `ctx.methods`' own param list.
|
|
6697
|
-
let variadic_split =
|
|
6698
|
-
match ctx.methods.get(&(dispatch_name.clone(), call.name.clone())) {
|
|
6699
|
-
Some(PlumType::TFun(params, _)) => match params.last() {
|
|
6700
|
-
Some(PlumType::TVariadic(elem)) => {
|
|
6701
|
-
Some(((**elem).clone(), params.len() - 1))
|
|
6702
|
-
}
|
|
6703
|
-
_ => None,
|
|
6704
|
-
},
|
|
6705
|
-
_ => None,
|
|
6706
|
-
};
|
|
6707
|
-
let sig_params: Vec<ValType> = ctx
|
|
6708
|
-
.func_sigs
|
|
6709
|
-
.get(&key)
|
|
6710
|
-
.map(|s| s.params.clone())
|
|
6711
|
-
.unwrap_or_default();
|
|
6712
|
-
match variadic_split {
|
|
6713
|
-
Some((elem_ty, fixed_count)) => {
|
|
6714
|
-
for (i, arg) in call.args.iter().take(fixed_count).enumerate() {
|
|
6715
|
-
compileExprAs(argExprOf(arg), sig_params.get(i + 1).copied(), body, ctx, state)?;
|
|
6716
|
-
}
|
|
6717
|
-
let trailing: Vec<&ast::Expr> =
|
|
6718
|
-
call.args.iter().skip(fixed_count).map(argExprOf).collect();
|
|
6719
|
-
let elem_vt = plumTypeToValtype(&elem_ty);
|
|
6720
|
-
let array_type_idx = *ctx
|
|
6721
|
-
.gc_types
|
|
6722
|
-
.variadic_array_type_idx
|
|
6723
|
-
.get(&elem_vt)
|
|
6724
|
-
.ok_or_else(|| "internal codegen error: no variadic array type registered for this elem type".to_string())?;
|
|
6725
|
-
for arg_expr in &trailing {
|
|
6726
|
-
compileExprAs(arg_expr, Some(elem_vt), body, ctx, state)?;
|
|
6727
|
-
}
|
|
6728
|
-
Instruction::ArrayNewFixed {
|
|
6729
|
-
array_type_index: array_type_idx,
|
|
6730
|
-
array_size: trailing.len() as u32,
|
|
6731
|
-
}
|
|
6732
|
-
.encode(body);
|
|
6733
|
-
}
|
|
6734
|
-
None => {
|
|
6735
|
-
for (i, arg) in call.args.iter().enumerate() {
|
|
6736
|
-
compileExprAs(argExprOf(arg), sig_params.get(i + 1).copied(), body, ctx, state)?;
|
|
6737
|
-
}
|
|
6738
|
-
|
|
6317
|
+
compileMethodCall(&attr.object, call, &obj_ty, expr, body, ctx, state)?
|
|
6739
|
-
}
|
|
6740
|
-
}
|
|
6741
|
-
Instruction::Call(func_idx).encode(body);
|
|
6742
6318
|
}
|
|
6743
6319
|
}
|
|
6744
6320
|
}
|
|
@@ -6768,6 +6344,353 @@ fn compileExpr(
|
|
|
6768
6344
|
Ok(())
|
|
6769
6345
|
}
|
|
6770
6346
|
|
|
6347
|
+
/// After a call to a function whose DECLARED return type is `decl_ret`: converts
|
|
6348
|
+
/// the result from the callee's (erased) representation to the call
|
|
6349
|
+
/// expression's own instantiated type (unboxing a primitive that travelled
|
|
6350
|
+
/// through a generic `T` slot, or casting an erased reference back down).
|
|
6351
|
+
fn coerceCallResult(decl_ret: &PlumType, call_expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
6352
|
+
coerceCallResultTo(decl_ret, &inferLocalType(call_expr, ctx), body, ctx);
|
|
6353
|
+
}
|
|
6354
|
+
|
|
6355
|
+
fn coerceCallResultTo(decl_ret: &PlumType, want: &PlumType, body: &mut Vec<u8>, ctx: &LocalCtx) {
|
|
6356
|
+
if matches!(decl_ret, PlumType::TUnit) || matches!(want, PlumType::TUnit | PlumType::TVar(_)) {
|
|
6357
|
+
return;
|
|
6358
|
+
}
|
|
6359
|
+
coerceVt(
|
|
6360
|
+
plumTypeToValtype(decl_ret),
|
|
6361
|
+
plumTypeToValtype(want),
|
|
6362
|
+
body,
|
|
6363
|
+
ctx.gc_types,
|
|
6364
|
+
);
|
|
6365
|
+
}
|
|
6366
|
+
|
|
6367
|
+
/// Pushes the arguments of a call to the function described by `sig`: each fixed
|
|
6368
|
+
/// argument converted to its DECLARED param type (boxing into erased `T` slots,
|
|
6369
|
+
/// wrapping a bare `Int`/`Float` for a `Number` param), a trailing `...T` pack
|
|
6370
|
+
/// built into one GC array, and the declared defaults for omitted trailing params.
|
|
6371
|
+
fn compileCallArgs(
|
|
6372
|
+
func_key: &str,
|
|
6373
|
+
args: &[ast::Arg],
|
|
6374
|
+
sig: &FuncSig,
|
|
6375
|
+
body: &mut Vec<u8>,
|
|
6376
|
+
ctx: &LocalCtx,
|
|
6377
|
+
state: &mut ModuleState,
|
|
6378
|
+
) -> Result<(), String> {
|
|
6379
|
+
let decl = &sig.decl_params;
|
|
6380
|
+
match decl.last() {
|
|
6381
|
+
Some(PlumType::TVariadic(elem)) => {
|
|
6382
|
+
let fixed = decl.len() - 1;
|
|
6383
|
+
for (i, arg) in args.iter().take(fixed).enumerate() {
|
|
6384
|
+
compileExprInto(argExprOf(arg), &decl[i], body, ctx, state)?;
|
|
6385
|
+
}
|
|
6386
|
+
let trailing: Vec<&ast::Expr> = args.iter().skip(fixed).map(argExprOf).collect();
|
|
6387
|
+
let elem_vt = plumTypeToValtype(elem);
|
|
6388
|
+
let array_type_idx = *ctx
|
|
6389
|
+
.gc_types
|
|
6390
|
+
.variadic_array_type_idx
|
|
6391
|
+
.get(&elem_vt)
|
|
6392
|
+
.ok_or_else(|| "internal codegen error: no variadic array type registered for this elem type".to_string())?;
|
|
6393
|
+
for arg_expr in &trailing {
|
|
6394
|
+
compileExprInto(arg_expr, elem, body, ctx, state)?;
|
|
6395
|
+
}
|
|
6396
|
+
Instruction::ArrayNewFixed {
|
|
6397
|
+
array_type_index: array_type_idx,
|
|
6398
|
+
array_size: trailing.len() as u32,
|
|
6399
|
+
}
|
|
6400
|
+
.encode(body);
|
|
6401
|
+
}
|
|
6402
|
+
_ => {
|
|
6403
|
+
for (i, arg) in args.iter().enumerate() {
|
|
6404
|
+
match decl.get(i) {
|
|
6405
|
+
Some(d) => compileExprInto(argExprOf(arg), d, body, ctx, state)?,
|
|
6406
|
+
None => compileExpr(argExprOf(arg), body, ctx, state)?,
|
|
6407
|
+
}
|
|
6408
|
+
}
|
|
6409
|
+
compileDefaultArgs(func_key, args.len(), body, ctx, state)?;
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
6412
|
+
Ok(())
|
|
6413
|
+
}
|
|
6414
|
+
|
|
6415
|
+
/// `object.field` for a class / enum value: a `struct.get` of the field at its
|
|
6416
|
+
/// DECLARED (possibly erased `T`) representation, converted to the access
|
|
6417
|
+
/// expression's own instantiated type.
|
|
6418
|
+
fn compileFieldRead(
|
|
6419
|
+
object: &ast::Expr,
|
|
6420
|
+
field_name: &str,
|
|
6421
|
+
obj_ty: &PlumType,
|
|
6422
|
+
expr: &ast::Expr,
|
|
6423
|
+
body: &mut Vec<u8>,
|
|
6424
|
+
ctx: &LocalCtx,
|
|
6425
|
+
state: &mut ModuleState,
|
|
6426
|
+
) -> Result<(), String> {
|
|
6427
|
+
// `Str` is an ordinary class (`data: Buffer`) with no name of its own carried
|
|
6428
|
+
// in `PlumType::TStr` — resolve it via the literal name "Str", same as a
|
|
6429
|
+
// `TNamed`/`TApp` does via its own name.
|
|
6430
|
+
let class_name = match (plum_checker::nominalName(obj_ty), obj_ty) {
|
|
6431
|
+
(Some(n), _) => n.to_string(),
|
|
6432
|
+
(None, PlumType::TStr) => "Str".to_string(),
|
|
6433
|
+
_ => {
|
|
6434
|
+
return Err(format!(
|
|
6435
|
+
"codegen: cannot access field '{}' on non-class type {}",
|
|
6436
|
+
field_name, obj_ty
|
|
6437
|
+
))
|
|
6438
|
+
}
|
|
6439
|
+
};
|
|
6440
|
+
let want = plumTypeToValtype(&inferLocalType(expr, ctx));
|
|
6441
|
+
// A discriminant enum's shared params are declared directly on the enum's
|
|
6442
|
+
// SUPERTYPE (see `buildGcTypeRegistry`'s `EnumSuper` arm) — no `ref.cast` to
|
|
6443
|
+
// any one variant needed.
|
|
6444
|
+
if let Some(params) = ctx.enum_params.get(&class_name) {
|
|
6445
|
+
let field_idx = params
|
|
6446
|
+
.iter()
|
|
6447
|
+
.position(|(n, _)| n == field_name)
|
|
6448
|
+
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
6449
|
+
let super_type_idx = *ctx
|
|
6450
|
+
.gc_types
|
|
6451
|
+
.enum_super_type_idx
|
|
6452
|
+
.get(&class_name)
|
|
6453
|
+
.ok_or_else(|| {
|
|
6454
|
+
format!("codegen: enum '{}' missing from the GC type registry", class_name)
|
|
6455
|
+
})?;
|
|
6456
|
+
compileExpr(object, body, ctx, state)?;
|
|
6457
|
+
Instruction::StructGet {
|
|
6458
|
+
struct_type_index: super_type_idx,
|
|
6459
|
+
field_index: fld(field_idx),
|
|
6460
|
+
}
|
|
6461
|
+
.encode(body);
|
|
6462
|
+
coerceVt(
|
|
6463
|
+
plumTypeToValtype(¶ms[field_idx].1),
|
|
6464
|
+
want,
|
|
6465
|
+
body,
|
|
6466
|
+
ctx.gc_types,
|
|
6467
|
+
);
|
|
6468
|
+
return Ok(());
|
|
6469
|
+
}
|
|
6470
|
+
// Not a discriminant enum: `class_name` is an ordinary enum, and the checker
|
|
6471
|
+
// already verified exactly ONE of its variants declares a field with this name.
|
|
6472
|
+
// Compile `.field` as a checked downcast to that one variant's own struct type,
|
|
6473
|
+
// then read the field off it — traps at runtime if the value turns out to be a
|
|
6474
|
+
// different variant.
|
|
6475
|
+
let (variant_name, info) = ctx
|
|
6476
|
+
.enum_variants
|
|
6477
|
+
.iter()
|
|
6478
|
+
.find(|(_, info)| {
|
|
6479
|
+
info.enum_name == class_name && info.field_names.iter().any(|n| n == field_name)
|
|
6480
|
+
})
|
|
6481
|
+
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
6482
|
+
let field_idx = info
|
|
6483
|
+
.field_names
|
|
6484
|
+
.iter()
|
|
6485
|
+
.position(|n| n == field_name)
|
|
6486
|
+
.expect("just found by this field name");
|
|
6487
|
+
let variant_type_idx = *ctx
|
|
6488
|
+
.gc_types
|
|
6489
|
+
.variant_type_idx
|
|
6490
|
+
.get(variant_name)
|
|
6491
|
+
.ok_or_else(|| {
|
|
6492
|
+
format!(
|
|
6493
|
+
"codegen: variant '{}' missing from the GC type registry",
|
|
6494
|
+
variant_name
|
|
6495
|
+
)
|
|
6496
|
+
})?;
|
|
6497
|
+
compileExpr(object, body, ctx, state)?;
|
|
6498
|
+
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx)).encode(body);
|
|
6499
|
+
Instruction::StructGet {
|
|
6500
|
+
struct_type_index: variant_type_idx,
|
|
6501
|
+
field_index: fld(field_idx),
|
|
6502
|
+
}
|
|
6503
|
+
.encode(body);
|
|
6504
|
+
coerceVt(
|
|
6505
|
+
plumTypeToValtype(&info.field_types[field_idx]),
|
|
6506
|
+
want,
|
|
6507
|
+
body,
|
|
6508
|
+
ctx.gc_types,
|
|
6509
|
+
);
|
|
6510
|
+
Ok(())
|
|
6511
|
+
}
|
|
6512
|
+
|
|
6513
|
+
/// Resolves a method call's receiver name to the function that implements it:
|
|
6514
|
+
/// the receiver's own `Name::method`, or — for a primitive/class receiver whose
|
|
6515
|
+
/// method is defined once on the "union" enum that bare-wraps it
|
|
6516
|
+
/// (`enum Number = | Int | Float`) — that enum's method together with the wrapping
|
|
6517
|
+
/// variant the raw `self` must be boxed into first.
|
|
6518
|
+
fn resolveMethodKey(class_name: &str, method: &str, ctx: &LocalCtx) -> (String, String, Option<String>) {
|
|
6519
|
+
let direct_key = format!("{}::{}", class_name, method);
|
|
6520
|
+
if !ctx.func_ids.contains_key(&direct_key) {
|
|
6521
|
+
if let Some(info) = ctx.enum_variants.get(class_name) {
|
|
6522
|
+
let wrap_key = format!("{}::{}", info.enum_name, method);
|
|
6523
|
+
if ctx.func_ids.contains_key(&wrap_key) {
|
|
6524
|
+
return (info.enum_name.clone(), wrap_key, Some(class_name.to_string()));
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
(class_name.to_string(), direct_key, None)
|
|
6529
|
+
}
|
|
6530
|
+
|
|
6531
|
+
/// The trait that declares `method` (with `arity` params) and has a `$dyn$` dispatcher,
|
|
6532
|
+
/// for a call on an erased (`TParam`-typed) receiver whose bound traits the codegen
|
|
6533
|
+
/// does not otherwise track.
|
|
6534
|
+
fn dynOwnerForMethod(method: &str, arity: usize, ctx: &LocalCtx) -> Option<String> {
|
|
6535
|
+
let mut owners: Vec<&String> = ctx
|
|
6536
|
+
.gc_types
|
|
6537
|
+
.dyn_fns
|
|
6538
|
+
.keys()
|
|
6539
|
+
.filter(|(_, m)| m == method)
|
|
6540
|
+
.map(|(t, _)| t)
|
|
6541
|
+
.filter(|t| {
|
|
6542
|
+
matches!(
|
|
6543
|
+
ctx.methods.get(&((*t).clone(), method.to_string())),
|
|
6544
|
+
Some(PlumType::TFun(ps, _)) if ps.len() == arity
|
|
6545
|
+
)
|
|
6546
|
+
})
|
|
6547
|
+
.collect();
|
|
6548
|
+
owners.sort();
|
|
6549
|
+
owners.first().map(|t| (*t).clone())
|
|
6550
|
+
}
|
|
6551
|
+
|
|
6552
|
+
/// Compiles `object.method(args)`. `obj_ty` is the receiver's static type and
|
|
6553
|
+
/// `result_expr` the expression whose recorded type is the call's instantiated
|
|
6554
|
+
/// result type (a desugared `Type(...)` -> `Type.init(...)` passes the original
|
|
6555
|
+
/// call expression).
|
|
6556
|
+
fn compileMethodCall(
|
|
6557
|
+
object: &ast::Expr,
|
|
6558
|
+
call: &ast::FnCall,
|
|
6559
|
+
obj_ty: &PlumType,
|
|
6560
|
+
result_expr: &ast::Expr,
|
|
6561
|
+
body: &mut Vec<u8>,
|
|
6562
|
+
ctx: &LocalCtx,
|
|
6563
|
+
state: &mut ModuleState,
|
|
6564
|
+
) -> Result<(), String> {
|
|
6565
|
+
// A method on an erased generic parameter (`K: Hashable` -> `key.hash()`)
|
|
6566
|
+
// dispatches dynamically through the declaring trait's `$dyn$` dispatcher.
|
|
6567
|
+
if matches!(obj_ty, PlumType::TParam(_)) {
|
|
6568
|
+
let owner = dynOwnerForMethod(&call.name, call.args.len(), ctx).ok_or_else(|| {
|
|
6569
|
+
format!(
|
|
6570
|
+
"codegen: '{}' has no method '{}' (add a bound)",
|
|
6571
|
+
obj_ty, call.name
|
|
6572
|
+
)
|
|
6573
|
+
})?;
|
|
6574
|
+
return compileDynCall(&owner, object, call, &inferLocalType(result_expr, ctx), body, ctx, state);
|
|
6575
|
+
}
|
|
6576
|
+
let class_name = match plum_checker::methodReceiverName(obj_ty) {
|
|
6577
|
+
Some(n) => n,
|
|
6578
|
+
None => {
|
|
6579
|
+
return Err(format!(
|
|
6580
|
+
"codegen: cannot call method '{}' on non-class type {}",
|
|
6581
|
+
call.name, obj_ty
|
|
6582
|
+
))
|
|
6583
|
+
}
|
|
6584
|
+
};
|
|
6585
|
+
// A method call on a trait-typed value goes through the trait's
|
|
6586
|
+
// `$dyn$<Trait>$<method>` dispatcher (reads the receiver's typeId).
|
|
6587
|
+
let is_trait_recv = ctx.gc_types.class_type_idx.get(&class_name)
|
|
6588
|
+
== Some(&ctx.gc_types.obj_type_idx)
|
|
6589
|
+
&& ctx
|
|
6590
|
+
.methods
|
|
6591
|
+
.contains_key(&(class_name.clone(), plum_checker::TRAIT_MARKER.to_string()));
|
|
6592
|
+
if is_trait_recv {
|
|
6593
|
+
// A method the receiver's own trait doesn't declare dispatches through
|
|
6594
|
+
// whichever trait does.
|
|
6595
|
+
let owner = if ctx
|
|
6596
|
+
.gc_types
|
|
6597
|
+
.dyn_fns
|
|
6598
|
+
.contains_key(&(class_name.clone(), call.name.clone()))
|
|
6599
|
+
{
|
|
6600
|
+
Some(class_name.clone())
|
|
6601
|
+
} else {
|
|
6602
|
+
dynOwnerForMethod(&call.name, call.args.len(), ctx)
|
|
6603
|
+
};
|
|
6604
|
+
return match owner {
|
|
6605
|
+
Some(owner) => compileDynCall(&owner, object, call, &inferLocalType(result_expr, ctx), body, ctx, state),
|
|
6606
|
+
None => {
|
|
6607
|
+
// The trait declares the method (the checker rejects any other) but
|
|
6608
|
+
// no type implements it: unreachable at runtime, but the call must
|
|
6609
|
+
// still be valid wasm.
|
|
6610
|
+
Instruction::Unreachable.encode(body);
|
|
6611
|
+
Ok(())
|
|
6612
|
+
}
|
|
6613
|
+
};
|
|
6614
|
+
}
|
|
6615
|
+
|
|
6616
|
+
let (dispatch_name, key, box_variant) = resolveMethodKey(&class_name, &call.name, ctx);
|
|
6617
|
+
let func_idx = *ctx
|
|
6618
|
+
.func_ids
|
|
6619
|
+
.get(&key)
|
|
6620
|
+
.ok_or_else(|| format!("codegen: unknown method '{}.{}'", class_name, call.name))?;
|
|
6621
|
+
// A "static"-style call (`Bool.parse("true")`, `Float.fromStr("3.14")`) on a
|
|
6622
|
+
// self-less method declared inside a `type`/`enum` body — its receiver is a
|
|
6623
|
+
// bare reference to the type's own name, not a real value. Every method still
|
|
6624
|
+
// reserves a leading self slot in its wasm signature regardless of whether its
|
|
6625
|
+
// Plum source declares a `self` param (see `fnWasmParamTypes`), so something of
|
|
6626
|
+
// the right type must still be pushed — the body simply never reads it.
|
|
6627
|
+
let is_static_call = matches!(object, ast::Expr::TypeName(n) if *n == class_name);
|
|
6628
|
+
if is_static_call {
|
|
6629
|
+
let vt = astTypeToWasm(&dispatch_name).unwrap_or(ValType::I32);
|
|
6630
|
+
pushSelfPlaceholder(vt, body);
|
|
6631
|
+
} else {
|
|
6632
|
+
if let Some(variant) = &box_variant {
|
|
6633
|
+
// The wrap-enum variant's header typeId goes below its payload.
|
|
6634
|
+
Instruction::I32Const(typeIdOf(ctx.gc_types, variant)).encode(body);
|
|
6635
|
+
}
|
|
6636
|
+
compileExpr(object, body, ctx, state)?; // push self
|
|
6637
|
+
if let Some(variant) = &box_variant {
|
|
6638
|
+
// Dispatching to the wrap-enum's method — its `self` param is typed as
|
|
6639
|
+
// the ENUM (`Number`), a boxed GC struct, not the bare primitive just pushed.
|
|
6640
|
+
let variant_idx = *ctx.gc_types.variant_type_idx.get(variant).ok_or_else(|| {
|
|
6641
|
+
format!("codegen: variant '{}' missing from the GC type registry", variant)
|
|
6642
|
+
})?;
|
|
6643
|
+
Instruction::StructNew(variant_idx).encode(body);
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
let sig = ctx
|
|
6647
|
+
.func_sigs
|
|
6648
|
+
.get(&key)
|
|
6649
|
+
.ok_or_else(|| format!("codegen: unknown method '{}.{}'", class_name, call.name))?;
|
|
6650
|
+
compileCallArgs(&key, &call.args, sig, body, ctx, state)?;
|
|
6651
|
+
Instruction::Call(func_idx).encode(body);
|
|
6652
|
+
coerceCallResult(&sig.decl_ret, result_expr, body, ctx);
|
|
6653
|
+
Ok(())
|
|
6654
|
+
}
|
|
6655
|
+
|
|
6656
|
+
/// A call through `owner`'s `$dyn$<owner>$<method>` dispatcher: the receiver (any
|
|
6657
|
+
/// value, converted to the erased `$Obj` representation — a primitive is boxed)
|
|
6658
|
+
/// followed by the arguments at the trait method's declared param types.
|
|
6659
|
+
fn compileDynCall(
|
|
6660
|
+
owner: &str,
|
|
6661
|
+
object: &ast::Expr,
|
|
6662
|
+
call: &ast::FnCall,
|
|
6663
|
+
want: &PlumType,
|
|
6664
|
+
body: &mut Vec<u8>,
|
|
6665
|
+
ctx: &LocalCtx,
|
|
6666
|
+
state: &mut ModuleState,
|
|
6667
|
+
) -> Result<(), String> {
|
|
6668
|
+
let dyn_idx = *ctx
|
|
6669
|
+
.gc_types
|
|
6670
|
+
.dyn_fns
|
|
6671
|
+
.get(&(owner.to_string(), call.name.clone()))
|
|
6672
|
+
.ok_or_else(|| format!("codegen: no dispatcher for '{}.{}'", owner, call.name))?;
|
|
6673
|
+
let (param_tys, ret_ty): (Vec<PlumType>, PlumType) =
|
|
6674
|
+
match ctx.methods.get(&(owner.to_string(), call.name.clone())) {
|
|
6675
|
+
Some(PlumType::TFun(ps, r)) => (ps.clone(), (**r).clone()),
|
|
6676
|
+
_ => (Vec::new(), PlumType::TUnit),
|
|
6677
|
+
};
|
|
6678
|
+
let obj_vt = ValType::Ref(RefType {
|
|
6679
|
+
nullable: true,
|
|
6680
|
+
heap_type: HeapType::Concrete(ctx.gc_types.obj_type_idx),
|
|
6681
|
+
});
|
|
6682
|
+
compileExprAs(object, Some(obj_vt), body, ctx, state)?;
|
|
6683
|
+
for (i, arg) in call.args.iter().enumerate() {
|
|
6684
|
+
match param_tys.get(i) {
|
|
6685
|
+
Some(d) => compileExprInto(argExprOf(arg), d, body, ctx, state)?,
|
|
6686
|
+
None => compileExpr(argExprOf(arg), body, ctx, state)?,
|
|
6687
|
+
}
|
|
6688
|
+
}
|
|
6689
|
+
Instruction::Call(dyn_idx).encode(body);
|
|
6690
|
+
coerceCallResultTo(&ret_ty, want, body, ctx);
|
|
6691
|
+
Ok(())
|
|
6692
|
+
}
|
|
6693
|
+
|
|
6771
6694
|
/// Compiles a variant-construction call. A payload-free variant (`End`, called as
|
|
6772
6695
|
/// `End()` rather than used bare) is its pre-allocated singleton global. A payload
|
|
6773
6696
|
/// variant pushes its field values in order and does `struct.new` into its own
|
|
@@ -6790,14 +6713,17 @@ fn compileDefaultArgs(
|
|
|
6790
6713
|
let Some(sig) = ctx.func_sigs.get(func_key) else {
|
|
6791
6714
|
return Ok(());
|
|
6792
6715
|
};
|
|
6793
|
-
for default in sig.param_defaults.iter().skip(supplied_count) {
|
|
6716
|
+
for (i, default) in sig.param_defaults.iter().enumerate().skip(supplied_count) {
|
|
6794
6717
|
let default_expr = default.as_ref().ok_or_else(|| {
|
|
6795
6718
|
format!(
|
|
6796
6719
|
"codegen: call '{}' is missing a required argument (no default declared)",
|
|
6797
6720
|
func_key
|
|
6798
6721
|
)
|
|
6799
6722
|
})?;
|
|
6723
|
+
match sig.decl_params.get(i) {
|
|
6724
|
+
Some(d) => compileExprInto(default_expr, d, body, ctx, state)?,
|
|
6800
|
-
|
|
6725
|
+
None => compileExpr(default_expr, body, ctx, state)?,
|
|
6726
|
+
}
|
|
6801
6727
|
}
|
|
6802
6728
|
Ok(())
|
|
6803
6729
|
}
|
|
@@ -6852,13 +6778,10 @@ fn compileVariantConstruction(
|
|
|
6852
6778
|
ast::Arg::Keyword { value, .. } => value,
|
|
6853
6779
|
ast::Arg::Pair { value, .. } => value,
|
|
6854
6780
|
};
|
|
6855
|
-
compileExprAs(
|
|
6856
|
-
arg_expr,
|
|
6857
|
-
|
|
6781
|
+
match info.field_types.get(i) {
|
|
6858
|
-
body,
|
|
6782
|
+
Some(d) => compileExprInto(arg_expr, d, body, ctx, state)?,
|
|
6859
|
-
ctx,
|
|
6860
|
-
state,
|
|
6783
|
+
None => compileExpr(arg_expr, body, ctx, state)?,
|
|
6861
|
-
|
|
6784
|
+
}
|
|
6862
6785
|
}
|
|
6863
6786
|
Instruction::StructNew(variant_type_idx).encode(body);
|
|
6864
6787
|
Ok(())
|
|
@@ -6944,10 +6867,35 @@ fn compileInterpolatedString(
|
|
|
6944
6867
|
Instruction::Call(func_idx).encode(body);
|
|
6945
6868
|
}
|
|
6946
6869
|
other => {
|
|
6870
|
+
// An erased (`T`) or trait-typed value dispatches `toStr`
|
|
6871
|
+
// dynamically; a class/enum with its own `toStr` calls it.
|
|
6872
|
+
let to_str = ast::FnCall {
|
|
6873
|
+
name: "toStr".to_string(),
|
|
6874
|
+
args: vec![],
|
|
6875
|
+
};
|
|
6876
|
+
let str_ty = PlumType::TStr;
|
|
6877
|
+
let is_dyn = match &other {
|
|
6878
|
+
PlumType::TParam(_) => true,
|
|
6879
|
+
t => plum_checker::nominalName(t).is_some_and(|n| {
|
|
6880
|
+
ctx.gc_types.class_type_idx.get(n)
|
|
6881
|
+
== Some(&ctx.gc_types.obj_type_idx)
|
|
6882
|
+
}),
|
|
6883
|
+
};
|
|
6884
|
+
if is_dyn {
|
|
6885
|
+
let owner = dynOwnerForMethod("toStr", 0, ctx).ok_or_else(|| {
|
|
6886
|
+
format!("codegen: interpolating a value of type {} requires a `toStr` method", other)
|
|
6887
|
+
})?;
|
|
6888
|
+
compileDynCall(&owner, expr, &to_str, &str_ty, body, ctx, state)?;
|
|
6889
|
+
} else if plum_checker::nominalName(&other)
|
|
6890
|
+
.is_some_and(|n| ctx.func_ids.contains_key(&format!("{}::toStr", n)))
|
|
6891
|
+
{
|
|
6892
|
+
compileMethodCall(expr, &to_str, &other, expr, body, ctx, state)?;
|
|
6893
|
+
} else {
|
|
6947
|
-
|
|
6894
|
+
return Err(format!(
|
|
6948
|
-
|
|
6895
|
+
"codegen: interpolating a value of type {} is not yet supported",
|
|
6949
|
-
|
|
6896
|
+
other
|
|
6950
|
-
|
|
6897
|
+
));
|
|
6898
|
+
}
|
|
6951
6899
|
}
|
|
6952
6900
|
}
|
|
6953
6901
|
}
|
|
@@ -6980,7 +6928,7 @@ fn compileClosureLiteral(
|
|
|
6980
6928
|
// env struct (snapshotting each captured free variable's current value from the
|
|
6981
6929
|
// enclosing function's local), then wrap both into the shared closure struct.
|
|
6982
6930
|
Instruction::I32Const(info.table_idx as i32).encode(body);
|
|
6983
|
-
for (name,
|
|
6931
|
+
for (name, cap_ty) in &info.free_vars {
|
|
6984
6932
|
let local_idx = *ctx.locals.get(name).ok_or_else(|| {
|
|
6985
6933
|
format!(
|
|
6986
6934
|
"codegen: captured variable '{}' is not a local in the enclosing scope",
|
|
@@ -6988,20 +6936,30 @@ fn compileClosureLiteral(
|
|
|
6988
6936
|
)
|
|
6989
6937
|
})?;
|
|
6990
6938
|
Instruction::LocalGet(local_idx).encode(body);
|
|
6939
|
+
let have = ctx
|
|
6940
|
+
.local_decl_types
|
|
6941
|
+
.get(name)
|
|
6942
|
+
.map(plumTypeToValtype)
|
|
6943
|
+
.unwrap_or_else(|| plumTypeToValtype(cap_ty));
|
|
6944
|
+
coerceVt(have, plumTypeToValtype(cap_ty), body, ctx.gc_types);
|
|
6991
6945
|
}
|
|
6992
6946
|
Instruction::StructNew(info.env_type_idx).encode(body);
|
|
6993
6947
|
Instruction::StructNew(ctx.gc_types.closure_type_idx).encode(body);
|
|
6994
6948
|
Ok(())
|
|
6995
6949
|
}
|
|
6996
6950
|
|
|
6997
|
-
/// Compiles a call to a closure-typed local via `call_indirect`
|
|
6951
|
+
/// Compiles a call to a closure-typed local via `call_indirect`, using the erased
|
|
6952
|
+
/// closure ABI (`ClosureSigKey`): every argument is converted to `anyref`
|
|
6953
|
+
/// (primitives boxed), the result is converted from `anyref` to the call's own
|
|
6954
|
+
/// instantiated type (or dropped when the closure's declared result is `Unit`).
|
|
6998
|
-
///
|
|
6955
|
+
/// The local is statically `anyref` (closures share that placeholder type — see
|
|
6999
6956
|
/// `plumTypeToValtype`), so every read of its fields `ref.cast`s down to the shared
|
|
7000
6957
|
/// concrete closure struct type first. Stack order matches the closure function's
|
|
7001
6958
|
/// signature `(env, ...args)`: push the env, then each argument, then the table
|
|
7002
6959
|
/// index (the `call_indirect` operand).
|
|
7003
6960
|
fn compileClosureCall(
|
|
7004
6961
|
call: &ast::FnCall,
|
|
6962
|
+
call_expr: &ast::Expr,
|
|
7005
6963
|
body: &mut Vec<u8>,
|
|
7006
6964
|
ctx: &LocalCtx,
|
|
7007
6965
|
state: &mut ModuleState,
|
|
@@ -7011,41 +6969,14 @@ fn compileClosureCall(
|
|
|
7011
6969
|
.get(&call.name)
|
|
7012
6970
|
.ok_or_else(|| format!("codegen: closure '{}' is not a local", call.name))?;
|
|
7013
6971
|
let closure_type_idx = ctx.gc_types.closure_type_idx;
|
|
7014
|
-
|
|
7015
|
-
// Prefer the exact signature recorded when this local was assigned a closure
|
|
7016
|
-
// *literal* (see `closure_local_sigs`) — it's already correct. Otherwise (e.g.
|
|
7017
|
-
// `call.name` is a `fn(...)`-typed parameter, whose declared type is reliable on
|
|
7018
|
-
// its own) fall back to re-deriving it from the type env.
|
|
7019
|
-
let sig_key: ClosureSigKey = match ctx.closure_local_sigs.borrow().get(&call.name) {
|
|
7020
|
-
Some(key) => key.clone(),
|
|
7021
|
-
None => {
|
|
7022
|
-
let (param_ptypes, ret_ptype) =
|
|
7023
|
-
match inferLocalType(&ast::Expr::Var(call.name.clone()), ctx) {
|
|
7024
|
-
PlumType::TFun(p, r) => (p, *r),
|
|
7025
|
-
other => {
|
|
7026
|
-
return Err(format!(
|
|
7027
|
-
"codegen: '{}' is not callable (type {:?})",
|
|
7028
|
-
call.name, other
|
|
7029
|
-
))
|
|
7030
|
-
}
|
|
7031
|
-
};
|
|
7032
|
-
let mut sig_params = vec![ValType::Ref(RefType::ANYREF)]; // env pointer
|
|
7033
|
-
for p in ¶m_ptypes {
|
|
7034
|
-
sig_params.push(plumTypeToValtype(p));
|
|
7035
|
-
}
|
|
7036
|
-
let ret_vt = match ret_ptype {
|
|
7037
|
-
PlumType::TUnit => None,
|
|
7038
|
-
other => Some(plumTypeToValtype(&other)),
|
|
7039
|
-
};
|
|
7040
|
-
(sig_params, ret_vt)
|
|
7041
|
-
}
|
|
7042
|
-
};
|
|
7043
|
-
let type_index = *ctx.closure_call_types.get(&
|
|
6972
|
+
let type_index = *ctx.closure_call_types.get(&call.args.len()).ok_or_else(|| {
|
|
7044
6973
|
format!(
|
|
7045
|
-
"internal codegen error: no call_indirect type for closure '{}'",
|
|
6974
|
+
"internal codegen error: no call_indirect type for closure '{}' with {} args",
|
|
7046
|
-
call.name
|
|
6975
|
+
call.name,
|
|
6976
|
+
call.args.len()
|
|
7047
6977
|
)
|
|
7048
6978
|
})?;
|
|
6979
|
+
let any = ValType::Ref(RefType::ANYREF);
|
|
7049
6980
|
|
|
7050
6981
|
// env (closure struct field 1)
|
|
7051
6982
|
Instruction::LocalGet(closure_local).encode(body);
|
|
@@ -7055,9 +6986,9 @@ fn compileClosureCall(
|
|
|
7055
6986
|
field_index: 1,
|
|
7056
6987
|
}
|
|
7057
6988
|
.encode(body);
|
|
7058
|
-
// real arguments
|
|
6989
|
+
// real arguments, boxed to the erased ABI
|
|
7059
6990
|
for arg in &call.args {
|
|
7060
|
-
|
|
6991
|
+
compileExprAs(argExprOf(arg), Some(any), body, ctx, state)?;
|
|
7061
6992
|
}
|
|
7062
6993
|
// table index (closure struct field 0) — the call_indirect operand
|
|
7063
6994
|
Instruction::LocalGet(closure_local).encode(body);
|
|
@@ -7072,12 +7003,36 @@ fn compileClosureCall(
|
|
|
7072
7003
|
table_index: 0,
|
|
7073
7004
|
}
|
|
7074
7005
|
.encode(body);
|
|
7006
|
+
// The result: `Unit` closures produce a null anyref that is dropped here, so
|
|
7007
|
+
// a Unit-typed call leaves nothing on the stack (see `exprHasResult`).
|
|
7008
|
+
if closureCallReturnsUnit(&call.name, ctx) {
|
|
7009
|
+
Instruction::Drop.encode(body);
|
|
7010
|
+
} else {
|
|
7011
|
+
let want = plumTypeToValtype(&inferLocalType(call_expr, ctx));
|
|
7012
|
+
coerceVt(any, want, body, ctx.gc_types);
|
|
7013
|
+
}
|
|
7075
7014
|
Ok(())
|
|
7076
7015
|
}
|
|
7077
7016
|
|
|
7017
|
+
/// Whether the closure-typed local `name` is declared to return `Unit`.
|
|
7018
|
+
fn closureCallReturnsUnit(name: &str, ctx: &LocalCtx) -> bool {
|
|
7019
|
+
match ctx.local_decl_types.get(name) {
|
|
7020
|
+
Some(PlumType::TFun(_, ret)) => matches!(**ret, PlumType::TUnit),
|
|
7021
|
+
_ => match inferLocalType(&ast::Expr::Var(name.to_string()), ctx) {
|
|
7022
|
+
PlumType::TFun(_, ret) => matches!(*ret, PlumType::TUnit),
|
|
7023
|
+
_ => false,
|
|
7024
|
+
},
|
|
7025
|
+
}
|
|
7026
|
+
}
|
|
7027
|
+
|
|
7078
|
-
/// Compiles a closure literal's own body into a standalone wasm function
|
|
7028
|
+
/// Compiles a closure literal's own body into a standalone wasm function with the
|
|
7029
|
+
/// erased closure ABI `(env: anyref, a1: anyref, ..., an: anyref) -> anyref`
|
|
7030
|
+
/// (see `ClosureSigKey`). Local 0 is the env pointer; locals `1..=n` are the raw
|
|
7079
|
-
///
|
|
7031
|
+
/// incoming params; typed locals follow — one per closure param (holding the
|
|
7032
|
+
/// raw param unboxed/cast to its real Plum type by the prologue), one per
|
|
7080
|
-
///
|
|
7033
|
+
/// captured free variable (restored from the env struct), then the body's own
|
|
7034
|
+
/// named locals and scratch slots. The tail value is boxed back to `anyref` (a
|
|
7035
|
+
/// `Unit` body yields a null `anyref`).
|
|
7081
7036
|
fn compileClosureBody(
|
|
7082
7037
|
cl: &ast::Closure,
|
|
7083
7038
|
info: &ClosureInfo,
|
|
@@ -7086,23 +7041,21 @@ fn compileClosureBody(
|
|
|
7086
7041
|
) -> Result<Vec<u8>, String> {
|
|
7087
7042
|
let mut body = Vec::new();
|
|
7088
7043
|
|
|
7089
|
-
//
|
|
7044
|
+
// Fallback type env (synthesized nodes only) + declared local types.
|
|
7090
7045
|
let mut base_env = ctx.global_env.clone();
|
|
7046
|
+
let mut decl_types: HashMap<String, PlumType> = HashMap::new();
|
|
7091
7047
|
for (name, ty) in &info.free_vars {
|
|
7092
7048
|
base_env.insert(name.clone(), TypeScheme::mono(ty.clone()));
|
|
7049
|
+
decl_types.insert(name.clone(), ty.clone());
|
|
7093
7050
|
}
|
|
7094
7051
|
for (name, pty) in cl.params.iter().zip(info.param_ptypes.iter()) {
|
|
7095
7052
|
base_env.insert(name.clone(), TypeScheme::mono(pty.clone()));
|
|
7053
|
+
decl_types.insert(name.clone(), pty.clone());
|
|
7096
7054
|
}
|
|
7097
7055
|
|
|
7098
7056
|
let mut collector = Collector {
|
|
7099
|
-
env: base_env.clone(),
|
|
7100
|
-
cctx: checkCtxOf(
|
|
7101
|
-
|
|
7057
|
+
types: &ctx.types,
|
|
7102
|
-
|
|
7058
|
+
enum_variants: &ctx.enum_variants,
|
|
7103
|
-
&ctx.enum_params,
|
|
7104
|
-
&ctx.min_required,
|
|
7105
|
-
),
|
|
7106
7059
|
named: Vec::new(),
|
|
7107
7060
|
named_set: Default::default(),
|
|
7108
7061
|
match_scratch: HashMap::new(),
|
|
@@ -7115,14 +7068,18 @@ fn compileClosureBody(
|
|
|
7115
7068
|
collector.walkBlock(&cl.body);
|
|
7116
7069
|
|
|
7117
7070
|
// ---- local index layout ----
|
|
7118
|
-
// [env_ptr][
|
|
7071
|
+
// [env_ptr][raw params][typed params][free-var locals][named...][match][scratch]
|
|
7119
7072
|
let mut locals: HashMap<String, u32> = HashMap::new();
|
|
7120
7073
|
let mut groups: Vec<ValType> = Vec::new();
|
|
7121
7074
|
let mut idx = 0u32;
|
|
7122
7075
|
|
|
7123
|
-
idx += 1; //
|
|
7076
|
+
idx += 1 + cl.params.len() as u32; // env + raw params (all in the signature)
|
|
7077
|
+
let mut param_prologue: Vec<(u32, u32, ValType)> = Vec::new(); // (raw, typed, vt)
|
|
7078
|
+
for (i, (name, pty)) in cl.params.iter().zip(info.param_ptypes.iter()).enumerate() {
|
|
7124
|
-
|
|
7079
|
+
let vt = plumTypeToValtype(pty);
|
|
7125
7080
|
locals.insert(name.clone(), idx);
|
|
7081
|
+
groups.push(vt);
|
|
7082
|
+
param_prologue.push((i as u32 + 1, idx, vt));
|
|
7126
7083
|
idx += 1;
|
|
7127
7084
|
}
|
|
7128
7085
|
for (name, ty) in &info.free_vars {
|
|
@@ -7131,6 +7088,7 @@ fn compileClosureBody(
|
|
|
7131
7088
|
idx += 1;
|
|
7132
7089
|
}
|
|
7133
7090
|
for (name, ty) in &collector.named {
|
|
7091
|
+
decl_types.entry(name.clone()).or_insert_with(|| ty.clone());
|
|
7134
7092
|
if locals.contains_key(name) {
|
|
7135
7093
|
continue;
|
|
7136
7094
|
}
|
|
@@ -7150,10 +7108,6 @@ fn compileClosureBody(
|
|
|
7150
7108
|
}
|
|
7151
7109
|
|
|
7152
7110
|
let nested_class_scratch_base = idx;
|
|
7153
|
-
// Each slot is declared with its OWN concrete variant ref type (not a uniform
|
|
7154
|
-
// placeholder) — `struct.get` on a constructor-pattern match requires the local
|
|
7155
|
-
// holding the narrowed (`ref.cast`) value to be statically typed as that exact
|
|
7156
|
-
// variant, and different slots very likely narrow to different variants.
|
|
7157
7111
|
for vname in &collector.nested_class_scratch_types {
|
|
7158
7112
|
let variant_idx = withGcTypes(|r| classOrVariantTypeIdx(r, vname));
|
|
7159
7113
|
groups.push(gcRef(variant_idx));
|
|
@@ -7184,6 +7138,12 @@ fn compileClosureBody(
|
|
|
7184
7138
|
}
|
|
7185
7139
|
}
|
|
7186
7140
|
|
|
7141
|
+
// Prologue: convert each raw `anyref` param to its real representation.
|
|
7142
|
+
for (raw, typed, vt) in ¶m_prologue {
|
|
7143
|
+
Instruction::LocalGet(*raw).encode(&mut body);
|
|
7144
|
+
coerceVt(ValType::Ref(RefType::ANYREF), *vt, &mut body, &ctx.gc_types);
|
|
7145
|
+
Instruction::LocalSet(*typed).encode(&mut body);
|
|
7146
|
+
}
|
|
7187
7147
|
// Restore each captured free variable from the env struct (local 0, statically
|
|
7188
7148
|
// `anyref` — `ref.cast` down to THIS closure's own concrete env type) at entry.
|
|
7189
7149
|
for (i, (name, _)) in info.free_vars.iter().enumerate() {
|
|
@@ -7208,11 +7168,14 @@ fn compileClosureBody(
|
|
|
7208
7168
|
variadic_for_scratch: collector.variadic_for_scratch,
|
|
7209
7169
|
func_ids: &ctx.func_ids,
|
|
7210
7170
|
func_sigs: &ctx.func_sigs,
|
|
7171
|
+
types: &ctx.types,
|
|
7172
|
+
local_decl_types: decl_types,
|
|
7211
7173
|
closures: &ctx.closures,
|
|
7212
7174
|
closure_call_types: &ctx.closure_call_types,
|
|
7213
7175
|
named_fn_values: &ctx.named_fn_values,
|
|
7214
7176
|
string_concat_func: ctx.string_concat_func,
|
|
7215
7177
|
string_eq_func: ctx.string_eq_func,
|
|
7178
|
+
dyn_eq_func: ctx.dyn_eq_func,
|
|
7216
7179
|
int_to_string_func: ctx.int_to_string_func,
|
|
7217
7180
|
methods: &ctx.methods,
|
|
7218
7181
|
enum_variants: &ctx.enum_variants,
|
|
@@ -7221,13 +7184,22 @@ fn compileClosureBody(
|
|
|
7221
7184
|
gc_types: &ctx.gc_types,
|
|
7222
7185
|
singleton_globals: &ctx.singleton_globals,
|
|
7223
7186
|
type_env: RefCell::new(base_env),
|
|
7224
|
-
closure_local_sigs: RefCell::new(HashMap::new()),
|
|
7225
7187
|
loop_extra_depth: Cell::new(0),
|
|
7226
|
-
|
|
7188
|
+
in_closure: true,
|
|
7189
|
+
ret_type: Some(info.ret_ptype.clone()),
|
|
7227
7190
|
match_subject_types: RefCell::new(HashMap::new()),
|
|
7191
|
+
subject_ptypes: RefCell::new(HashMap::new()),
|
|
7228
7192
|
};
|
|
7229
7193
|
|
|
7194
|
+
let any = ValType::Ref(RefType::ANYREF);
|
|
7195
|
+
if matches!(info.ret_ptype, PlumType::TUnit) {
|
|
7196
|
+
compileBlockAsFnBody(&cl.body, &mut body, &local_ctx, state, None)?;
|
|
7197
|
+
Instruction::RefNull(HeapType::ANY).encode(&mut body);
|
|
7198
|
+
} else {
|
|
7199
|
+
let ret_vt = plumTypeToValtype(&info.ret_ptype);
|
|
7230
|
-
|
|
7200
|
+
compileBlockAsFnBody(&cl.body, &mut body, &local_ctx, state, Some(ret_vt))?;
|
|
7201
|
+
coerceVt(ret_vt, any, &mut body, &ctx.gc_types);
|
|
7202
|
+
}
|
|
7231
7203
|
|
|
7232
7204
|
Instruction::End.encode(&mut body);
|
|
7233
7205
|
Ok(body)
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -194,6 +194,9 @@ fn assertValid(src: &str) -> Vec<u8> {
|
|
|
194
194
|
let source = parse(src);
|
|
195
195
|
let bytes =
|
|
196
196
|
compileSource(&source).unwrap_or_else(|e| panic!("compile failed for {:?}: {}", src, e));
|
|
197
|
+
if let Some(path) = std::env::var_os("PLUM_DUMP_WASM") {
|
|
198
|
+
let _ = std::fs::write(path, &bytes);
|
|
199
|
+
}
|
|
197
200
|
let result = wasmparser::validate(&bytes);
|
|
198
201
|
assert!(result.is_ok(), "wasm validation failed: {:?}", result.err());
|
|
199
202
|
bytes
|
|
@@ -1132,9 +1135,9 @@ fn question_mark_different_ok_types() {
|
|
|
1132
1135
|
fun inner() -> Try[Int, [A]] =
|
|
1133
1136
|
{}
|
|
1134
1137
|
|
|
1135
|
-
fun outer() -> Try[
|
|
1138
|
+
fun outer() -> Try[Float, [A, B]] =
|
|
1136
1139
|
x := inner()?
|
|
1137
|
-
Ok(
|
|
1140
|
+
Ok(1.5)
|
|
1138
1141
|
|
|
1139
1142
|
fun main() -> Int =
|
|
1140
1143
|
match outer()
|
|
@@ -1288,9 +1291,9 @@ fn probe_question_mark_payload_tag_and_str_payload() {
|
|
|
1288
1291
|
fun inner() -> Try[Int, [Bad(Str)]] =
|
|
1289
1292
|
{}
|
|
1290
1293
|
|
|
1291
|
-
fun outer() -> Try[
|
|
1294
|
+
fun outer() -> Try[Float, [Bad(Str), Other(Int)]] =
|
|
1292
1295
|
x := inner()?
|
|
1293
|
-
Ok(
|
|
1296
|
+
Ok(1.5)
|
|
1294
1297
|
|
|
1295
1298
|
fun main() -> Int =
|
|
1296
1299
|
match outer()
|
|
@@ -1468,7 +1471,7 @@ fn probe_question_mark_merges_two_ok_types() {
|
|
|
1468
1471
|
fun getInt() -> Try[Int, [A]] =
|
|
1469
1472
|
{}
|
|
1470
1473
|
|
|
1471
|
-
fun getStr() -> Try[
|
|
1474
|
+
fun getStr() -> Try[Float, [B]] =
|
|
1472
1475
|
{}
|
|
1473
1476
|
|
|
1474
1477
|
fun both() -> Try[Int, [A, B]] =
|
|
@@ -1485,8 +1488,8 @@ fun main() -> Int =
|
|
|
1485
1488
|
a, b
|
|
1486
1489
|
)
|
|
1487
1490
|
};
|
|
1488
|
-
assert_eq!(runTryMain(&prog("Ok(5)", "Ok(
|
|
1491
|
+
assert_eq!(runTryMain(&prog("Ok(5)", "Ok(2.5)")), 5);
|
|
1489
|
-
assert_eq!(runTryMain(&prog("Err(A)", "Ok(
|
|
1492
|
+
assert_eq!(runTryMain(&prog("Err(A)", "Ok(2.5)")), 100);
|
|
1490
1493
|
assert_eq!(runTryMain(&prog("Ok(5)", "Err(B)")), 200);
|
|
1491
1494
|
}
|
|
1492
1495
|
|
|
@@ -1672,3 +1675,100 @@ fun main() -> Int =
|
|
|
1672
1675
|
";
|
|
1673
1676
|
assert_eq!(runMerged::<i64>("boxed", src), 1);
|
|
1674
1677
|
}
|
|
1678
|
+
|
|
1679
|
+
// ---- erased generics (T4) ----
|
|
1680
|
+
|
|
1681
|
+
#[test]
|
|
1682
|
+
fn boxed_i64_extremes_through_generic() {
|
|
1683
|
+
let src = "\
|
|
1684
|
+
import std/Bool
|
|
1685
|
+
import std/Str
|
|
1686
|
+
import std/Number
|
|
1687
|
+
|
|
1688
|
+
fun identity(x: T) -> T =
|
|
1689
|
+
x
|
|
1690
|
+
|
|
1691
|
+
fun main() -> Int =
|
|
1692
|
+
lo := 0 - 9223372036854775807 - 1
|
|
1693
|
+
big := 9007199254740993
|
|
1694
|
+
nz := identity(-0.0)
|
|
1695
|
+
ok := identity(lo) == lo && identity(big) == big && identity(big + 0) - big == 0
|
|
1696
|
+
neg := 1.0 / nz < 0.0
|
|
1697
|
+
if ok && neg && identity(0 - 5) == 0 - 5
|
|
1698
|
+
return 1
|
|
1699
|
+
return 0
|
|
1700
|
+
";
|
|
1701
|
+
assert_eq!(runMerged::<i64>("extremes", src), 1);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
#[test]
|
|
1705
|
+
fn generic_closure_thunk() {
|
|
1706
|
+
let src = "\
|
|
1707
|
+
import std/Bool
|
|
1708
|
+
import std/Str
|
|
1709
|
+
import std/Number
|
|
1710
|
+
import std/Try
|
|
1711
|
+
import std/List
|
|
1712
|
+
|
|
1713
|
+
fun main() -> Int =
|
|
1714
|
+
xs := List(1, 2, 3).map(|n| n * 2)
|
|
1715
|
+
xs.reduce(0, |acc, x| acc + x)
|
|
1716
|
+
";
|
|
1717
|
+
assert_eq!(runMerged::<i64>("thunk", src), 12);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
#[test]
|
|
1721
|
+
fn try_of_list_of_try() {
|
|
1722
|
+
let src = "\
|
|
1723
|
+
import std/Bool
|
|
1724
|
+
import std/Str
|
|
1725
|
+
import std/Number
|
|
1726
|
+
import std/Try
|
|
1727
|
+
import std/List
|
|
1728
|
+
|
|
1729
|
+
fun inner(n: Int) -> Try[Int, [A]] =
|
|
1730
|
+
if n > 1
|
|
1731
|
+
return Err(A)
|
|
1732
|
+
return Ok(n + 40)
|
|
1733
|
+
|
|
1734
|
+
fun make() -> Try[List[Try[Int, [A]]], [B]] =
|
|
1735
|
+
return Ok(List(inner(1), inner(2)))
|
|
1736
|
+
|
|
1737
|
+
fun main() -> Int =
|
|
1738
|
+
match make()
|
|
1739
|
+
Ok(xs) =>
|
|
1740
|
+
first := xs.get(0).unwrap()
|
|
1741
|
+
second := xs.get(1).unwrap()
|
|
1742
|
+
match first, second
|
|
1743
|
+
Ok(a), Err(_) => a
|
|
1744
|
+
_, _ => 0
|
|
1745
|
+
Err(_) => 0
|
|
1746
|
+
";
|
|
1747
|
+
assert_eq!(runMerged::<i64>("trylist", src), 41);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
#[test]
|
|
1751
|
+
fn one_copy_per_generic() {
|
|
1752
|
+
let src = "\
|
|
1753
|
+
import std/Bool
|
|
1754
|
+
import std/Str
|
|
1755
|
+
import std/Number
|
|
1756
|
+
import std/Try
|
|
1757
|
+
import std/List
|
|
1758
|
+
|
|
1759
|
+
fun main() -> Int =
|
|
1760
|
+
a := List(1, 2).map(|n| n + 1)
|
|
1761
|
+
b := List(\"x\", \"y\").map(|s| s + \"!\")
|
|
1762
|
+
a.length() + b.length()
|
|
1763
|
+
";
|
|
1764
|
+
let dir = std::env::temp_dir().join(format!("plum_t4_copy_{}", std::process::id()));
|
|
1765
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
1766
|
+
let path = dir.join("main.plum");
|
|
1767
|
+
std::fs::write(&path, src).unwrap();
|
|
1768
|
+
let source = plum_core::loadAndMerge(&path, &repoRoot()).expect("load");
|
|
1769
|
+
let (_, keys) = plum_wasm_codegen::compileSourceWithFunctions(&source).expect("compile");
|
|
1770
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1771
|
+
let maps: Vec<&String> = keys.iter().filter(|k| k.as_str() == "List::map").collect();
|
|
1772
|
+
assert_eq!(maps.len(), 1, "got {:?}", keys);
|
|
1773
|
+
assert!(!keys.iter().any(|k| k.contains('$')), "mangled names remain");
|
|
1774
|
+
}
|