plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
85ed0b1
— Peter John
2026-10-26T14:41:12+05:30
fix: fn-field nested call args, heap-valued consts, value-enum nested patterns and field assign through heap fields
- plum-checker/src/lib.rs +10 -0
- plum-std/Heap.plum +1 -3
- plum-wasm-codegen/src/lib.rs +389 -134
- plum-wasm-codegen/tests/codegen_tests.rs +71 -0
plum-checker/src/lib.rs
CHANGED
|
@@ -1300,6 +1300,7 @@ fn checkSourceInner(source: &ast::Source) -> (CheckResult<()>, TypedInfo) {
|
|
|
1300
1300
|
// `buildGlobalTables` only had literals to go on; now that every method/enum
|
|
1301
1301
|
// table exists, re-infer each one with the full inferer. Two passes let a const
|
|
1302
1302
|
// reference one declared later.
|
|
1303
|
+
drainCalls();
|
|
1303
1304
|
for _ in 0..2 {
|
|
1304
1305
|
for item in &source.items {
|
|
1305
1306
|
if let ast::Item::Const(c) = item {
|
|
@@ -1309,6 +1310,9 @@ fn checkSourceInner(source: &ast::Source) -> (CheckResult<()>, TypedInfo) {
|
|
|
1309
1310
|
}
|
|
1310
1311
|
}
|
|
1311
1312
|
}
|
|
1313
|
+
// The calls inside a const initializer (`List(1, 2)`) are instantiated like a
|
|
1314
|
+
// root fn's: codegen compiles each initializer once, outside any instantiation.
|
|
1315
|
+
let const_calls = drainCalls();
|
|
1312
1316
|
|
|
1313
1317
|
errors.extend(checkValueEnums(source));
|
|
1314
1318
|
|
|
@@ -1388,6 +1392,12 @@ fn checkSourceInner(source: &ast::Source) -> (CheckResult<()>, TypedInfo) {
|
|
|
1388
1392
|
file,
|
|
1389
1393
|
};
|
|
1390
1394
|
drainCalls();
|
|
1395
|
+
CALLS.with(|c| {
|
|
1396
|
+
if let Some(v) = c.borrow_mut().as_mut() {
|
|
1397
|
+
v.extend(const_calls);
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
registerCalls(&mut inst, &fn_by_key, &ctx, None);
|
|
1391
1401
|
|
|
1392
1402
|
// Roots: every non-generic fn and every test. A generic fn is only
|
|
1393
1403
|
// name-resolved here (its body is type-checked per instantiation below).
|
plum-std/Heap.plum
CHANGED
|
@@ -29,9 +29,7 @@ enum Heap[T] =
|
|
|
29
29
|
self.items.data.set(j, a)
|
|
30
30
|
|
|
31
31
|
fun less(self, i: Int, j: Int) -> Bool =
|
|
32
|
-
a := self.items.data.get(i)
|
|
33
|
-
b := self.items.data.get(j)
|
|
34
|
-
return self.key(
|
|
32
|
+
return self.key(self.items.data.get(i)) < self.key(self.items.data.get(j))
|
|
35
33
|
|
|
36
34
|
fun siftUp(self, start: Int) -> Unit =
|
|
37
35
|
i := start
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -2439,6 +2439,30 @@ fn compileSourceInner(
|
|
|
2439
2439
|
Instruction::GlobalSet(global_idx).encode(&mut start_body);
|
|
2440
2440
|
}
|
|
2441
2441
|
|
|
2442
|
+
// Heap-valued consts (`TABLE = List(..)`): one lazily-initialised cached global and
|
|
2443
|
+
// one accessor function each (`const#NAME`), compiled below.
|
|
2444
|
+
let mut const_inits: Vec<(String, ConstInit, u32)> = Vec::new();
|
|
2445
|
+
for item in &source.items {
|
|
2446
|
+
let ast::Item::Const(c) = item else { continue };
|
|
2447
|
+
if !isBuiltConstInit(&c.value) {
|
|
2448
|
+
continue;
|
|
2449
|
+
}
|
|
2450
|
+
let Some(ty) = expr_types.get(&(&c.value as *const ast::Expr as usize)) else { continue };
|
|
2451
|
+
if !matches!(ty, PlumType::TNamed(_) | PlumType::TApp(..)) || isValueTy(ty) {
|
|
2452
|
+
continue;
|
|
2453
|
+
}
|
|
2454
|
+
let vt = plumTypeToValtype(ty);
|
|
2455
|
+
let ValType::Ref(rt) = vt else { continue };
|
|
2456
|
+
let mut init = Vec::new();
|
|
2457
|
+
Instruction::RefNull(rt.heap_type).encode(&mut init);
|
|
2458
|
+
let global = module.addGlobal(vt, true, &init);
|
|
2459
|
+
let t = module.addType(&[], &[vt]);
|
|
2460
|
+
let func_idx = module.addFunction(t, &[]);
|
|
2461
|
+
module.nameFunction(func_idx, &format!("const.{}", c.name));
|
|
2462
|
+
func_ids.insert(format!("const#{}", c.name), func_idx);
|
|
2463
|
+
const_inits.push((c.name.clone(), ConstInit { expr: &c.value, ty: ty.clone(), global }, func_idx));
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2442
2466
|
Instruction::End.encode(&mut start_body);
|
|
2443
2467
|
let mut final_start_body = vec![0u8];
|
|
2444
2468
|
final_start_body.extend(start_body);
|
|
@@ -2501,6 +2525,25 @@ fn compileSourceInner(
|
|
|
2501
2525
|
module.functions[slot].1 = body.clone();
|
|
2502
2526
|
}
|
|
2503
2527
|
|
|
2528
|
+
for (name, ci, func_idx) in &const_inits {
|
|
2529
|
+
let stand_in = ast::Fn {
|
|
2530
|
+
name: format!("const#{name}"),
|
|
2531
|
+
line: 0,
|
|
2532
|
+
column: 0,
|
|
2533
|
+
type_param: None,
|
|
2534
|
+
is_extern: false,
|
|
2535
|
+
params: Vec::new(),
|
|
2536
|
+
returns: None,
|
|
2537
|
+
body: ast::FnBody::Block(ast::Block { stmts: Vec::new() }),
|
|
2538
|
+
};
|
|
2539
|
+
let no_subst = Subst::new();
|
|
2540
|
+
let uref = UnitRef { idx: usize::MAX, inst: None, subst: &no_subst, types: &expr_types };
|
|
2541
|
+
let body = compileFnBodyImpl(&stand_in, Some(ci), &ctx, &mut state, &uref)
|
|
2542
|
+
.map_err(|e| format!("{e} (in const '{name}')"))?;
|
|
2543
|
+
let slot = (*func_idx - module.func_import_count) as usize;
|
|
2544
|
+
module.functions[slot].1 = body;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2504
2547
|
// Compile each closure literal's own body into its reserved function slot.
|
|
2505
2548
|
let mut closure_bodies: Vec<(u32, Vec<u8>)> = Vec::new();
|
|
2506
2549
|
for ((ui, ptr), info) in &ctx.closures {
|
|
@@ -3854,6 +3897,22 @@ impl<'a> Collector<'a> {
|
|
|
3854
3897
|
}
|
|
3855
3898
|
ast::AssignTarget::Field(object, _) => {
|
|
3856
3899
|
self.walkExpr(object);
|
|
3900
|
+
// `p.pos.x = v` through a heap field copies each value-enum
|
|
3901
|
+
// link of the chain out into a scratch (see `compileFieldStore`).
|
|
3902
|
+
let mut cur: &ast::Expr = object;
|
|
3903
|
+
loop {
|
|
3904
|
+
let t = self.ty(cur);
|
|
3905
|
+
if !isValueTy(&t) {
|
|
3906
|
+
break;
|
|
3907
|
+
}
|
|
3908
|
+
self.bind(&rmwLocal(cur), t);
|
|
3909
|
+
match cur {
|
|
3910
|
+
ast::Expr::Attribute(at) if matches!(at.attr, ast::AttrKind::Field(_)) => {
|
|
3911
|
+
cur = &at.object
|
|
3912
|
+
}
|
|
3913
|
+
_ => break,
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3857
3916
|
}
|
|
3858
3917
|
}
|
|
3859
3918
|
}
|
|
@@ -4049,6 +4108,37 @@ fn compileFnBody(
|
|
|
4049
4108
|
ctx: &CompileCtx,
|
|
4050
4109
|
state: &mut ModuleState,
|
|
4051
4110
|
unit: &UnitRef,
|
|
4111
|
+
) -> Result<Vec<u8>, String> {
|
|
4112
|
+
compileFnBodyImpl(f, None, ctx, state, unit)
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
/// A heap-valued top-level const (`TABLE = List(1, 2)`): its initializer expression
|
|
4116
|
+
/// (the ORIGINAL AST node, so the checker's per-expression tables apply), the const's
|
|
4117
|
+
/// type, and the mutable global caching the built value.
|
|
4118
|
+
struct ConstInit<'e> {
|
|
4119
|
+
expr: &'e ast::Expr,
|
|
4120
|
+
ty: PlumType,
|
|
4121
|
+
global: u32,
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
/// Whether a const initializer builds a fresh object — compiled once into a cached
|
|
4125
|
+
/// global (see `ConstInit`) rather than inlined (re-evaluated) at every use.
|
|
4126
|
+
fn isBuiltConstInit(e: &ast::Expr) -> bool {
|
|
4127
|
+
match e {
|
|
4128
|
+
ast::Expr::ClassCall(_) | ast::Expr::FnCall(_) => true,
|
|
4129
|
+
ast::Expr::Attribute(a) => matches!(a.attr, ast::AttrKind::Method(_)),
|
|
4130
|
+
_ => false,
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
/// Compiles a function body. With `const_init`, `f` is a bodyless stand-in and the
|
|
4135
|
+
/// body is the lazily-run, cache-once initializer of that const, returning its value.
|
|
4136
|
+
fn compileFnBodyImpl(
|
|
4137
|
+
f: &ast::Fn,
|
|
4138
|
+
const_init: Option<&ConstInit>,
|
|
4139
|
+
ctx: &CompileCtx,
|
|
4140
|
+
state: &mut ModuleState,
|
|
4141
|
+
unit: &UnitRef,
|
|
4052
4142
|
) -> Result<Vec<u8>, String> {
|
|
4053
4143
|
let mut body = Vec::new();
|
|
4054
4144
|
|
|
@@ -4078,7 +4168,9 @@ fn compileFnBody(
|
|
|
4078
4168
|
variadic_for_scratch: HashMap::new(),
|
|
4079
4169
|
next_variadic_for_slot: 0,
|
|
4080
4170
|
};
|
|
4171
|
+
if let Some(ci) = const_init {
|
|
4172
|
+
collector.walkExpr(ci.expr);
|
|
4081
|
-
if let ast::FnBody::Block(block) = &f.body {
|
|
4173
|
+
} else if let ast::FnBody::Block(block) = &f.body {
|
|
4082
4174
|
collector.walkBlock(block);
|
|
4083
4175
|
} else if let ast::FnBody::Expr(e) = &f.body {
|
|
4084
4176
|
// An expression-bodied fn can still contain a closure literal (e.g.
|
|
@@ -4195,6 +4287,17 @@ fn compileFnBody(
|
|
|
4195
4287
|
Some(plumTypeToValtype(&ret_decl))
|
|
4196
4288
|
};
|
|
4197
4289
|
|
|
4290
|
+
if let Some(ci) = const_init {
|
|
4291
|
+
Instruction::GlobalGet(ci.global).encode(&mut body);
|
|
4292
|
+
Instruction::RefIsNull.encode(&mut body);
|
|
4293
|
+
Instruction::If(BlockType::Empty).encode(&mut body);
|
|
4294
|
+
compileExprInto(ci.expr, &ci.ty, &mut body, &local_ctx, state)?;
|
|
4295
|
+
Instruction::GlobalSet(ci.global).encode(&mut body);
|
|
4296
|
+
Instruction::End.encode(&mut body);
|
|
4297
|
+
Instruction::GlobalGet(ci.global).encode(&mut body);
|
|
4298
|
+
Instruction::End.encode(&mut body);
|
|
4299
|
+
return Ok(body);
|
|
4300
|
+
}
|
|
4198
4301
|
match &f.body {
|
|
4199
4302
|
ast::FnBody::Expr(e) => {
|
|
4200
4303
|
compileExpr(e, &mut body, &local_ctx, state)?;
|
|
@@ -4393,6 +4496,161 @@ fn compileBlockAsFnBody(
|
|
|
4393
4496
|
}
|
|
4394
4497
|
}
|
|
4395
4498
|
|
|
4499
|
+
/// What `compileFieldStore` assigns: its type and a pusher for it (natural form —
|
|
4500
|
+
/// flat leaves for a value enum — converted for a destination of the given type).
|
|
4501
|
+
struct StoreSrc<'a> {
|
|
4502
|
+
ty: PlumType,
|
|
4503
|
+
push: &'a dyn Fn(&PlumType, &mut Vec<u8>, &LocalCtx, &mut ModuleState) -> Result<(), String>,
|
|
4504
|
+
}
|
|
4505
|
+
|
|
4506
|
+
/// The reserved scratch local holding a copy of the value-enum expression `e`
|
|
4507
|
+
/// while one of its fields is assigned (see `compileFieldStore`).
|
|
4508
|
+
fn rmwLocal(e: &ast::Expr) -> String {
|
|
4509
|
+
format!("rmw#{}", e as *const ast::Expr as usize)
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
/// `object.field_name = src`. Heap objects get a `struct.set`. A field of a
|
|
4513
|
+
/// flattened value enum is assigned in the local's leaves when `object` is a local
|
|
4514
|
+
/// place; otherwise (`p.pos.x = 1.0` through a heap field) the value is copied out
|
|
4515
|
+
/// into a scratch local, the field set there, and the whole value stored back into
|
|
4516
|
+
/// the place it came from.
|
|
4517
|
+
fn compileFieldStore(
|
|
4518
|
+
object: &ast::Expr,
|
|
4519
|
+
field_name: &str,
|
|
4520
|
+
src: &StoreSrc,
|
|
4521
|
+
body: &mut Vec<u8>,
|
|
4522
|
+
ctx: &LocalCtx,
|
|
4523
|
+
state: &mut ModuleState,
|
|
4524
|
+
) -> Result<(), String> {
|
|
4525
|
+
let obj_ty = inferLocalType(object, ctx);
|
|
4526
|
+
if let Some((off, fty)) = valueFieldRange(&obj_ty, field_name) {
|
|
4527
|
+
if let Some((base, _)) = valueLocalPlace(object, ctx) {
|
|
4528
|
+
(src.push)(&fty, body, ctx, state)?;
|
|
4529
|
+
emitSetLocals(base + off, &fty, body);
|
|
4530
|
+
return Ok(());
|
|
4531
|
+
}
|
|
4532
|
+
let ast::Expr::Attribute(a) = object else {
|
|
4533
|
+
return Err(format!(
|
|
4534
|
+
"codegen: cannot assign field '{}' of a value enum that is not a variable or field; assign the whole value instead",
|
|
4535
|
+
field_name
|
|
4536
|
+
));
|
|
4537
|
+
};
|
|
4538
|
+
let ast::AttrKind::Field(parent_field) = &a.attr else {
|
|
4539
|
+
return Err(format!(
|
|
4540
|
+
"codegen: cannot assign field '{}' of a value enum returned by a call; assign the whole value instead",
|
|
4541
|
+
field_name
|
|
4542
|
+
));
|
|
4543
|
+
};
|
|
4544
|
+
let tmp = *ctx
|
|
4545
|
+
.locals
|
|
4546
|
+
.get(&rmwLocal(object))
|
|
4547
|
+
.ok_or_else(|| "internal codegen error: missing value-field assignment scratch".to_string())?;
|
|
4548
|
+
compileExpr(object, body, ctx, state)?;
|
|
4549
|
+
emitSetLocals(tmp, &obj_ty, body);
|
|
4550
|
+
(src.push)(&fty, body, ctx, state)?;
|
|
4551
|
+
emitSetLocals(tmp + off, &fty, body);
|
|
4552
|
+
let whole = StoreSrc {
|
|
4553
|
+
ty: obj_ty.clone(),
|
|
4554
|
+
push: &|_, body, _, _| {
|
|
4555
|
+
emitGetLocals(tmp, &obj_ty, body);
|
|
4556
|
+
Ok(())
|
|
4557
|
+
},
|
|
4558
|
+
};
|
|
4559
|
+
return compileFieldStore(&a.object, parent_field, &whole, body, ctx, state);
|
|
4560
|
+
}
|
|
4561
|
+
let class_name = match plum_checker::nominalName(&obj_ty) {
|
|
4562
|
+
Some(n) => n.to_string(),
|
|
4563
|
+
None => {
|
|
4564
|
+
return Err(format!(
|
|
4565
|
+
"codegen: cannot assign field '{}' on non-class type {}",
|
|
4566
|
+
field_name, obj_ty
|
|
4567
|
+
))
|
|
4568
|
+
}
|
|
4569
|
+
};
|
|
4570
|
+
// struct.set expects [(ref null $t) value] on the stack (ref
|
|
4571
|
+
// pushed first/deeper, value second/on top) — same push order
|
|
4572
|
+
// this already used for the old memory store. Mirrors the read
|
|
4573
|
+
// side's discriminant-enum-param / single-owning-variant
|
|
4574
|
+
// fallback chain (`AttrKind::Field` below) — this also covers a
|
|
4575
|
+
// record-shaped enum's own field (the `type X = ...`
|
|
4576
|
+
// replacement), whose one variant is trivially its own unique
|
|
4577
|
+
// owner of every field it declares.
|
|
4578
|
+
match ctx.enum_params.get(&class_name) {
|
|
4579
|
+
Some(params) => {
|
|
4580
|
+
let field_idx = params
|
|
4581
|
+
.iter()
|
|
4582
|
+
.position(|(n, _)| n == field_name)
|
|
4583
|
+
.ok_or_else(|| {
|
|
4584
|
+
format!(
|
|
4585
|
+
"codegen: no field '{}' on enum '{}'",
|
|
4586
|
+
field_name, class_name
|
|
4587
|
+
)
|
|
4588
|
+
})?;
|
|
4589
|
+
let super_type_idx = ctx
|
|
4590
|
+
.gc_types
|
|
4591
|
+
.superIdx(&obj_ty)
|
|
4592
|
+
.ok_or_else(|| {
|
|
4593
|
+
format!(
|
|
4594
|
+
"codegen: enum '{}' missing from the GC type registry",
|
|
4595
|
+
class_name
|
|
4596
|
+
)
|
|
4597
|
+
})?;
|
|
4598
|
+
let sub_s: Subst = ctx
|
|
4599
|
+
.enum_variants
|
|
4600
|
+
.values()
|
|
4601
|
+
.find(|i| i.enum_name == class_name)
|
|
4602
|
+
.map(|i| match &obj_ty {
|
|
4603
|
+
PlumType::TApp(_, a) => i.generics.iter().cloned().zip(a.iter().cloned()).collect(),
|
|
4604
|
+
_ => Subst::new(),
|
|
4605
|
+
})
|
|
4606
|
+
.unwrap_or_default();
|
|
4607
|
+
let fty = plum_checker::types::substitute(¶ms[field_idx].1, &sub_s);
|
|
4608
|
+
compileExpr(object, body, ctx, state)?;
|
|
4609
|
+
(src.push)(&fty, body, ctx, state)?;
|
|
4610
|
+
boxIfValue(&src.ty, body);
|
|
4611
|
+
Instruction::StructSet {
|
|
4612
|
+
struct_type_index: super_type_idx,
|
|
4613
|
+
field_index: field_idx as u32,
|
|
4614
|
+
}
|
|
4615
|
+
.encode(body);
|
|
4616
|
+
}
|
|
4617
|
+
None => {
|
|
4618
|
+
let (variant_name, info) = ctx
|
|
4619
|
+
.enum_variants
|
|
4620
|
+
.iter()
|
|
4621
|
+
.find(|(_, info)| {
|
|
4622
|
+
info.enum_name == class_name
|
|
4623
|
+
&& info.field_names.iter().any(|n| n == field_name)
|
|
4624
|
+
})
|
|
4625
|
+
.ok_or_else(|| {
|
|
4626
|
+
format!(
|
|
4627
|
+
"codegen: no field '{}' on enum '{}'",
|
|
4628
|
+
field_name, class_name
|
|
4629
|
+
)
|
|
4630
|
+
})?;
|
|
4631
|
+
let field_idx = info
|
|
4632
|
+
.field_names
|
|
4633
|
+
.iter()
|
|
4634
|
+
.position(|n| n == field_name)
|
|
4635
|
+
.expect("just found by this field name");
|
|
4636
|
+
let variant_type_idx = ctx.gc_types.variantIdx(&obj_ty, variant_name)
|
|
4637
|
+
.ok_or_else(|| format!("codegen: variant '{}' of {} missing from the GC type registry", variant_name, obj_ty))?;
|
|
4638
|
+
let fty = plum_checker::variantFields(info, &obj_ty)[field_idx].clone();
|
|
4639
|
+
compileExpr(object, body, ctx, state)?;
|
|
4640
|
+
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx))
|
|
4641
|
+
.encode(body);
|
|
4642
|
+
(src.push)(&fty, body, ctx, state)?;
|
|
4643
|
+
boxIfValue(&src.ty, body);
|
|
4644
|
+
Instruction::StructSet {
|
|
4645
|
+
struct_type_index: variant_type_idx,
|
|
4646
|
+
field_index: field_idx as u32,
|
|
4647
|
+
}
|
|
4648
|
+
.encode(body);
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
Ok(())
|
|
4652
|
+
}
|
|
4653
|
+
|
|
4396
4654
|
fn compileStmt(
|
|
4397
4655
|
stmt: &ast::Stmt,
|
|
4398
4656
|
body: &mut Vec<u8>,
|
|
@@ -4421,110 +4679,11 @@ fn compileStmt(
|
|
|
4421
4679
|
.insert(name.clone(), TypeScheme::mono(vty));
|
|
4422
4680
|
}
|
|
4423
4681
|
ast::AssignTarget::Field(object, field_name) => {
|
|
4682
|
+
let src = StoreSrc {
|
|
4424
|
-
|
|
4683
|
+
ty: inferLocalType(value, ctx),
|
|
4425
|
-
// A field of a flattened value enum: assign straight into the
|
|
4426
|
-
// local copy's leaves (the original it was copied from is untouched).
|
|
4427
|
-
if let Some((off, fty)) = valueFieldRange(&obj_ty, field_name) {
|
|
4428
|
-
let (base, _) = valueLocalPlace(object, ctx).ok_or_else(|| {
|
|
4429
|
-
format!(
|
|
4430
|
-
"codegen: cannot assign field '{}' of a value enum that is not a local variable; assign the whole value instead",
|
|
4431
|
-
field_name
|
|
4432
|
-
)
|
|
4433
|
-
})?;
|
|
4434
|
-
|
|
4684
|
+
push: &|dest, body, ctx, state| compileExprInto(value, dest, body, ctx, state),
|
|
4435
|
-
emitSetLocals(base + off, &fty, body);
|
|
4436
|
-
continue;
|
|
4437
|
-
}
|
|
4438
|
-
let class_name = match plum_checker::nominalName(&obj_ty) {
|
|
4439
|
-
Some(n) => n.to_string(),
|
|
4440
|
-
None => {
|
|
4441
|
-
return Err(format!(
|
|
4442
|
-
"codegen: cannot assign field '{}' on non-class type {}",
|
|
4443
|
-
field_name, obj_ty
|
|
4444
|
-
))
|
|
4445
|
-
}
|
|
4446
4685
|
};
|
|
4447
|
-
// struct.set expects [(ref null $t) value] on the stack (ref
|
|
4448
|
-
// pushed first/deeper, value second/on top) — same push order
|
|
4449
|
-
// this already used for the old memory store. Mirrors the read
|
|
4450
|
-
// side's discriminant-enum-param / single-owning-variant
|
|
4451
|
-
// fallback chain (`AttrKind::Field` below) — this also covers a
|
|
4452
|
-
// record-shaped enum's own field (the `type X = ...`
|
|
4453
|
-
// replacement), whose one variant is trivially its own unique
|
|
4454
|
-
// owner of every field it declares.
|
|
4455
|
-
match ctx.enum_params.get(&class_name) {
|
|
4456
|
-
Some(params) => {
|
|
4457
|
-
let field_idx = params
|
|
4458
|
-
.iter()
|
|
4459
|
-
.position(|(n, _)| n == field_name)
|
|
4460
|
-
.ok_or_else(|| {
|
|
4461
|
-
format!(
|
|
4462
|
-
"codegen: no field '{}' on enum '{}'",
|
|
4463
|
-
field_name, class_name
|
|
4464
|
-
)
|
|
4465
|
-
})?;
|
|
4466
|
-
let super_type_idx = ctx
|
|
4467
|
-
.gc_types
|
|
4468
|
-
.superIdx(&obj_ty)
|
|
4469
|
-
.ok_or_else(|| {
|
|
4470
|
-
format!(
|
|
4471
|
-
"codegen: enum '{}' missing from the GC type registry",
|
|
4472
|
-
class_name
|
|
4473
|
-
)
|
|
4474
|
-
})?;
|
|
4475
|
-
let sub_s: Subst = ctx
|
|
4476
|
-
.enum_variants
|
|
4477
|
-
.values()
|
|
4478
|
-
.find(|i| i.enum_name == class_name)
|
|
4479
|
-
.map(|i| match &obj_ty {
|
|
4480
|
-
PlumType::TApp(_, a) => i.generics.iter().cloned().zip(a.iter().cloned()).collect(),
|
|
4481
|
-
_ => Subst::new(),
|
|
4482
|
-
})
|
|
4483
|
-
.unwrap_or_default();
|
|
4484
|
-
let fty = plum_checker::types::substitute(¶ms[field_idx].1, &sub_s);
|
|
4485
|
-
compileExpr(object, body, ctx, state)?;
|
|
4486
|
-
|
|
4686
|
+
compileFieldStore(object, field_name, &src, body, ctx, state)?;
|
|
4487
|
-
boxIfValue(&inferLocalType(value, ctx), body);
|
|
4488
|
-
Instruction::StructSet {
|
|
4489
|
-
struct_type_index: super_type_idx,
|
|
4490
|
-
field_index: field_idx as u32,
|
|
4491
|
-
}
|
|
4492
|
-
.encode(body);
|
|
4493
|
-
}
|
|
4494
|
-
None => {
|
|
4495
|
-
let (variant_name, info) = ctx
|
|
4496
|
-
.enum_variants
|
|
4497
|
-
.iter()
|
|
4498
|
-
.find(|(_, info)| {
|
|
4499
|
-
info.enum_name == class_name
|
|
4500
|
-
&& info.field_names.iter().any(|n| n == field_name)
|
|
4501
|
-
})
|
|
4502
|
-
.ok_or_else(|| {
|
|
4503
|
-
format!(
|
|
4504
|
-
"codegen: no field '{}' on enum '{}'",
|
|
4505
|
-
field_name, class_name
|
|
4506
|
-
)
|
|
4507
|
-
})?;
|
|
4508
|
-
let field_idx = info
|
|
4509
|
-
.field_names
|
|
4510
|
-
.iter()
|
|
4511
|
-
.position(|n| n == field_name)
|
|
4512
|
-
.expect("just found by this field name");
|
|
4513
|
-
let variant_type_idx = ctx.gc_types.variantIdx(&obj_ty, variant_name)
|
|
4514
|
-
.ok_or_else(|| format!("codegen: variant '{}' of {} missing from the GC type registry", variant_name, obj_ty))?;
|
|
4515
|
-
let fty = plum_checker::variantFields(info, &obj_ty)[field_idx].clone();
|
|
4516
|
-
compileExpr(object, body, ctx, state)?;
|
|
4517
|
-
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx))
|
|
4518
|
-
.encode(body);
|
|
4519
|
-
compileExprInto(value, &fty, body, ctx, state)?;
|
|
4520
|
-
boxIfValue(&inferLocalType(value, ctx), body);
|
|
4521
|
-
Instruction::StructSet {
|
|
4522
|
-
struct_type_index: variant_type_idx,
|
|
4523
|
-
field_index: field_idx as u32,
|
|
4524
|
-
}
|
|
4525
|
-
.encode(body);
|
|
4526
|
-
}
|
|
4527
|
-
}
|
|
4528
4687
|
}
|
|
4529
4688
|
}
|
|
4530
4689
|
}
|
|
@@ -5472,6 +5631,7 @@ fn compileVariantConstructorArm(
|
|
|
5472
5631
|
0,
|
|
5473
5632
|
scratch_local,
|
|
5474
5633
|
if name == "Ok" { NULLABLE_OK } else { NULLABLE_ERR },
|
|
5634
|
+
None,
|
|
5475
5635
|
rest,
|
|
5476
5636
|
all_subjects,
|
|
5477
5637
|
result_vt,
|
|
@@ -5507,12 +5667,11 @@ fn compileVariantConstructorArm(
|
|
|
5507
5667
|
// Payload types instantiated by the subject's type arguments.
|
|
5508
5668
|
let inst_types = instFields(info, &sty);
|
|
5509
5669
|
let field_types = inst_types.clone();
|
|
5510
|
-
if isValueTy(&sty) && inst_types.iter().any(isValueTy) {
|
|
5511
|
-
return Err(format!(
|
|
5512
|
-
|
|
5670
|
+
// A value enum's box struct holds its fields FLATTENED into leaves, so field `i`
|
|
5671
|
+
// of the pattern lives at leaf `value_leaf_map[i]` (nested value fields span several).
|
|
5672
|
+
let value_leaf_map: Option<Vec<u32>> = valueFieldsOf(&sty).map(|fs| {
|
|
5513
|
-
|
|
5673
|
+
fs.iter().map(|(n, _)| valueFieldRange(&sty, n).map(|(o, _)| o).unwrap_or(0)).collect()
|
|
5514
|
-
|
|
5674
|
+
});
|
|
5515
|
-
}
|
|
5516
5675
|
|
|
5517
5676
|
// This pattern's own narrowly-typed scratch local (declared with variant's exact
|
|
5518
5677
|
// concrete ref type — see `nested_class_scratch_types`), distinct from
|
|
@@ -5543,6 +5702,7 @@ fn compileVariantConstructorArm(
|
|
|
5543
5702
|
0,
|
|
5544
5703
|
narrow_local,
|
|
5545
5704
|
variant_idx,
|
|
5705
|
+
value_leaf_map.as_deref(),
|
|
5546
5706
|
rest,
|
|
5547
5707
|
all_subjects,
|
|
5548
5708
|
result_vt,
|
|
@@ -5592,6 +5752,7 @@ fn emitContainerField(
|
|
|
5592
5752
|
container_local: u32,
|
|
5593
5753
|
container_type_idx: u32,
|
|
5594
5754
|
fpos: usize,
|
|
5755
|
+
leaf_map: Option<&[u32]>,
|
|
5595
5756
|
) {
|
|
5596
5757
|
match container_type_idx {
|
|
5597
5758
|
NULLABLE_OK => Instruction::LocalGet(container_local).encode(body),
|
|
@@ -5600,7 +5761,7 @@ fn emitContainerField(
|
|
|
5600
5761
|
Instruction::LocalGet(container_local).encode(body);
|
|
5601
5762
|
Instruction::StructGet {
|
|
5602
5763
|
struct_type_index: idx,
|
|
5603
|
-
field_index: fpos as u32,
|
|
5764
|
+
field_index: leaf_map.map(|m| m[fpos]).unwrap_or(fpos as u32),
|
|
5604
5765
|
}
|
|
5605
5766
|
.encode(body);
|
|
5606
5767
|
}
|
|
@@ -5622,6 +5783,7 @@ fn compileFieldPatterns(
|
|
|
5622
5783
|
fpos: usize,
|
|
5623
5784
|
container_local: u32,
|
|
5624
5785
|
container_type_idx: u32,
|
|
5786
|
+
leaf_map: Option<&[u32]>,
|
|
5625
5787
|
rest: &[ast::Case],
|
|
5626
5788
|
all_subjects: &[(ValType, u32)],
|
|
5627
5789
|
result_vt: Option<ValType>,
|
|
@@ -5646,7 +5808,7 @@ fn compileFieldPatterns(
|
|
|
5646
5808
|
pat,
|
|
5647
5809
|
tags,
|
|
5648
5810
|
&|b: &mut Vec<u8>| {
|
|
5649
|
-
emitContainerField(b, ctx, container_local, container_type_idx, fpos);
|
|
5811
|
+
emitContainerField(b, ctx, container_local, container_type_idx, fpos, leaf_map);
|
|
5650
5812
|
},
|
|
5651
5813
|
result_vt,
|
|
5652
5814
|
body,
|
|
@@ -5660,6 +5822,7 @@ fn compileFieldPatterns(
|
|
|
5660
5822
|
fpos + 1,
|
|
5661
5823
|
container_local,
|
|
5662
5824
|
container_type_idx,
|
|
5825
|
+
leaf_map,
|
|
5663
5826
|
rest,
|
|
5664
5827
|
all_subjects,
|
|
5665
5828
|
result_vt,
|
|
@@ -5693,6 +5856,7 @@ fn compileFieldPatterns(
|
|
|
5693
5856
|
fpos + 1,
|
|
5694
5857
|
container_local,
|
|
5695
5858
|
container_type_idx,
|
|
5859
|
+
leaf_map,
|
|
5696
5860
|
rest,
|
|
5697
5861
|
all_subjects,
|
|
5698
5862
|
result_vt,
|
|
@@ -5704,7 +5868,7 @@ fn compileFieldPatterns(
|
|
|
5704
5868
|
),
|
|
5705
5869
|
// A nested `True`/`False` against a `Bool` field: i32 compare.
|
|
5706
5870
|
ast::CasePattern::Name(n) if n == "True" || n == "False" => {
|
|
5707
|
-
emitContainerField(body, ctx, container_local, container_type_idx, fpos);
|
|
5871
|
+
emitContainerField(body, ctx, container_local, container_type_idx, fpos, leaf_map);
|
|
5708
5872
|
if n == "False" {
|
|
5709
5873
|
Instruction::I32Eqz.encode(body);
|
|
5710
5874
|
}
|
|
@@ -5716,6 +5880,7 @@ fn compileFieldPatterns(
|
|
|
5716
5880
|
fpos + 1,
|
|
5717
5881
|
container_local,
|
|
5718
5882
|
container_type_idx,
|
|
5883
|
+
leaf_map,
|
|
5719
5884
|
rest,
|
|
5720
5885
|
all_subjects,
|
|
5721
5886
|
result_vt,
|
|
@@ -5748,12 +5913,22 @@ fn compileFieldPatterns(
|
|
|
5748
5913
|
ctx.locals.get(n).copied().ok_or_else(|| {
|
|
5749
5914
|
format!("internal codegen error: missing binding local '{}'", n)
|
|
5750
5915
|
})?;
|
|
5751
|
-
|
|
5916
|
+
if let (true, Some(m)) = (isValueTy(field_ty), leaf_map) {
|
|
5917
|
+
// Flattened container: the nested value's leaves are consecutive fields.
|
|
5752
|
-
|
|
5918
|
+
for j in 0..leafCount(field_ty) {
|
|
5919
|
+
Instruction::LocalGet(container_local).encode(body);
|
|
5920
|
+
Instruction::StructGet { struct_type_index: container_type_idx, field_index: m[fpos] + j }
|
|
5753
|
-
|
|
5921
|
+
.encode(body);
|
|
5922
|
+
}
|
|
5754
5923
|
emitSetLocals(idx, field_ty, body);
|
|
5755
5924
|
} else {
|
|
5925
|
+
emitContainerField(body, ctx, container_local, container_type_idx, fpos, leaf_map);
|
|
5926
|
+
if isValueTy(field_ty) {
|
|
5927
|
+
emitUnbox(field_ty, body);
|
|
5928
|
+
emitSetLocals(idx, field_ty, body);
|
|
5929
|
+
} else {
|
|
5756
|
-
|
|
5930
|
+
Instruction::LocalSet(idx).encode(body);
|
|
5931
|
+
}
|
|
5757
5932
|
}
|
|
5758
5933
|
ctx.type_env
|
|
5759
5934
|
.borrow_mut()
|
|
@@ -5765,6 +5940,7 @@ fn compileFieldPatterns(
|
|
|
5765
5940
|
fpos + 1,
|
|
5766
5941
|
container_local,
|
|
5767
5942
|
container_type_idx,
|
|
5943
|
+
leaf_map,
|
|
5768
5944
|
rest,
|
|
5769
5945
|
all_subjects,
|
|
5770
5946
|
result_vt,
|
|
@@ -5790,7 +5966,7 @@ fn compileFieldPatterns(
|
|
|
5790
5966
|
let variant_idx = ctx.gc_types.variantIdx(field_ty, n).ok_or_else(|| {
|
|
5791
5967
|
format!("codegen: variant '{}' of {} missing from the GC type registry", n, field_ty)
|
|
5792
5968
|
})?;
|
|
5793
|
-
emitContainerField(body, ctx, container_local, container_type_idx, fpos);
|
|
5969
|
+
emitContainerField(body, ctx, container_local, container_type_idx, fpos, leaf_map);
|
|
5794
5970
|
Instruction::RefTestNonNull(HeapType::Concrete(variant_idx)).encode(body);
|
|
5795
5971
|
Instruction::If(blockTypeFor(result_vt)).encode(body);
|
|
5796
5972
|
compileFieldPatterns(
|
|
@@ -5800,6 +5976,7 @@ fn compileFieldPatterns(
|
|
|
5800
5976
|
fpos + 1,
|
|
5801
5977
|
container_local,
|
|
5802
5978
|
container_type_idx,
|
|
5979
|
+
leaf_map,
|
|
5803
5980
|
rest,
|
|
5804
5981
|
all_subjects,
|
|
5805
5982
|
result_vt,
|
|
@@ -5826,7 +6003,7 @@ fn compileFieldPatterns(
|
|
|
5826
6003
|
if inst_vt != ValType::I64 {
|
|
5827
6004
|
return Err("codegen: integer match pattern against a non-Int field".to_string());
|
|
5828
6005
|
}
|
|
5829
|
-
emitContainerField(body, ctx, container_local, container_type_idx, fpos);
|
|
6006
|
+
emitContainerField(body, ctx, container_local, container_type_idx, fpos, leaf_map);
|
|
5830
6007
|
Instruction::I64Const(*n).encode(body);
|
|
5831
6008
|
Instruction::I64Eq.encode(body);
|
|
5832
6009
|
Instruction::If(blockTypeFor(result_vt)).encode(body);
|
|
@@ -5837,6 +6014,7 @@ fn compileFieldPatterns(
|
|
|
5837
6014
|
fpos + 1,
|
|
5838
6015
|
container_local,
|
|
5839
6016
|
container_type_idx,
|
|
6017
|
+
leaf_map,
|
|
5840
6018
|
rest,
|
|
5841
6019
|
all_subjects,
|
|
5842
6020
|
result_vt,
|
|
@@ -5887,6 +6065,71 @@ fn compileFieldPatterns(
|
|
|
5887
6065
|
inner_fields.len()
|
|
5888
6066
|
));
|
|
5889
6067
|
}
|
|
6068
|
+
// A nested VALUE-enum field (`Rect(Vec2(px, py), size)`): there is exactly
|
|
6069
|
+
// one constructor, so nothing to test — destructure its leaves in place.
|
|
6070
|
+
// Inside a flattened (value) container they are consecutive fields of the
|
|
6071
|
+
// same struct; inside a heap container the field holds the value's box.
|
|
6072
|
+
if let Some(vfields) = valueFieldsOf(field_ty) {
|
|
6073
|
+
let box_idx = ctx.gc_types.classIdx(field_ty).ok_or_else(|| {
|
|
6074
|
+
format!("codegen: value enum '{}' missing from the GC type registry", field_ty)
|
|
6075
|
+
})?;
|
|
6076
|
+
let (inner_container, inner_idx, base, prelude_nested) = match leaf_map {
|
|
6077
|
+
Some(m) => (container_local, container_type_idx, m[fpos], false),
|
|
6078
|
+
None => {
|
|
6079
|
+
let key = pat as *const ast::CasePattern as usize;
|
|
6080
|
+
let slot = *ctx.nested_class_scratch.get(&key).ok_or_else(|| {
|
|
6081
|
+
"internal codegen error: missing nested constructor pattern scratch slot"
|
|
6082
|
+
.to_string()
|
|
6083
|
+
})?;
|
|
6084
|
+
(ctx.nested_class_scratch_base + slot, box_idx, 0, true)
|
|
6085
|
+
}
|
|
6086
|
+
};
|
|
6087
|
+
if prelude_nested {
|
|
6088
|
+
emitContainerField(body, ctx, container_local, container_type_idx, fpos, leaf_map);
|
|
6089
|
+
Instruction::RefCastNonNull(HeapType::Concrete(box_idx)).encode(body);
|
|
6090
|
+
Instruction::LocalSet(inner_container).encode(body);
|
|
6091
|
+
}
|
|
6092
|
+
let inner_map: Vec<u32> = vfields
|
|
6093
|
+
.iter()
|
|
6094
|
+
.map(|(n, _)| base + valueFieldRange(field_ty, n).map(|(o, _)| o).unwrap_or(0))
|
|
6095
|
+
.collect();
|
|
6096
|
+
let inner_types: Vec<PlumType> = vfields.iter().map(|(_, t)| t.clone()).collect();
|
|
6097
|
+
return compileFieldPatterns(
|
|
6098
|
+
inner_fields,
|
|
6099
|
+
&inner_types,
|
|
6100
|
+
&inner_types,
|
|
6101
|
+
0,
|
|
6102
|
+
inner_container,
|
|
6103
|
+
inner_idx,
|
|
6104
|
+
Some(&inner_map),
|
|
6105
|
+
rest,
|
|
6106
|
+
all_subjects,
|
|
6107
|
+
result_vt,
|
|
6108
|
+
exhaustive_fallback,
|
|
6109
|
+
body,
|
|
6110
|
+
ctx,
|
|
6111
|
+
state,
|
|
6112
|
+
&mut |body, state| {
|
|
6113
|
+
compileFieldPatterns(
|
|
6114
|
+
fields,
|
|
6115
|
+
field_types,
|
|
6116
|
+
inst_types,
|
|
6117
|
+
fpos + 1,
|
|
6118
|
+
container_local,
|
|
6119
|
+
container_type_idx,
|
|
6120
|
+
leaf_map,
|
|
6121
|
+
rest,
|
|
6122
|
+
all_subjects,
|
|
6123
|
+
result_vt,
|
|
6124
|
+
exhaustive_fallback,
|
|
6125
|
+
body,
|
|
6126
|
+
ctx,
|
|
6127
|
+
state,
|
|
6128
|
+
on_match,
|
|
6129
|
+
)
|
|
6130
|
+
},
|
|
6131
|
+
);
|
|
6132
|
+
}
|
|
5890
6133
|
let inner_variant_idx = ctx.gc_types.variantIdx(field_ty, name).ok_or_else(|| {
|
|
5891
6134
|
format!(
|
|
5892
6135
|
"codegen: variant '{}' of {} missing from the GC type registry",
|
|
@@ -5902,11 +6145,11 @@ fn compileFieldPatterns(
|
|
|
5902
6145
|
let inner_inst_types = instFields(info, field_ty);
|
|
5903
6146
|
let inner_field_types = inner_inst_types.clone();
|
|
5904
6147
|
|
|
5905
|
-
emitContainerField(body, ctx, container_local, container_type_idx, fpos);
|
|
6148
|
+
emitContainerField(body, ctx, container_local, container_type_idx, fpos, leaf_map);
|
|
5906
6149
|
Instruction::RefTestNonNull(HeapType::Concrete(inner_variant_idx)).encode(body);
|
|
5907
6150
|
Instruction::If(blockTypeFor(result_vt)).encode(body);
|
|
5908
6151
|
// Narrow into `nested_local` now that ref.test just above proved it's safe.
|
|
5909
|
-
emitContainerField(body, ctx, container_local, container_type_idx, fpos);
|
|
6152
|
+
emitContainerField(body, ctx, container_local, container_type_idx, fpos, leaf_map);
|
|
5910
6153
|
Instruction::RefCastNonNull(HeapType::Concrete(inner_variant_idx)).encode(body);
|
|
5911
6154
|
Instruction::LocalSet(nested_local).encode(body);
|
|
5912
6155
|
compileFieldPatterns(
|
|
@@ -5916,6 +6159,7 @@ fn compileFieldPatterns(
|
|
|
5916
6159
|
0,
|
|
5917
6160
|
nested_local,
|
|
5918
6161
|
inner_variant_idx,
|
|
6162
|
+
None,
|
|
5919
6163
|
rest,
|
|
5920
6164
|
all_subjects,
|
|
5921
6165
|
result_vt,
|
|
@@ -5931,6 +6175,7 @@ fn compileFieldPatterns(
|
|
|
5931
6175
|
fpos + 1,
|
|
5932
6176
|
container_local,
|
|
5933
6177
|
container_type_idx,
|
|
6178
|
+
leaf_map,
|
|
5934
6179
|
rest,
|
|
5935
6180
|
all_subjects,
|
|
5936
6181
|
result_vt,
|
|
@@ -6772,6 +7017,9 @@ fn compileExprCore(
|
|
|
6772
7017
|
None => {
|
|
6773
7018
|
let const_value = CURRENT_CONSTS.with(|c| c.borrow().get(n).cloned());
|
|
6774
7019
|
match const_value {
|
|
7020
|
+
Some(_) if ctx.func_ids.contains_key(&format!("const#{n}")) => {
|
|
7021
|
+
Instruction::Call(ctx.func_ids[&format!("const#{n}")]).encode(body);
|
|
7022
|
+
}
|
|
6775
7023
|
Some(value) => compileExpr(&value, body, ctx, state)?,
|
|
6776
7024
|
None if n
|
|
6777
7025
|
.chars()
|
|
@@ -7190,13 +7438,7 @@ fn compileMethodCall(
|
|
|
7190
7438
|
if !ctx.func_ids.contains_key(&key) && ctx.locals.contains_key(&field_local) {
|
|
7191
7439
|
compileFieldRead(object, &call.name, obj_ty, result_expr, body, ctx, state)?;
|
|
7192
7440
|
Instruction::LocalSet(ctx.locals[&field_local]).encode(body);
|
|
7193
|
-
let field_call = ast::FnCall {
|
|
7194
|
-
name: field_local,
|
|
7195
|
-
args: call.args.clone(),
|
|
7196
|
-
line: call.line,
|
|
7197
|
-
column: call.column,
|
|
7198
|
-
};
|
|
7199
|
-
return
|
|
7441
|
+
return compileClosureCallArgs(&field_local, &call.args, body, ctx, state);
|
|
7200
7442
|
}
|
|
7201
7443
|
let func_idx = *ctx
|
|
7202
7444
|
.func_ids
|
|
@@ -7534,20 +7776,33 @@ fn compileClosureCall(
|
|
|
7534
7776
|
body: &mut Vec<u8>,
|
|
7535
7777
|
ctx: &LocalCtx,
|
|
7536
7778
|
state: &mut ModuleState,
|
|
7779
|
+
) -> Result<(), String> {
|
|
7780
|
+
compileClosureCallArgs(&call.name, &call.args, body, ctx, state)
|
|
7781
|
+
}
|
|
7782
|
+
|
|
7783
|
+
/// `compileClosureCall` over the local `name` with explicit `args` — the args must be
|
|
7784
|
+
/// the ORIGINAL AST nodes (not clones): the checker's per-call tables are keyed by
|
|
7785
|
+
/// expression address, so a cloned arg containing a call would not be found.
|
|
7786
|
+
fn compileClosureCallArgs(
|
|
7787
|
+
name: &str,
|
|
7788
|
+
args: &[ast::Arg],
|
|
7789
|
+
body: &mut Vec<u8>,
|
|
7790
|
+
ctx: &LocalCtx,
|
|
7791
|
+
state: &mut ModuleState,
|
|
7537
7792
|
) -> Result<(), String> {
|
|
7538
7793
|
let closure_local = *ctx
|
|
7539
7794
|
.locals
|
|
7540
|
-
.get(
|
|
7795
|
+
.get(name)
|
|
7541
|
-
.ok_or_else(|| format!("codegen: closure '{}' is not a local",
|
|
7796
|
+
.ok_or_else(|| format!("codegen: closure '{}' is not a local", name))?;
|
|
7542
7797
|
let closure_type_idx = ctx.gc_types.closure_type_idx;
|
|
7543
|
-
let Some(PlumType::TFun(ps, r)) = ctx.local_decl_types.get(
|
|
7798
|
+
let Some(PlumType::TFun(ps, r)) = ctx.local_decl_types.get(name) else {
|
|
7544
|
-
return Err(format!("codegen: '{}' is not a function-typed local",
|
|
7799
|
+
return Err(format!("codegen: '{}' is not a function-typed local", name));
|
|
7545
7800
|
};
|
|
7546
7801
|
let key = closureSigKey(ps, r, ctx.gc_types);
|
|
7547
7802
|
let type_index = *ctx.closure_call_types.get(&key).ok_or_else(|| {
|
|
7548
7803
|
format!(
|
|
7549
7804
|
"internal codegen error: no call_indirect type for closure '{}' of type fn{:?}",
|
|
7550
|
-
|
|
7805
|
+
name, key
|
|
7551
7806
|
)
|
|
7552
7807
|
})?;
|
|
7553
7808
|
|
|
@@ -7558,7 +7813,7 @@ fn compileClosureCall(
|
|
|
7558
7813
|
field_index: 1,
|
|
7559
7814
|
}
|
|
7560
7815
|
.encode(body);
|
|
7561
|
-
for (i, arg) in
|
|
7816
|
+
for (i, arg) in args.iter().enumerate() {
|
|
7562
7817
|
match ps.get(i) {
|
|
7563
7818
|
Some(p) => compileExprInto(argExprOf(arg), p, body, ctx, state)?,
|
|
7564
7819
|
None => compileExpr(argExprOf(arg), body, ctx, state)?,
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -2382,3 +2382,74 @@ fn extern_value_multi_value() {
|
|
|
2382
2382
|
let main = instance.get_typed_func::<(), i64>(&mut store, "main").unwrap();
|
|
2383
2383
|
assert_eq!(main.call(&mut store, ()).unwrap(), 1);
|
|
2384
2384
|
}
|
|
2385
|
+
|
|
2386
|
+
// ---- fixes: nested call as fn-field arg, collection consts, value-enum patterns/assign ----
|
|
2387
|
+
|
|
2388
|
+
#[test]
|
|
2389
|
+
fn fn_field_nested_call_arg() {
|
|
2390
|
+
let src = format!("{}\
|
|
2391
|
+
import std/Array
|
|
2392
|
+
|
|
2393
|
+
enum Keyed[T] =
|
|
2394
|
+
| Keyed(items: List[T], key: fn(T) -> Int)
|
|
2395
|
+
|
|
2396
|
+
fun first(self) -> Int =
|
|
2397
|
+
self.key(self.items.data.get(0))
|
|
2398
|
+
|
|
2399
|
+
fun main() -> Int =
|
|
2400
|
+
k := Keyed[Int](items: List(5, 6), key: |n| n * 8)
|
|
2401
|
+
k.first() + 2
|
|
2402
|
+
", STD_HEAD);
|
|
2403
|
+
assert_eq!(runMerged::<i64>("fnfieldnested", &src), 42);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
#[test]
|
|
2407
|
+
fn const_list_table() {
|
|
2408
|
+
let src = format!("{}\
|
|
2409
|
+
TABLE = List(10, 20, 30)
|
|
2410
|
+
|
|
2411
|
+
fun total() -> Int =
|
|
2412
|
+
TABLE.reduce(0, |a, b| a + b)
|
|
2413
|
+
|
|
2414
|
+
fun main() -> Int =
|
|
2415
|
+
TABLE.push(4)
|
|
2416
|
+
TABLE.get(1).unwrap() + TABLE.length() + total() - 78 + 4
|
|
2417
|
+
", STD_HEAD);
|
|
2418
|
+
// TABLE is a single shared object, initialised once: push(4) persists, so
|
|
2419
|
+
// total() sees 64 (60 + 4), length 4, get(1) 20 -> 20 + 4 + 64 - 78 + 4 = 14
|
|
2420
|
+
assert_eq!(runMerged::<i64>("constlist", &src), 14);
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
#[test]
|
|
2424
|
+
fn value_nested_pattern() {
|
|
2425
|
+
let src = format!(
|
|
2426
|
+
"{VEC2}value enum Rect =\n | Rect(pos: Vec2, size: Vec2)\n\nfun f(r: Rect) -> Float =\n match r\n Rect(Vec2(px, py), size) => px + py * 10.0 + size.x * 100.0 + size.y * 1000.0\n\nfun g(v: Vec2) -> Float =\n match v\n Vec2(a, b) => a - b\n\nfun main() -> Int =\n r := Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0))\n if f(r) == 4321.0 && g(r.pos) == 0.0 - 1.0\n return 1\n return 0\n"
|
|
2427
|
+
);
|
|
2428
|
+
assert_eq!(runMerged::<i64>("value_nested_pattern", &src), 1);
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
#[test]
|
|
2432
|
+
fn local_value_subfield_assign() {
|
|
2433
|
+
let src = format!(
|
|
2434
|
+
"{VEC2}value enum Rect =\n | Rect(pos: Vec2, size: Vec2)\n\nfun main() -> Int =\n r := Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0))\n r.pos.x = 7.0\n r.size.y = 9.0\n if r.pos.x == 7.0 && r.pos.y == 2.0 && r.size.x == 3.0 && r.size.y == 9.0\n return 1\n return 0\n"
|
|
2435
|
+
);
|
|
2436
|
+
assert_eq!(runMerged::<i64>("local_value_subfield_assign", &src), 1);
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
#[test]
|
|
2440
|
+
fn heap_field_value_subfield_assign() {
|
|
2441
|
+
let src = format!(
|
|
2442
|
+
"{VEC2}enum Player =\n | Player(pos: Vec2, hp: Int)\n\nvalue enum Rect =\n | Rect(pos: Vec2, size: Vec2)\n\nenum Body =\n | Body(bounds: Rect, id: Int)\n\nfun main() -> Int =\n p := Player(pos: Vec2(x: 1.0, y: 2.0), hp: 5)\n p.pos.x = 8.0\n b := Body(bounds: Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0)), id: 1)\n b.bounds.size.y = 6.0\n if p.pos.x == 8.0 && p.pos.y == 2.0 && p.hp == 5 && b.bounds.size.y == 6.0 && b.bounds.size.x == 3.0 && b.bounds.pos.y == 2.0\n return 1\n return 0\n"
|
|
2443
|
+
);
|
|
2444
|
+
assert_eq!(runMerged::<i64>("heap_field_value_subfield_assign", &src), 1);
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
#[test]
|
|
2448
|
+
fn value_nested_pattern_mixed() {
|
|
2449
|
+
// Whole-value binding next to a nested destructure, and a heap container
|
|
2450
|
+
// (class-like enum) holding a value-enum field.
|
|
2451
|
+
let src = format!(
|
|
2452
|
+
"{VEC2}value enum Rect =\n | Rect(pos: Vec2, size: Vec2)\n\nenum Player =\n | Player(pos: Vec2, hp: Float)\n\nfun f(r: Rect) -> Float =\n match r\n Rect(p, Vec2(w, h)) => p.x + p.y * 10.0 + w * 100.0 + h * 1000.0\n\nfun g(p: Player) -> Float =\n match p\n Player(Vec2(x, y), hp) => x + y * 10.0 + hp * 100.0\n\nfun main() -> Int =\n r := Rect(pos: Vec2(x: 1.0, y: 2.0), size: Vec2(x: 3.0, y: 4.0))\n p := Player(pos: Vec2(x: 1.0, y: 2.0), hp: 3.0)\n if f(r) == 4321.0 && g(p) == 321.0\n return 1\n return 0\n"
|
|
2453
|
+
);
|
|
2454
|
+
assert_eq!(runMerged::<i64>("value_nested_pattern_mixed", &src), 1);
|
|
2455
|
+
}
|