plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
aa923a9
— Peter John
2026-11-02T20:24:55+05:30
T10: box-free trait layout (wasm-gc subtyping)
- plum-core/src/ast.rs +3 -3
- plum-core/src/parser.rs +2 -0
- plum-core/src/traits.rs +2 -1
- plum-core/src/union_lower.rs +2 -2
- plum-core/tests/traits_test.rs +15 -0
- plum-wasm-codegen/src/lib.rs +13 -2
- plum-wasm-codegen/tests/traits_e2e_test.rs +54 -0
plum-core/src/ast.rs
CHANGED
|
@@ -172,12 +172,12 @@ pub struct Enum {
|
|
|
172
172
|
/// `record X[T](..)`: a generic single-constructor heap type.
|
|
173
173
|
pub isRecord: bool,
|
|
174
174
|
/// Traits listed first in a `record`/`struct` field list (`record H(E, k: Int)`).
|
|
175
|
-
/// On a lowered trait enum (`isTrait`): the embedding records when the trait is
|
|
176
|
-
/// *subtyped* (every embedder lists it first, so their struct types extend the
|
|
177
|
-
/// trait's base struct and a conversion needs no box); empty otherwise.
|
|
178
175
|
pub embeds: Vec<String>,
|
|
179
176
|
/// A union enum produced by lowering a `trait`.
|
|
180
177
|
pub isTrait: bool,
|
|
178
|
+
/// On a lowered trait enum: every embedder lists the trait first, so the layout
|
|
179
|
+
/// is wasm-gc subtyping (no box). The variants are the embedding records.
|
|
180
|
+
pub isSubtypedTrait: bool,
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
#[derive(Debug, Clone, PartialEq)]
|
plum-core/src/parser.rs
CHANGED
|
@@ -300,6 +300,7 @@ impl<'a> AstParser<'a> {
|
|
|
300
300
|
isRecord: false,
|
|
301
301
|
embeds: Vec::new(),
|
|
302
302
|
isTrait: false,
|
|
303
|
+
isSubtypedTrait: false,
|
|
303
304
|
name,
|
|
304
305
|
line: pos.row + 1,
|
|
305
306
|
column: pos.column + 1,
|
|
@@ -376,6 +377,7 @@ impl<'a> AstParser<'a> {
|
|
|
376
377
|
isRecord: false,
|
|
377
378
|
embeds,
|
|
378
379
|
isTrait: false,
|
|
380
|
+
isSubtypedTrait: false,
|
|
379
381
|
name,
|
|
380
382
|
line: pos.row + 1,
|
|
381
383
|
column: pos.column + 1,
|
plum-core/src/traits.rs
CHANGED
|
@@ -248,8 +248,9 @@ pub fn lowerTraits(source: &mut Source) -> Vec<String> {
|
|
|
248
248
|
variants,
|
|
249
249
|
isValue: false,
|
|
250
250
|
isRecord: false,
|
|
251
|
-
embeds:
|
|
251
|
+
embeds: vec![],
|
|
252
252
|
isTrait: true,
|
|
253
|
+
isSubtypedTrait: subtyped,
|
|
253
254
|
};
|
|
254
255
|
source.traits.push(t.clone());
|
|
255
256
|
*item = Item::Enum(e);
|
plum-core/src/union_lower.rs
CHANGED
|
@@ -8,13 +8,13 @@ use std::collections::{HashMap, HashSet};
|
|
|
8
8
|
|
|
9
9
|
/// Applies `plan` to `source`.
|
|
10
10
|
pub fn applyPlan(source: &mut Source, plan: &UnionPlan) {
|
|
11
|
-
// Subtyped traits (see `Enum.
|
|
11
|
+
// Subtyped traits (see `Enum.isSubtypedTrait`) keep records as their own subtypes: no
|
|
12
12
|
// box, and trait field reads/writes stay plain field accesses for codegen.
|
|
13
13
|
let subtyped: HashSet<String> = source
|
|
14
14
|
.items
|
|
15
15
|
.iter()
|
|
16
16
|
.filter_map(|i| match i {
|
|
17
|
-
Item::Enum(e) if e.
|
|
17
|
+
Item::Enum(e) if e.isSubtypedTrait => Some(e.name.clone()),
|
|
18
18
|
_ => None,
|
|
19
19
|
})
|
|
20
20
|
.collect();
|
plum-core/tests/traits_test.rs
CHANGED
|
@@ -200,3 +200,18 @@ fn struct_typed_field_accessors_and_two_traits() {
|
|
|
200
200
|
assert_eq!(enumOf(&s, "Aa").variants.len(), 1);
|
|
201
201
|
assert_eq!(enumOf(&s, "Bb").variants[0].name, "Rr");
|
|
202
202
|
}
|
|
203
|
+
|
|
204
|
+
#[test]
|
|
205
|
+
fn subtyped_flag_explicit_and_idempotent() {
|
|
206
|
+
let mut s = parseRaw(
|
|
207
|
+
"trait Aa(p: Int)\ntrait Bb(q: Int)\ntrait Cc(r: Int)\nrecord Rr(Aa, Bb, k: Int)\nrecord Ss(Bb, Cc)\n",
|
|
208
|
+
);
|
|
209
|
+
assert!(lowerTraits(&mut s).is_empty());
|
|
210
|
+
assert!(enumOf(&s, "Aa").isSubtypedTrait);
|
|
211
|
+
assert!(!enumOf(&s, "Bb").isSubtypedTrait);
|
|
212
|
+
assert!(!enumOf(&s, "Cc").isSubtypedTrait);
|
|
213
|
+
assert!(enumOf(&s, "Aa").embeds.is_empty());
|
|
214
|
+
let before = s.clone();
|
|
215
|
+
assert!(lowerTraits(&mut s).is_empty());
|
|
216
|
+
assert_eq!(s, before);
|
|
217
|
+
}
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -1119,8 +1119,19 @@ fn subtypedTraits(source: &ast::Source) -> HashMap<String, Vec<String>> {
|
|
|
1119
1119
|
.items
|
|
1120
1120
|
.iter()
|
|
1121
1121
|
.filter_map(|i| match i {
|
|
1122
|
-
ast::Item::Enum(e) if e.
|
|
1122
|
+
ast::Item::Enum(e) if e.isSubtypedTrait => {
|
|
1123
|
+
Some((
|
|
1123
|
-
|
|
1124
|
+
e.name.clone(),
|
|
1125
|
+
e.variants
|
|
1126
|
+
.iter()
|
|
1127
|
+
.map(|v| {
|
|
1128
|
+
v.name
|
|
1129
|
+
.strip_prefix(&format!("{}__", e.name))
|
|
1130
|
+
.unwrap_or(&v.name)
|
|
1131
|
+
.to_string()
|
|
1132
|
+
})
|
|
1133
|
+
.collect(),
|
|
1134
|
+
))
|
|
1124
1135
|
}
|
|
1125
1136
|
_ => None,
|
|
1126
1137
|
})
|
plum-wasm-codegen/tests/traits_e2e_test.rs
CHANGED
|
@@ -325,3 +325,57 @@ fun main() -> Int =
|
|
|
325
325
|
// useExtra: n=11, b=8 -> 19; useBase: 4+5=9; t.n = 11
|
|
326
326
|
assert_eq!(runInt("boxed2", &src), 19000 + 90 + 11);
|
|
327
327
|
}
|
|
328
|
+
|
|
329
|
+
#[test]
|
|
330
|
+
fn nested_narrowing_two_levels() {
|
|
331
|
+
let src = prog("\
|
|
332
|
+
trait Actor(x: Int) =
|
|
333
|
+
fun hit(self) -> Int
|
|
334
|
+
|
|
335
|
+
record Dot(Actor, w: Int) =
|
|
336
|
+
fun hit(self) -> Int = self.w
|
|
337
|
+
|
|
338
|
+
record Cat(Actor) =
|
|
339
|
+
fun hit(self) -> Int = 1
|
|
340
|
+
|
|
341
|
+
record Holder(a: Actor)
|
|
342
|
+
record Outer(h: Holder)
|
|
343
|
+
|
|
344
|
+
fun f(o: Outer) -> Int =
|
|
345
|
+
match o
|
|
346
|
+
Outer(Holder(Dot(x, w))) => x * 100 + w
|
|
347
|
+
_ => 5
|
|
348
|
+
|
|
349
|
+
fun main() -> Int =
|
|
350
|
+
f(Outer(h: Holder(a: Dot(x: 4, w: 9)))) * 10 + f(Outer(h: Holder(a: Cat(x: 1))))
|
|
351
|
+
");
|
|
352
|
+
assert_eq!(runInt("nest2", &src), 4090 + 5);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
#[test]
|
|
356
|
+
fn subtyped_with_boxed_second_self_list_and_eq() {
|
|
357
|
+
let src = prog("\
|
|
358
|
+
trait Actor(x: Int, kids: List[Actor]) =
|
|
359
|
+
fun hit(self) -> Int
|
|
360
|
+
|
|
361
|
+
trait Extra(n: Int) =
|
|
362
|
+
fun b(self) -> Int
|
|
363
|
+
|
|
364
|
+
record Dot(Actor, Extra, w: Int) =
|
|
365
|
+
fun hit(self) -> Int = self.w
|
|
366
|
+
fun b(self) -> Int = self.n
|
|
367
|
+
|
|
368
|
+
fun main() -> Int =
|
|
369
|
+
d := Dot(x: 1, kids: List[Actor](), n: 6, w: 3)
|
|
370
|
+
a: Actor := d
|
|
371
|
+
e: Extra := d
|
|
372
|
+
e.n = 8
|
|
373
|
+
a.x = 2
|
|
374
|
+
a2: Actor := d
|
|
375
|
+
same := 0
|
|
376
|
+
if a == a2
|
|
377
|
+
same = 1
|
|
378
|
+
a.hit() * 1000 + d.n * 100 + d.x * 10 + same
|
|
379
|
+
");
|
|
380
|
+
assert_eq!(runInt("mix", &src), 3000 + 800 + 20 + 1);
|
|
381
|
+
}
|