plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


e02a80a — Peter John 2026-11-01T20:31:34+05:30
T6: applyPlan rewrite
plum-core/src/union_lower.rs ADDED
@@ -0,0 +1,593 @@
1
+ //! Rewrites a program in place from a `UnionPlan`: wraps member values in their
2
+ //! hidden variants, rewrites patterns, narrows subjects inside match arms and
3
+ //! synthesizes dispatch methods on enums. Post-order (children first), so every
4
+ //! recorded address is still valid when its node is visited.
5
+ use crate::ast::*;
6
+ use crate::unions::UnionPlan;
7
+ use std::collections::{HashMap, HashSet};
8
+
9
+ /// Applies `plan` to `source`.
10
+ pub fn applyPlan(source: &mut Source, plan: &UnionPlan) {
11
+ let mut lw = Lowerer { plan, pending: Vec::new(), needs: Vec::new() };
12
+ for item in &mut source.items {
13
+ match item {
14
+ Item::Fn(f) => match &mut f.body {
15
+ FnBody::Expr(e) => lw.expr(e),
16
+ FnBody::Block(b) => lw.block(b),
17
+ FnBody::Extern => {}
18
+ },
19
+ Item::Const(c) => lw.expr(&mut c.value),
20
+ Item::Test(t) => lw.block(&mut t.body),
21
+ _ => {}
22
+ }
23
+ }
24
+ // Items are only appended after the traversal: pushing moves addresses.
25
+ let mut counter = 0usize;
26
+ let mut done: HashSet<(String, String)> = HashSet::new();
27
+ for (variants, method) in std::mem::take(&mut lw.needs) {
28
+ let Some(enumName) = variants.first().and_then(|v| v.split("__").next()) else { continue };
29
+ let enumName = enumName.to_string();
30
+ if !done.insert((enumName.clone(), method.clone())) {
31
+ continue;
32
+ }
33
+ if let Some(f) = synthesize(source, &enumName, &variants, &method, &mut counter) {
34
+ source.items.push(Item::Fn(f));
35
+ }
36
+ }
37
+ }
38
+
39
+ struct Lowerer<'a> {
40
+ plan: &'a UnionPlan,
41
+ /// Narrowings (from, to) found in the condition being visited.
42
+ pending: Vec<(String, String)>,
43
+ /// (variants, method name) of every dispatch seen.
44
+ needs: Vec<(Vec<String>, String)>,
45
+ }
46
+
47
+ fn addr<T>(t: &T) -> usize {
48
+ t as *const T as usize
49
+ }
50
+
51
+ impl<'a> Lowerer<'a> {
52
+ fn block(&mut self, b: &mut Block) {
53
+ for s in &mut b.stmts {
54
+ self.stmt(s);
55
+ }
56
+ }
57
+
58
+ fn stmt(&mut self, s: &mut Stmt) {
59
+ let mark = self.pending.len();
60
+ match s {
61
+ Stmt::Assign(a) => {
62
+ for t in &mut a.targets {
63
+ if let AssignTarget::Field(o, _) = t {
64
+ self.expr(o);
65
+ }
66
+ }
67
+ for v in &mut a.values {
68
+ self.expr(v);
69
+ }
70
+ }
71
+ Stmt::Assert(c) => self.expr(&mut c.cond),
72
+ Stmt::ForLoop(f) => {
73
+ self.expr(&mut f.source);
74
+ for c in &mut f.clauses {
75
+ match c {
76
+ ForClause::If(e) | ForClause::Bind(_, e) => self.expr(e),
77
+ }
78
+ }
79
+ self.block(&mut f.body);
80
+ }
81
+ Stmt::While(w) => {
82
+ self.expr(&mut w.condition);
83
+ let pairs = self.pending.split_off(mark);
84
+ self.block(&mut w.body);
85
+ renameBlock(&mut w.body, &pairs);
86
+ }
87
+ Stmt::If(i) => {
88
+ self.expr(&mut i.condition);
89
+ let pairs = self.pending.split_off(mark);
90
+ self.block(&mut i.body);
91
+ renameBlock(&mut i.body, &pairs);
92
+ for ei in &mut i.else_ifs {
93
+ self.expr(&mut ei.condition);
94
+ let pairs = self.pending.split_off(mark);
95
+ self.block(&mut ei.body);
96
+ renameBlock(&mut ei.body, &pairs);
97
+ }
98
+ if let Some(b) = &mut i.else_ {
99
+ self.block(b);
100
+ }
101
+ }
102
+ Stmt::Match(m) => self.matchStmt(m),
103
+ Stmt::Return(Some(e)) | Stmt::Crash(e) | Stmt::Expr(e, ..) => self.expr(e),
104
+ Stmt::Return(None) | Stmt::Break | Stmt::Continue | Stmt::Todo => {}
105
+ }
106
+ self.pending.truncate(mark);
107
+ }
108
+
109
+ fn matchStmt(&mut self, m: &mut Match) {
110
+ for s in &mut m.subjects {
111
+ self.expr(s);
112
+ }
113
+ let subjects = m.subjects.clone();
114
+ for c in &mut m.cases {
115
+ self.caseArm(c, &subjects);
116
+ }
117
+ }
118
+
119
+ /// Visits one case (children first), applies its pattern rewrite and its
120
+ /// narrowing. Returns the (from, to) rename of a narrowing.
121
+ fn caseArm(&mut self, c: &mut Case, subjects: &[Expr]) -> Option<(String, String)> {
122
+ let caseAddr = addr(c);
123
+ for p in &mut c.patterns {
124
+ self.pattern(p);
125
+ }
126
+ if let Some(g) = &mut c.guard {
127
+ self.expr(g);
128
+ }
129
+ self.block(&mut c.body);
130
+ let (subject, variant, fresh) = self.plan.narrowings.get(&caseAddr)?.clone();
131
+ let idx = subjects.iter().position(|s| match s {
132
+ Expr::Var(n) => *n == subject,
133
+ Expr::Self_ => subject == "self",
134
+ _ => false,
135
+ })?;
136
+ let p = c.patterns.get_mut(idx)?;
137
+ match p {
138
+ CasePattern::Name(_) => {
139
+ *p = CasePattern::Class { name: variant.clone(), fields: vec![CasePattern::Name(fresh.clone())] };
140
+ }
141
+ CasePattern::Class { name, fields }
142
+ if *name == variant && matches!(fields.as_slice(), [CasePattern::Wildcard]) =>
143
+ {
144
+ fields[0] = CasePattern::Name(fresh.clone());
145
+ }
146
+ _ => return None,
147
+ }
148
+ let pair = vec![(subject.clone(), fresh.clone())];
149
+ if let Some(g) = &mut c.guard {
150
+ renameExpr(g, &pair);
151
+ }
152
+ renameBlock(&mut c.body, &pair);
153
+ Some((subject, fresh))
154
+ }
155
+
156
+ fn pattern(&mut self, p: &mut CasePattern) {
157
+ let a = addr(p);
158
+ if let CasePattern::Class { fields, .. } = p {
159
+ for f in fields {
160
+ self.pattern(f);
161
+ }
162
+ }
163
+ if let Some(variant) = self.plan.patterns.get(&a) {
164
+ let inner = match p {
165
+ CasePattern::Name(_) => CasePattern::Wildcard,
166
+ _ => std::mem::replace(p, CasePattern::Wildcard),
167
+ };
168
+ *p = CasePattern::Class { name: variant.clone(), fields: vec![inner] };
169
+ }
170
+ }
171
+
172
+ fn args(&mut self, args: &mut [Arg]) {
173
+ for a in args {
174
+ match a {
175
+ Arg::Positional(e) | Arg::Keyword { value: e, .. } => self.expr(e),
176
+ }
177
+ }
178
+ }
179
+
180
+ fn expr(&mut self, e: &mut Expr) {
181
+ let a = addr(e);
182
+ match e {
183
+ Expr::Binary(b) => {
184
+ self.expr(&mut b.left);
185
+ self.expr(&mut b.right);
186
+ }
187
+ Expr::Unary(u) => self.expr(&mut u.operand),
188
+ Expr::Bool(b) => {
189
+ self.expr(&mut b.left);
190
+ let mark = self.pending.len();
191
+ let _ = mark;
192
+ // Narrowings found on the left are visible on the right of `&&`.
193
+ let leftPairs: Vec<(String, String)> = self.pending.clone();
194
+ self.expr(&mut b.right);
195
+ if b.op == BoolOp::And && !leftPairs.is_empty() {
196
+ renameExpr(&mut b.right, &leftPairs);
197
+ }
198
+ }
199
+ Expr::Not(x) | Expr::Try(x) | Expr::Paren(x) => self.expr(x),
200
+ Expr::Compare(c) => {
201
+ self.expr(&mut c.left);
202
+ self.expr(&mut c.right);
203
+ }
204
+ Expr::InlineIf(i) => {
205
+ let mark = self.pending.len();
206
+ self.expr(&mut i.condition);
207
+ let pairs = self.pending.split_off(mark);
208
+ self.expr(&mut i.then);
209
+ renameExpr(&mut i.then, &pairs);
210
+ self.expr(&mut i.else_);
211
+ }
212
+ Expr::FnCall(c) => self.args(&mut c.args),
213
+ Expr::Yield(es) => es.iter_mut().for_each(|x| self.expr(x)),
214
+ Expr::ClassCall(c) => {
215
+ for f in &mut c.fields {
216
+ self.expr(&mut f.value);
217
+ }
218
+ if let Some(s) = &mut c.spread {
219
+ self.expr(s);
220
+ }
221
+ }
222
+ Expr::Attribute(at) => {
223
+ self.expr(&mut at.object);
224
+ if let AttrKind::Method(c) = &mut at.attr {
225
+ self.args(&mut c.args);
226
+ }
227
+ }
228
+ Expr::Bind(b) => {
229
+ self.expr(&mut b.value);
230
+ let subjects = [b.value.clone()];
231
+ if let Some(pair) = self.caseArm(&mut b.case, &subjects) {
232
+ self.pending.push(pair);
233
+ }
234
+ }
235
+ Expr::Bang(b) => self.expr(&mut b.value),
236
+ Expr::Catch(c) => {
237
+ self.expr(&mut c.value);
238
+ match &mut c.handler {
239
+ CatchHandler::Default(d) => self.expr(d),
240
+ CatchHandler::Block { body, .. } => self.block(body),
241
+ }
242
+ }
243
+ Expr::String(s) => {
244
+ for p in &mut s.parts {
245
+ if let StringPart::Interp(x) = p {
246
+ self.expr(x);
247
+ }
248
+ }
249
+ }
250
+ Expr::Closure(c) => self.block(&mut c.body),
251
+ Expr::ErrorValue(_)
252
+ | Expr::Int(_)
253
+ | Expr::Float(_)
254
+ | Expr::BoolLit(_)
255
+ | Expr::Self_
256
+ | Expr::Var(_)
257
+ | Expr::TypeName(_) => {}
258
+ }
259
+ if let Some(variants) = self.plan.dispatches.get(&a) {
260
+ let method = match e {
261
+ Expr::Attribute(at) => match &at.attr {
262
+ AttrKind::Method(c) => c.name.clone(),
263
+ AttrKind::Field(_) => "toStr".to_string(),
264
+ },
265
+ _ => "toStr".to_string(),
266
+ };
267
+ self.needs.push((variants.clone(), method));
268
+ }
269
+ if let Some(variant) = self.plan.wraps.get(&a) {
270
+ let inner = std::mem::replace(e, Expr::Int(0));
271
+ *e = Expr::FnCall(FnCall {
272
+ name: variant.clone(),
273
+ args: vec![Arg::Positional(inner)],
274
+ line: 0,
275
+ column: 0,
276
+ });
277
+ }
278
+ }
279
+ }
280
+
281
+ // ---- scope-aware renaming of a narrowed subject ---------------------------
282
+
283
+ type Pairs = [(String, String)];
284
+
285
+ fn renameBlock(b: &mut Block, pairs: &Pairs) {
286
+ for (from, to) in pairs {
287
+ renameStmts(&mut b.stmts, from, to);
288
+ }
289
+ }
290
+
291
+ fn renameExpr(e: &mut Expr, pairs: &Pairs) {
292
+ for (from, to) in pairs {
293
+ rnExpr(e, from, to);
294
+ }
295
+ }
296
+
297
+ fn patBinds(p: &CasePattern, name: &str) -> bool {
298
+ match p {
299
+ CasePattern::Name(n) => n == name,
300
+ CasePattern::Class { fields, .. } => fields.iter().any(|f| patBinds(f, name)),
301
+ _ => false,
302
+ }
303
+ }
304
+
305
+ fn condBinds(e: &Expr, name: &str) -> bool {
306
+ match e {
307
+ Expr::Bind(b) => patBinds(b.pattern(), name),
308
+ Expr::Bool(b) => condBinds(&b.left, name) || condBinds(&b.right, name),
309
+ Expr::Paren(x) => condBinds(x, name),
310
+ _ => false,
311
+ }
312
+ }
313
+
314
+ /// Renames in a statement list; a later `name := ...` declaration shadows the rest.
315
+ fn renameStmts(stmts: &mut [Stmt], from: &str, to: &str) {
316
+ for s in stmts {
317
+ rnStmt(s, from, to);
318
+ if let Stmt::Assign(a) = s {
319
+ if a.declare && a.targets.iter().any(|t| matches!(t, AssignTarget::Var(n) if n == from)) {
320
+ return;
321
+ }
322
+ }
323
+ }
324
+ }
325
+
326
+ fn rnBlock(b: &mut Block, from: &str, to: &str) {
327
+ renameStmts(&mut b.stmts, from, to);
328
+ }
329
+
330
+ fn rnStmt(s: &mut Stmt, from: &str, to: &str) {
331
+ match s {
332
+ Stmt::Assign(a) => {
333
+ for v in &mut a.values {
334
+ rnExpr(v, from, to);
335
+ }
336
+ for t in &mut a.targets {
337
+ match t {
338
+ AssignTarget::Field(o, _) => rnExpr(o, from, to),
339
+ AssignTarget::Var(n) if !a.declare && n == from => *n = to.to_string(),
340
+ _ => {}
341
+ }
342
+ }
343
+ }
344
+ Stmt::Assert(c) => rnExpr(&mut c.cond, from, to),
345
+ Stmt::ForLoop(f) => {
346
+ rnExpr(&mut f.source, from, to);
347
+ for c in &mut f.clauses {
348
+ match c {
349
+ ForClause::If(e) | ForClause::Bind(_, e) => rnExpr(e, from, to),
350
+ }
351
+ }
352
+ if !f.binders.iter().any(|b| b == from) {
353
+ rnBlock(&mut f.body, from, to);
354
+ }
355
+ }
356
+ Stmt::While(w) => {
357
+ rnExpr(&mut w.condition, from, to);
358
+ if !condBinds(&w.condition, from) {
359
+ rnBlock(&mut w.body, from, to);
360
+ }
361
+ }
362
+ Stmt::If(i) => {
363
+ rnExpr(&mut i.condition, from, to);
364
+ if !condBinds(&i.condition, from) {
365
+ rnBlock(&mut i.body, from, to);
366
+ }
367
+ for ei in &mut i.else_ifs {
368
+ rnExpr(&mut ei.condition, from, to);
369
+ if !condBinds(&ei.condition, from) {
370
+ rnBlock(&mut ei.body, from, to);
371
+ }
372
+ }
373
+ if let Some(b) = &mut i.else_ {
374
+ rnBlock(b, from, to);
375
+ }
376
+ }
377
+ Stmt::Match(m) => {
378
+ for x in &mut m.subjects {
379
+ rnExpr(x, from, to);
380
+ }
381
+ for c in &mut m.cases {
382
+ rnCase(c, from, to);
383
+ }
384
+ }
385
+ Stmt::Return(Some(e)) | Stmt::Crash(e) | Stmt::Expr(e, ..) => rnExpr(e, from, to),
386
+ Stmt::Return(None) | Stmt::Break | Stmt::Continue | Stmt::Todo => {}
387
+ }
388
+ }
389
+
390
+ fn rnCase(c: &mut Case, from: &str, to: &str) {
391
+ if c.patterns.iter().any(|p| patBinds(p, from)) {
392
+ return;
393
+ }
394
+ if let Some(g) = &mut c.guard {
395
+ rnExpr(g, from, to);
396
+ }
397
+ rnBlock(&mut c.body, from, to);
398
+ }
399
+
400
+ fn rnArgs(args: &mut [Arg], from: &str, to: &str) {
401
+ for a in args {
402
+ match a {
403
+ Arg::Positional(e) | Arg::Keyword { value: e, .. } => rnExpr(e, from, to),
404
+ }
405
+ }
406
+ }
407
+
408
+ fn rnExpr(e: &mut Expr, from: &str, to: &str) {
409
+ match e {
410
+ Expr::Var(n) if n == from => *n = to.to_string(),
411
+ Expr::Self_ if from == "self" => *e = Expr::Var(to.to_string()),
412
+ Expr::Binary(b) => {
413
+ rnExpr(&mut b.left, from, to);
414
+ rnExpr(&mut b.right, from, to);
415
+ }
416
+ Expr::Unary(u) => rnExpr(&mut u.operand, from, to),
417
+ Expr::Bool(b) => {
418
+ rnExpr(&mut b.left, from, to);
419
+ if !(b.op == BoolOp::And && condBinds(&b.left, from)) {
420
+ rnExpr(&mut b.right, from, to);
421
+ }
422
+ }
423
+ Expr::Not(x) | Expr::Try(x) | Expr::Paren(x) => rnExpr(x, from, to),
424
+ Expr::Compare(c) => {
425
+ rnExpr(&mut c.left, from, to);
426
+ rnExpr(&mut c.right, from, to);
427
+ }
428
+ Expr::InlineIf(i) => {
429
+ rnExpr(&mut i.condition, from, to);
430
+ if !condBinds(&i.condition, from) {
431
+ rnExpr(&mut i.then, from, to);
432
+ }
433
+ rnExpr(&mut i.else_, from, to);
434
+ }
435
+ Expr::FnCall(c) => rnArgs(&mut c.args, from, to),
436
+ Expr::Yield(es) => es.iter_mut().for_each(|x| rnExpr(x, from, to)),
437
+ Expr::ClassCall(c) => {
438
+ for f in &mut c.fields {
439
+ rnExpr(&mut f.value, from, to);
440
+ }
441
+ if let Some(s) = &mut c.spread {
442
+ rnExpr(s, from, to);
443
+ }
444
+ }
445
+ Expr::Attribute(at) => {
446
+ rnExpr(&mut at.object, from, to);
447
+ if let AttrKind::Method(c) = &mut at.attr {
448
+ rnArgs(&mut c.args, from, to);
449
+ }
450
+ }
451
+ Expr::Bind(b) => {
452
+ rnExpr(&mut b.value, from, to);
453
+ if !patBinds(b.pattern(), from) {
454
+ if let Some(g) = &mut b.case.guard {
455
+ rnExpr(g, from, to);
456
+ }
457
+ }
458
+ }
459
+ Expr::Bang(b) => rnExpr(&mut b.value, from, to),
460
+ Expr::Catch(c) => {
461
+ rnExpr(&mut c.value, from, to);
462
+ match &mut c.handler {
463
+ CatchHandler::Default(d) => rnExpr(d, from, to),
464
+ CatchHandler::Block { binder, body } => {
465
+ if binder != from {
466
+ rnBlock(body, from, to);
467
+ }
468
+ }
469
+ }
470
+ }
471
+ Expr::String(s) => {
472
+ for p in &mut s.parts {
473
+ if let StringPart::Interp(x) = p {
474
+ rnExpr(x, from, to);
475
+ }
476
+ }
477
+ }
478
+ Expr::Closure(c) => {
479
+ if !c.params.iter().any(|p| p == from) {
480
+ rnBlock(&mut c.body, from, to);
481
+ }
482
+ }
483
+ _ => {}
484
+ }
485
+ }
486
+
487
+ // ---- dispatch method synthesis ---------------------------------------------
488
+
489
+ fn substType(t: &Type, map: &HashMap<String, Type>) -> Type {
490
+ if t.generics.is_empty() {
491
+ if let Some(r) = map.get(&t.name) {
492
+ return r.clone();
493
+ }
494
+ }
495
+ Type { name: t.name.clone(), generics: t.generics.iter().map(|g| substType(g, map)).collect() }
496
+ }
497
+
498
+ fn substParam(p: &ParamType, map: &HashMap<String, Type>) -> ParamType {
499
+ match p {
500
+ ParamType::Type(t) => ParamType::Type(substType(t, map)),
501
+ ParamType::Variadic(t) => ParamType::Variadic(substType(t, map)),
502
+ ParamType::Seq(t) => ParamType::Seq(substType(t, map)),
503
+ ParamType::Fn(ps, r) => ParamType::Fn(
504
+ ps.iter().map(|t| substType(t, map)).collect(),
505
+ r.as_ref().map(|t| Box::new(substType(t, map))),
506
+ ),
507
+ }
508
+ }
509
+
510
+ /// Builds `fun <method>(self, ...) = match self` over `variants`, with the
511
+ /// signature of the first member type that declares `method`.
512
+ fn synthesize(
513
+ source: &Source,
514
+ enumName: &str,
515
+ variants: &[String],
516
+ method: &str,
517
+ counter: &mut usize,
518
+ ) -> Option<Fn> {
519
+ let exists = source.items.iter().any(|i| {
520
+ matches!(i, Item::Fn(f) if f.type_param.as_deref() == Some(enumName) && f.name == method)
521
+ });
522
+ if exists {
523
+ return None;
524
+ }
525
+ let enumItem = source.items.iter().find_map(|i| match i {
526
+ Item::Enum(e) if e.name == enumName => Some(e),
527
+ _ => None,
528
+ })?;
529
+ // Signature: first member that declares the method.
530
+ let mut sig: Option<(&Fn, HashMap<String, Type>)> = None;
531
+ for vname in variants {
532
+ let Some(member) = enumItem.variants.iter().find(|v| &v.name == vname).and_then(|v| v.fields.first()) else {
533
+ continue;
534
+ };
535
+ let decl = source.items.iter().find_map(|i| match i {
536
+ Item::Fn(f) if f.type_param.as_deref() == Some(member.name.as_str()) && f.name == method => Some(f),
537
+ _ => None,
538
+ });
539
+ if let Some(f) = decl {
540
+ let mut map = HashMap::new();
541
+ let generics = source.items.iter().find_map(|i| match i {
542
+ Item::Enum(me) if me.name == member.name => Some(&me.generics),
543
+ _ => None,
544
+ });
545
+ if let Some(gs) = generics {
546
+ for (g, arg) in gs.iter().zip(member.generics.iter()) {
547
+ map.insert(g.name.clone(), arg.clone());
548
+ }
549
+ }
550
+ sig = Some((f, map));
551
+ break;
552
+ }
553
+ }
554
+ let (decl, map) = sig?;
555
+ let params: Vec<Param> = decl
556
+ .params
557
+ .iter()
558
+ .map(|p| Param { name: p.name.clone(), ty: substParam(&p.ty, &map), default: p.default.clone() })
559
+ .collect();
560
+ let cases: Vec<Case> = variants
561
+ .iter()
562
+ .map(|v| {
563
+ *counter += 1;
564
+ let d = format!("d__{counter}");
565
+ let args = params.iter().map(|p| Arg::Positional(Expr::Var(p.name.clone()))).collect();
566
+ let call = Expr::Attribute(Box::new(AttributeExpr {
567
+ object: Expr::Var(d.clone()),
568
+ attr: AttrKind::Method(FnCall { name: method.to_string(), args, line: 0, column: 0 }),
569
+ }));
570
+ Case {
571
+ patterns: vec![CasePattern::Class { name: v.clone(), fields: vec![CasePattern::Name(d)] }],
572
+ guard: None,
573
+ body: Block { stmts: vec![Stmt::Expr(call, 0, 0)] },
574
+ }
575
+ })
576
+ .collect();
577
+ Some(Fn {
578
+ name: method.to_string(),
579
+ file: decl.file.clone(),
580
+ line: 0,
581
+ column: 0,
582
+ type_param: Some(enumName.to_string()),
583
+ is_extern: false,
584
+ is_star: false,
585
+ has_self: true,
586
+ params,
587
+ yields: None,
588
+ returns: decl.returns.as_ref().map(|t| substType(t, &map)),
589
+ body: FnBody::Block(Block {
590
+ stmts: vec![Stmt::Match(Match { subjects: vec![Expr::Self_], cases })],
591
+ }),
592
+ })
593
+ }
plum-core/tests/union_lower_test.rs ADDED
@@ -0,0 +1,329 @@
1
+ #![allow(non_snake_case)]
2
+ use plum_core::ast::*;
3
+ use plum_core::union_lower::applyPlan;
4
+ use plum_core::unions::UnionPlan;
5
+ use plum_core::AstParser;
6
+
7
+ fn parse(src: &str) -> Source {
8
+ let mut parser = tree_sitter::Parser::new();
9
+ parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
10
+ let tree = parser.parse(src, None).unwrap();
11
+ assert!(!tree.root_node().has_error(), "{}", tree.root_node().to_sexp());
12
+ AstParser::new(src).parseSource(tree.root_node())
13
+ }
14
+
15
+ fn fnNamed<'a>(s: &'a Source, name: &str) -> &'a Fn {
16
+ s.items
17
+ .iter()
18
+ .find_map(|i| match i {
19
+ Item::Fn(f) if f.name == name => Some(f),
20
+ _ => None,
21
+ })
22
+ .unwrap()
23
+ }
24
+
25
+ fn stmts<'a>(s: &'a Source, name: &str) -> &'a Vec<Stmt> {
26
+ match &fnNamed(s, name).body {
27
+ FnBody::Block(b) => &b.stmts,
28
+ _ => panic!("block body expected"),
29
+ }
30
+ }
31
+
32
+ fn exprOf(s: &Stmt) -> &Expr {
33
+ match s {
34
+ Stmt::Expr(e, ..) => e,
35
+ _ => panic!("expr stmt expected"),
36
+ }
37
+ }
38
+
39
+ fn matchOf(s: &Stmt) -> &Match {
40
+ match s {
41
+ Stmt::Match(m) => m,
42
+ _ => panic!("match expected"),
43
+ }
44
+ }
45
+
46
+ fn addr<T>(t: &T) -> usize {
47
+ t as *const T as usize
48
+ }
49
+
50
+ fn varName(e: &Expr) -> &str {
51
+ match e {
52
+ Expr::Var(n) => n,
53
+ _ => panic!("var expected, got {e:?}"),
54
+ }
55
+ }
56
+
57
+ /// `obj.field` -> the object's variable name.
58
+ fn fieldObj(e: &Expr) -> &str {
59
+ match e {
60
+ Expr::Attribute(a) => varName(&a.object),
61
+ _ => panic!("attribute expected, got {e:?}"),
62
+ }
63
+ }
64
+
65
+ const SHAPES: &str = "struct Circle(r: Float) =\n fun area(self) -> Float = 1.0\n fun scale(self, k: Float, j: Float) -> Float = 2.0\n fun toStr(self) -> Str = \"c\"\nstruct Square(w: Float) =\n fun area(self) -> Float = 2.0\n fun scale(self, k: Float, j: Float) -> Float = 3.0\n fun toStr(self) -> Str = \"s\"\nenum Shape = Circle | Square\n";
66
+
67
+ fn withShapes(rest: &str) -> Source {
68
+ parse(&format!("{SHAPES}{rest}"))
69
+ }
70
+
71
+ /// The parser's own positional variant call, with positions zeroed.
72
+ fn parsedCall(src: &str) -> Expr {
73
+ let s = parse(&format!("fun g() =\n {src}\n"));
74
+ let mut e = exprOf(&stmts(&s, "g")[0]).clone();
75
+ if let Expr::FnCall(c) = &mut e {
76
+ c.line = 0;
77
+ c.column = 0;
78
+ }
79
+ e
80
+ }
81
+
82
+ #[test]
83
+ fn wrap_builds_the_parsers_variant_call() {
84
+ let mut s = parse("fun f() =\n Circle(r: 2.0)\n");
85
+ let a = addr(exprOf(&stmts(&s, "f")[0]));
86
+ let mut plan = UnionPlan::default();
87
+ plan.wraps.insert(a, "ShapeCircle".to_string());
88
+ applyPlan(&mut s, &plan);
89
+ assert_eq!(*exprOf(&stmts(&s, "f")[0]), parsedCall("ShapeCircle(Circle(r: 2.0))"));
90
+ }
91
+
92
+ #[test]
93
+ fn pattern_class_and_bare() {
94
+ let mut s = parse("fun f(s: Shape) =\n match s\n Circle(r) =>\n r\n Square =>\n 0.0\n");
95
+ let m = matchOf(&stmts(&s, "f")[0]);
96
+ let a0 = addr(&m.cases[0].patterns[0]);
97
+ let a1 = addr(&m.cases[1].patterns[0]);
98
+ let mut plan = UnionPlan::default();
99
+ plan.patterns.insert(a0, "Shape__Circle".into());
100
+ plan.patterns.insert(a1, "Shape__Square".into());
101
+ applyPlan(&mut s, &plan);
102
+ let m = matchOf(&stmts(&s, "f")[0]);
103
+ assert_eq!(
104
+ m.cases[0].patterns[0],
105
+ CasePattern::Class {
106
+ name: "Shape__Circle".into(),
107
+ fields: vec![CasePattern::Class { name: "Circle".into(), fields: vec![CasePattern::Name("r".into())] }]
108
+ }
109
+ );
110
+ assert_eq!(
111
+ m.cases[1].patterns[0],
112
+ CasePattern::Class { name: "Shape__Square".into(), fields: vec![CasePattern::Wildcard] }
113
+ );
114
+ }
115
+
116
+ fn narrowPlan(s: &Source, fnName: &str, subject: &str, variant: &str, fresh: &str) -> UnionPlan {
117
+ let m = matchOf(&stmts(s, fnName)[0]);
118
+ let mut plan = UnionPlan::default();
119
+ plan.narrowings.insert(addr(&m.cases[0]), (subject.into(), variant.into(), fresh.into()));
120
+ plan
121
+ }
122
+
123
+ #[test]
124
+ fn narrow_renames_guard_and_body() {
125
+ let mut s = parse("fun f(s: Shape) =\n match s\n Circle =>\n s.r\n Square =>\n s.w\n");
126
+ let plan = narrowPlan(&s, "f", "s", "Shape__Circle", "s__n1");
127
+ applyPlan(&mut s, &plan);
128
+ let m = matchOf(&stmts(&s, "f")[0]);
129
+ assert_eq!(
130
+ m.cases[0].patterns[0],
131
+ CasePattern::Class { name: "Shape__Circle".into(), fields: vec![CasePattern::Name("s__n1".into())] }
132
+ );
133
+ assert_eq!(fieldObj(exprOf(&m.cases[0].body.stmts[0])), "s__n1");
134
+ // the other arm is untouched
135
+ assert_eq!(fieldObj(exprOf(&m.cases[1].body.stmts[0])), "s");
136
+ }
137
+
138
+ #[test]
139
+ fn narrow_closure_capture() {
140
+ let mut s = parse("fun f(s: Shape) =\n match s\n Circle =>\n g := |x| s.r\n g(1)\n");
141
+ let plan = narrowPlan(&s, "f", "s", "Shape__Circle", "s__n1");
142
+ applyPlan(&mut s, &plan);
143
+ let m = matchOf(&stmts(&s, "f")[0]);
144
+ let Stmt::Assign(a) = &m.cases[0].body.stmts[0] else { panic!() };
145
+ let Expr::Closure(c) = &a.values[0] else { panic!() };
146
+ assert_eq!(fieldObj(exprOf(&c.body.stmts[0])), "s__n1");
147
+ }
148
+
149
+ #[test]
150
+ fn narrow_respects_shadowing() {
151
+ let mut s = parse(
152
+ "fun f(s: Shape) =\n match s\n Circle =>\n s.r\n h := |s| s.w\n s := 5\n s.r\n",
153
+ );
154
+ let plan = narrowPlan(&s, "f", "s", "Shape__Circle", "s__n1");
155
+ applyPlan(&mut s, &plan);
156
+ let m = matchOf(&stmts(&s, "f")[0]);
157
+ let body = &m.cases[0].body.stmts;
158
+ assert_eq!(fieldObj(exprOf(&body[0])), "s__n1");
159
+ let Stmt::Assign(a) = &body[1] else { panic!() };
160
+ let Expr::Closure(c) = &a.values[0] else { panic!() };
161
+ assert_eq!(fieldObj(exprOf(&c.body.stmts[0])), "s", "closure param shadows");
162
+ assert_eq!(fieldObj(exprOf(&body[3])), "s", "after `s := 5`");
163
+ }
164
+
165
+ #[test]
166
+ fn narrow_self() {
167
+ let mut s = parse("fun area(self) =\n match self\n Circle =>\n self.r\n Square =>\n 0.0\n");
168
+ let plan = narrowPlan(&s, "area", "self", "Shape__Circle", "self__n1");
169
+ applyPlan(&mut s, &plan);
170
+ let m = matchOf(&stmts(&s, "area")[0]);
171
+ assert_eq!(fieldObj(exprOf(&m.cases[0].body.stmts[0])), "self__n1");
172
+ }
173
+
174
+ #[test]
175
+ fn narrow_in_pattern_if() {
176
+ let mut s = parse("fun f(s: Shape) =\n if Circle := s\n s.r\n else\n s.w\n");
177
+ let Stmt::If(i) = &stmts(&s, "f")[0] else { panic!() };
178
+ let Expr::Bind(b) = &i.condition else { panic!("bind expected: {:?}", i.condition) };
179
+ let mut plan = UnionPlan::default();
180
+ plan.narrowings.insert(addr(&b.case), ("s".into(), "Shape__Circle".into(), "s__n1".into()));
181
+ applyPlan(&mut s, &plan);
182
+ let Stmt::If(i) = &stmts(&s, "f")[0] else { panic!() };
183
+ assert_eq!(fieldObj(exprOf(&i.body.stmts[0])), "s__n1");
184
+ assert_eq!(fieldObj(exprOf(&i.else_.as_ref().unwrap().stmts[0])), "s");
185
+ let Expr::Bind(b) = &i.condition else { panic!() };
186
+ assert_eq!(
187
+ *b.pattern(),
188
+ CasePattern::Class { name: "Shape__Circle".into(), fields: vec![CasePattern::Name("s__n1".into())] }
189
+ );
190
+ }
191
+
192
+ fn shapeFn<'a>(s: &'a Source, method: &str) -> &'a Fn {
193
+ s.items
194
+ .iter()
195
+ .find_map(|i| match i {
196
+ Item::Fn(f) if f.name == method && f.type_param.as_deref() == Some("Shape") => Some(f),
197
+ _ => None,
198
+ })
199
+ .unwrap_or_else(|| panic!("no synthesized {method}"))
200
+ }
201
+
202
+ fn assertDispatchBody(f: &Fn, method: &str, argNames: &[&str]) {
203
+ let FnBody::Block(b) = &f.body else { panic!() };
204
+ let m = matchOf(&b.stmts[0]);
205
+ assert_eq!(m.subjects, vec![Expr::Self_]);
206
+ assert_eq!(m.cases.len(), 2);
207
+ for (k, (case, v)) in m.cases.iter().zip(["Shape__Circle", "Shape__Square"]).enumerate() {
208
+ let d = format!("d__{}", k + 1);
209
+ assert_eq!(
210
+ case.patterns[0],
211
+ CasePattern::Class { name: v.into(), fields: vec![CasePattern::Name(d.clone())] }
212
+ );
213
+ let Expr::Attribute(a) = exprOf(&case.body.stmts[0]) else { panic!() };
214
+ assert_eq!(varName(&a.object), d);
215
+ let AttrKind::Method(c) = &a.attr else { panic!() };
216
+ assert_eq!(c.name, method);
217
+ let args: Vec<&str> = c
218
+ .args
219
+ .iter()
220
+ .map(|a| match a {
221
+ Arg::Positional(e) => varName(e),
222
+ _ => panic!(),
223
+ })
224
+ .collect();
225
+ assert_eq!(args, argNames);
226
+ }
227
+ }
228
+
229
+ #[test]
230
+ fn dispatch_leaves_call_and_adds_method() {
231
+ let mut s = withShapes("fun f(s: Shape) -> Float =\n s.area()\n");
232
+ let before = exprOf(&stmts(&s, "f")[0]).clone();
233
+ let a = addr(exprOf(&stmts(&s, "f")[0]));
234
+ let mut plan = UnionPlan::default();
235
+ plan.dispatches.insert(a, vec!["Shape__Circle".into(), "Shape__Square".into()]);
236
+ applyPlan(&mut s, &plan);
237
+ assert_eq!(*exprOf(&stmts(&s, "f")[0]), before);
238
+ let f = shapeFn(&s, "area");
239
+ assert!(f.has_self && f.params.is_empty());
240
+ assert_eq!(f.returns, Some(Type { name: "Float".into(), generics: vec![] }));
241
+ assertDispatchBody(f, "area", &[]);
242
+ }
243
+
244
+ #[test]
245
+ fn dispatch_passes_two_args() {
246
+ let mut s = withShapes("fun f(s: Shape) -> Float =\n s.scale(1.0, 2.0)\n");
247
+ let a = addr(exprOf(&stmts(&s, "f")[0]));
248
+ let mut plan = UnionPlan::default();
249
+ plan.dispatches.insert(a, vec!["Shape__Circle".into(), "Shape__Square".into()]);
250
+ applyPlan(&mut s, &plan);
251
+ let f = shapeFn(&s, "scale");
252
+ assert_eq!(f.params.len(), 2);
253
+ assert_eq!(f.params[0].name, "k");
254
+ assertDispatchBody(f, "scale", &["k", "j"]);
255
+ }
256
+
257
+ #[test]
258
+ fn dispatch_in_interpolation() {
259
+ let mut s = withShapes("fun f(s: Shape) -> Str =\n \"{s}\"\n");
260
+ let Expr::String(se) = exprOf(&stmts(&s, "f")[0]) else { panic!() };
261
+ let StringPart::Interp(inner) = &se.parts[0] else { panic!() };
262
+ let a = addr(inner);
263
+ let mut plan = UnionPlan::default();
264
+ plan.dispatches.insert(a, vec!["Shape__Circle".into(), "Shape__Square".into()]);
265
+ applyPlan(&mut s, &plan);
266
+ let Expr::String(se) = exprOf(&stmts(&s, "f")[0]) else { panic!() };
267
+ let StringPart::Interp(inner) = &se.parts[0] else { panic!() };
268
+ assert_eq!(varName(inner), "s");
269
+ assertDispatchBody(shapeFn(&s, "toStr"), "toStr", &[]);
270
+ }
271
+
272
+ #[test]
273
+ fn dispatch_once_per_method() {
274
+ let mut s = withShapes("fun f(s: Shape) -> Float =\n s.area() + s.area()\n");
275
+ let Expr::Binary(b) = exprOf(&stmts(&s, "f")[0]) else { panic!() };
276
+ let (a0, a1) = (addr(&b.left), addr(&b.right));
277
+ let mut plan = UnionPlan::default();
278
+ for a in [a0, a1] {
279
+ plan.dispatches.insert(a, vec!["Shape__Circle".into(), "Shape__Square".into()]);
280
+ }
281
+ applyPlan(&mut s, &plan);
282
+ let n = s
283
+ .items
284
+ .iter()
285
+ .filter(|i| matches!(i, Item::Fn(f) if f.name == "area" && f.type_param.as_deref() == Some("Shape")))
286
+ .count();
287
+ assert_eq!(n, 1);
288
+ }
289
+
290
+ #[test]
291
+ fn nested_wrap_inside_dispatch_arg() {
292
+ let mut s = withShapes("fun f(s: Shape) -> Float =\n s.scale(1.0, 2.0)\n");
293
+ // dispatch on the call, wrap on its first argument
294
+ let e = exprOf(&stmts(&s, "f")[0]);
295
+ let Expr::Attribute(a) = e else { panic!() };
296
+ let AttrKind::Method(c) = &a.attr else { panic!() };
297
+ let Arg::Positional(arg0) = &c.args[0] else { panic!() };
298
+ let mut plan = UnionPlan::default();
299
+ plan.dispatches.insert(addr(e), vec!["Shape__Circle".into(), "Shape__Square".into()]);
300
+ plan.wraps.insert(addr(arg0), "Shape__Circle".into());
301
+ applyPlan(&mut s, &plan);
302
+ let Expr::Attribute(a) = exprOf(&stmts(&s, "f")[0]) else { panic!() };
303
+ let AttrKind::Method(c) = &a.attr else { panic!() };
304
+ let Arg::Positional(Expr::FnCall(w)) = &c.args[0] else { panic!("wrap expected") };
305
+ assert_eq!(w.name, "Shape__Circle");
306
+ shapeFn(&s, "scale");
307
+ }
308
+
309
+ #[test]
310
+ fn dispatch_substitutes_member_generics() {
311
+ let mut s = parse(
312
+ "record Box[T](v: T) =\n fun get(self, d: T) -> T = d\nenum Opt = Box[Int] | Nothing\nfun f(o: Opt) -> Int =\n o.get(1)\n",
313
+ );
314
+ let a = addr(exprOf(&stmts(&s, "f")[0]));
315
+ let mut plan = UnionPlan::default();
316
+ plan.dispatches.insert(a, vec!["Opt__Box".into()]);
317
+ applyPlan(&mut s, &plan);
318
+ let f = s
319
+ .items
320
+ .iter()
321
+ .find_map(|i| match i {
322
+ Item::Fn(f) if f.type_param.as_deref() == Some("Opt") => Some(f),
323
+ _ => None,
324
+ })
325
+ .unwrap();
326
+ let int = Type { name: "Int".into(), generics: vec![] };
327
+ assert_eq!(f.params[0].ty, ParamType::Type(int.clone()));
328
+ assert_eq!(f.returns, Some(int));
329
+ }