plum

#treesitter#compiler#wasm

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

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


3af14d9 — Peter John 2026-11-04T13:00:00+05:30
Remove own: ownership checker, own-only tests and corpus; own_inplace tests become unique_inplace
plum-checker/src/ownership.rs DELETED
@@ -1,1198 +0,0 @@
1
- //! Ownership pass for `own` types: an own binding is moved, not copied.
2
- //!
3
- //! Own types are the enums/records flagged `own record` (`Enum.isOwn`), traits
4
- //! flagged `own trait`, and any record that embeds one of those. Ordinary types
5
- //! are never tracked, so a program without `own` is untouched.
6
- //!
7
- //! Rules (straight-line; branches, loops and closures are handled
8
- //! conservatively through the `mergeBranches` / `walkLoopBody` / `walkClosure`
9
- //! hooks):
10
- //! * moves: passing the binding to an `own` param (or a generic `T` param, a
11
- //! constructor argument, or a callee that is not known), calling an `own self`
12
- //! method on it, returning it, storing it into a field or constructor, aliasing
13
- //! it (`t := s`) or using it as a spread source `T(..s, ...)`.
14
- //! * borrows: field reads, plain params, comparisons.
15
- //! * `s.f = v` needs an owned `s` (own local, own param, own self).
16
- //! * a borrowed (plain) param of own type cannot be stored or returned.
17
- //! * `s = expr` revives `s`.
18
-
19
- use crate::types::CheckError;
20
- use plum_core::ast::*;
21
- use std::collections::{HashMap, HashSet};
22
-
23
- /// Every own type name of `src`: flagged enums and traits, then every enum that
24
- /// embeds one (to a fixpoint).
25
- pub fn ownTypes(src: &Source) -> HashSet<String> {
26
- let mut own: HashSet<String> = HashSet::new();
27
- for item in &src.items {
28
- match item {
29
- Item::Enum(e) if e.isOwn => {
30
- own.insert(e.name.clone());
31
- }
32
- Item::Trait(t) if t.isOwn => {
33
- own.insert(t.name.clone());
34
- }
35
- _ => {}
36
- }
37
- }
38
- for t in &src.traits {
39
- if t.isOwn {
40
- own.insert(t.name.clone());
41
- }
42
- }
43
- loop {
44
- let mut grew = false;
45
- for item in &src.items {
46
- let Item::Enum(e) = item else { continue };
47
- if own.contains(&e.name) {
48
- continue;
49
- }
50
- let embedsOwn = e
51
- .embeds
52
- .iter()
53
- .chain(e.embedFields.iter().map(|(n, _)| n))
54
- .any(|n| own.contains(n));
55
- if embedsOwn {
56
- own.insert(e.name.clone());
57
- grew = true;
58
- }
59
- }
60
- if !grew {
61
- break;
62
- }
63
- }
64
- own
65
- }
66
-
67
- /// Whether `name` is an own type of `src`.
68
- pub fn isOwnType(src: &Source, name: &str) -> bool {
69
- ownTypes(src).contains(name)
70
- }
71
-
72
- pub fn checkOwnership(src: &Source) -> Vec<CheckError> {
73
- let own = ownTypes(src);
74
- if own.is_empty() {
75
- return Vec::new();
76
- }
77
- let mut cx = Cx {
78
- own,
79
- enums: HashMap::new(),
80
- fns: HashMap::new(),
81
- methods: HashMap::new(),
82
- errors: Vec::new(),
83
- pos: (0, 0),
84
- env: HashMap::new(),
85
- selfTy: None,
86
- tys: HashMap::new(),
87
- elems: HashMap::new(),
88
- views: HashMap::new(),
89
- traits: HashMap::new(),
90
- dead: false,
91
- loops: Vec::new(),
92
- swap: None,
93
- synth: false,
94
- };
95
- for t in src.traits.iter() {
96
- cx.traits.insert(t.name.clone(), t);
97
- }
98
- for item in &src.items {
99
- match item {
100
- Item::Enum(e) => {
101
- cx.enums.insert(e.name.clone(), e);
102
- }
103
- Item::Fn(f) => match &f.type_param {
104
- Some(t) => {
105
- cx.methods.insert((t.clone(), f.name.clone()), f);
106
- }
107
- None => {
108
- cx.fns.insert(f.name.clone(), f);
109
- }
110
- },
111
- Item::Trait(t) => {
112
- cx.traits.insert(t.name.clone(), t);
113
- }
114
- _ => {}
115
- }
116
- }
117
- for item in &src.items {
118
- match item {
119
- Item::Fn(f) => cx.checkFn(f),
120
- Item::Test(t) => {
121
- cx.begin((t.line, t.column), None);
122
- cx.walkBlock(&t.body);
123
- }
124
- _ => {}
125
- }
126
- }
127
- cx.errors
128
- }
129
-
130
- #[derive(Clone, Copy, PartialEq)]
131
- enum Mode {
132
- /// An own local or an `own` param / `own self`: may be written and moved.
133
- Owned,
134
- /// A plain param / plain `self`: read-only, cannot be stored.
135
- Borrowed,
136
- }
137
-
138
- #[derive(Clone)]
139
- struct Binding {
140
- /// Type name when known.
141
- ty: Option<String>,
142
- mode: Mode,
143
- /// Where it was moved, if it currently is.
144
- moved: Option<(usize, usize)>,
145
- }
146
-
147
- type Env = HashMap<String, Binding>;
148
-
149
- struct Cx<'a> {
150
- own: HashSet<String>,
151
- enums: HashMap<String, &'a Enum>,
152
- fns: HashMap<String, &'a Fn>,
153
- methods: HashMap<(String, String), &'a Fn>,
154
- errors: Vec<CheckError>,
155
- /// Last known source position (statement, call or fn start).
156
- pos: (usize, usize),
157
- env: Env,
158
- selfTy: Option<String>,
159
- /// Declared types of every local / param, own or not (for field typing).
160
- tys: HashMap<String, String>,
161
- /// `Slice[T]` locals whose element type `T` is known.
162
- elems: HashMap<String, String>,
163
- /// Borrowed projections of an owned binding (pattern binders / element views
164
- /// holding own data), mapped to the root binding: moving the root kills them.
165
- views: HashMap<String, String>,
166
- traits: HashMap<String, &'a TraitDecl>,
167
- /// The current path ended in return/crash/break/continue.
168
- dead: bool,
169
- loops: Vec<LoopFrame>,
170
- /// `w.f` of the swap form `w.f = <expr using w.f>`: may be moved out.
171
- swap: Option<String>,
172
- /// Checking a compiler-synthesized fn (union method forwarder, line 0): its
173
- /// match patterns are not tracked, the lowering forwards ownership by construction.
174
- synth: bool,
175
- }
176
-
177
- #[derive(Default)]
178
- struct LoopFrame {
179
- exits: Vec<Env>,
180
- backs: Vec<Env>,
181
- }
182
-
183
- fn isGeneric(name: &str) -> bool {
184
- name.len() == 1 && name.chars().all(|c| c.is_ascii_uppercase())
185
- }
186
-
187
- fn nameOf(e: &Expr) -> Option<String> {
188
- match e {
189
- Expr::Var(x) => Some(x.clone()),
190
- Expr::Self_ => Some("self".to_string()),
191
- Expr::Paren(i) => nameOf(i),
192
- _ => None,
193
- }
194
- }
195
-
196
- /// `(isSetter, field)` for a generated trait accessor name (`__get_x`/`__set_x`).
197
- fn accessorField(name: &str) -> Option<(bool, &str)> {
198
- let get = plum_core::traits::getter("");
199
- let set = plum_core::traits::setter("");
200
- name.strip_prefix(set.as_str())
201
- .map(|f| (true, f))
202
- .or_else(|| name.strip_prefix(get.as_str()).map(|f| (false, f)))
203
- }
204
-
205
- /// The root variable of a field chain `a.b.c` (or `a` itself).
206
- fn rootName(e: &Expr) -> Option<String> {
207
- match e {
208
- Expr::Attribute(a) if matches!(a.attr, AttrKind::Field(_)) => rootName(&a.object),
209
- Expr::Paren(i) => rootName(i),
210
- _ => nameOf(e),
211
- }
212
- }
213
-
214
- impl<'a> Cx<'a> {
215
- fn begin(&mut self, pos: (usize, usize), selfTy: Option<String>) {
216
- self.pos = pos;
217
- self.env = HashMap::new();
218
- self.selfTy = selfTy;
219
- self.tys = HashMap::new();
220
- self.elems = HashMap::new();
221
- self.views = HashMap::new();
222
- self.dead = false;
223
- self.loops = Vec::new();
224
- self.swap = None;
225
- self.synth = false;
226
- }
227
-
228
- fn noteType(&mut self, name: &str, t: &Type) {
229
- self.tys.insert(name.to_string(), t.name.clone());
230
- if t.name == "Slice" {
231
- if let Some(e) = t.generics.first() {
232
- if self.own.contains(&e.name) {
233
- self.elems.insert(name.to_string(), e.name.clone());
234
- return;
235
- }
236
- }
237
- }
238
- self.elems.remove(name);
239
- }
240
-
241
- /// Method `name` of `ty`, looking through embedded records and traits.
242
- fn findMethod(&self, ty: &str, name: &str, depth: usize) -> Option<&'a Fn> {
243
- if depth > 8 {
244
- return None;
245
- }
246
- if let Some(f) = self.methods.get(&(ty.to_string(), name.to_string())) {
247
- return Some(*f);
248
- }
249
- if let Some(t) = self.traits.get(ty) {
250
- if let Some(f) = t.methods.iter().chain(t.required.iter()).find(|f| f.name == name) {
251
- return Some(f);
252
- }
253
- }
254
- if let Some(e) = self.enums.get(ty) {
255
- let parents = e.embeds.iter().chain(e.embedFields.iter().map(|(n, _)| n));
256
- for p in parents {
257
- if let Some(f) = self.findMethod(p, name, depth + 1) {
258
- return Some(f);
259
- }
260
- }
261
- }
262
- None
263
- }
264
-
265
- /// Receiver type unknown (e.g. bound by a pattern): a method called `name`, but
266
- /// only when EVERY method of that name in the program (trait methods, required
267
- /// methods, record/enum methods) takes `self` and each param the same way, so
268
- /// the call's modes do not depend on which type the receiver turns out to be.
269
- /// Otherwise `None`, and the caller consumes every argument.
270
- fn uniqueTraitMethod(&self, name: &str) -> Option<&'a Fn> {
271
- let all = self
272
- .traits
273
- .values()
274
- .flat_map(|t| t.methods.iter().chain(t.required.iter()))
275
- .chain(self.methods.values().copied())
276
- .filter(|f| f.name == name);
277
- let mut found: Option<&'a Fn> = None;
278
- for f in all {
279
- match found {
280
- None => found = Some(f),
281
- Some(g) if sameModes(g, f) => {}
282
- Some(_) => return None,
283
- }
284
- }
285
- found
286
- }
287
-
288
- /// `xs.at(i)` and friends on a `List[Own]`: a borrowed view of an element.
289
- fn elemRead(&self, e: &Expr) -> Option<(String, String)> {
290
- let Expr::Attribute(a) = e else { return None };
291
- let AttrKind::Method(c) = &a.attr else { return None };
292
- if !matches!(c.name.as_str(), "at" | "get" | "first" | "last") {
293
- return None;
294
- }
295
- let recv = nameOf(&a.object)?;
296
- let el = self.elems.get(&recv)?;
297
- Some((format!("{}.{}", recv, c.name), el.clone()))
298
- }
299
-
300
- /// The owned binding a `match` subject / element read is rooted in, looking
301
- /// through views (`n.kind`, `n`, `ks.at(0)` where `ks` is a view of `n`).
302
- fn viewRoot(&self, e: &Expr) -> Option<String> {
303
- let r = match e {
304
- Expr::Paren(i) => return self.viewRoot(i),
305
- Expr::Attribute(a) if matches!(a.attr, AttrKind::Method(_)) => {
306
- return self.elemViewRoot(e).or_else(|| self.viewRoot(&a.object));
307
- }
308
- Expr::Attribute(a) => return self.viewRoot(&a.object),
309
- _ => nameOf(e)?,
310
- };
311
- if let Some(root) = self.views.get(&r) {
312
- return Some(root.clone());
313
- }
314
- self.env.contains_key(&r).then_some(r)
315
- }
316
-
317
- /// `ks.at(i)` where `ks` is a view into an owned binding: that binding.
318
- fn elemViewRoot(&self, e: &Expr) -> Option<String> {
319
- let Expr::Attribute(a) = e else { return None };
320
- let AttrKind::Method(c) = &a.attr else { return None };
321
- if !matches!(c.name.as_str(), "at" | "get" | "first" | "last") {
322
- return None;
323
- }
324
- self.viewRoot(&a.object)
325
- }
326
-
327
- /// Names a pattern binds to own data: an own-typed field or a `Slice[Own]`.
328
- fn viewBinds(&self, p: &CasePattern, out: &mut Vec<String>) {
329
- let CasePattern::Class { name, fields } = p else { return };
330
- let owner = self.enums.get(name).copied().or_else(|| {
331
- self.enums
332
- .values()
333
- .copied()
334
- .find(|e| e.variants.iter().any(|v| &v.name == name))
335
- });
336
- let Some(v) = owner.and_then(|e| e.variants.iter().find(|v| &v.name == name)) else {
337
- return;
338
- };
339
- for (i, f) in fields.iter().enumerate() {
340
- let holdsOwn = match v.fields.get(i) {
341
- Some(t) => {
342
- self.own.contains(&t.name)
343
- || (t.name == "Slice" && t.generics.iter().any(|g| self.own.contains(&g.name)))
344
- }
345
- None => false,
346
- };
347
- match f {
348
- CasePattern::Name(n) if holdsOwn => out.push(n.clone()),
349
- CasePattern::Class { .. } => self.viewBinds(f, out),
350
- _ => {}
351
- }
352
- }
353
- }
354
-
355
- fn isOwnTy(&self, ty: &Option<String>) -> bool {
356
- ty.as_ref().is_some_and(|t| self.own.contains(t))
357
- }
358
-
359
- fn err(&mut self, pos: (usize, usize), msg: String) {
360
- self.errors.push(CheckError::at(pos.0, pos.1, msg));
361
- }
362
-
363
- fn checkFn(&mut self, f: &Fn) {
364
- // Synthesized constructors store their params by construction.
365
- if f.name.ends_with(plum_core::ctors::NEW_SUFFIX) {
366
- return;
367
- }
368
- // Generated trait field accessors write/read `self` by construction.
369
- if accessorField(&f.name).is_some() {
370
- return;
371
- }
372
- self.begin((f.line, f.column), f.type_param.clone());
373
- self.synth = f.line == 0 && f.column == 0;
374
- if f.has_self {
375
- if let Some(t) = &f.type_param {
376
- if self.own.contains(t) {
377
- // `init` builds the value, so it owns its receiver.
378
- let mode = if f.ownSelf || f.name == "init" {
379
- Mode::Owned
380
- } else {
381
- Mode::Borrowed
382
- };
383
- self.env.insert(
384
- "self".to_string(),
385
- Binding {
386
- ty: Some(t.clone()),
387
- mode,
388
- moved: None,
389
- },
390
- );
391
- }
392
- }
393
- }
394
- for p in &f.params {
395
- let (ParamType::Type(t) | ParamType::Variadic(t)) = &p.ty else {
396
- continue;
397
- };
398
- let ty = Some(t.name.clone());
399
- self.noteType(&p.name, t);
400
- if !self.isOwnTy(&ty) {
401
- continue;
402
- }
403
- let mode = if p.isOwn { Mode::Owned } else { Mode::Borrowed };
404
- self.env.insert(
405
- p.name.clone(),
406
- Binding {
407
- ty,
408
- mode,
409
- moved: None,
410
- },
411
- );
412
- }
413
- match &f.body {
414
- FnBody::Expr(e) => self.consume(e, None),
415
- FnBody::Block(b) => {
416
- self.walkStmts(&b.stmts, true);
417
- }
418
- FnBody::Extern => {}
419
- }
420
- }
421
-
422
- // ---- typing (best effort) ----------------------------------------------
423
-
424
- fn typeOf(&self, e: &Expr) -> Option<String> {
425
- match e {
426
- Expr::Var(x) => self
427
- .env
428
- .get(x)
429
- .and_then(|b| b.ty.clone())
430
- .or_else(|| self.tys.get(x).cloned()),
431
- Expr::Self_ => self.selfTy.clone(),
432
- Expr::Paren(i) => self.typeOf(i),
433
- Expr::ClassCall(c) => Some(c.type_name.clone()),
434
- Expr::FnCall(c) => {
435
- if let Some(t) = c.name.strip_suffix(plum_core::ctors::NEW_SUFFIX) {
436
- return Some(t.to_string());
437
- }
438
- self.fns.get(&c.name).and_then(|f| retTy(f))
439
- }
440
- Expr::Attribute(a) => match &a.attr {
441
- AttrKind::Method(c) => {
442
- let recv = match &a.object {
443
- Expr::TypeName(t) => Some(t.clone()),
444
- o => self.typeOf(o),
445
- }?;
446
- self.findMethod(&recv, &c.name, 0).and_then(|f| retTy(f))
447
- }
448
- AttrKind::Field(name) => {
449
- let recv = self.typeOf(&a.object)?;
450
- let en = self.enums.get(&recv)?;
451
- let v = en.variants.first()?;
452
- let i = v.field_names.iter().position(|n| n == name)?;
453
- let t = v.fields.get(i)?;
454
- (!isGeneric(&t.name)).then(|| t.name.clone())
455
- }
456
- },
457
- _ => None,
458
- }
459
- }
460
-
461
- /// How a match scrutinee of own type holds its value: `Owned` for an owned
462
- /// local / param / `own self` / fresh call result, `Borrowed` for a borrowed
463
- /// binding or a field read. `None` when it is not of a known own type.
464
- fn subjectMode(&self, e: &Expr) -> Option<Mode> {
465
- if self.synth || !self.isOwnTy(&self.typeOf(e)) {
466
- return None;
467
- }
468
- match e {
469
- Expr::Paren(i) => self.subjectMode(i),
470
- Expr::Var(_) | Expr::Self_ => {
471
- nameOf(e).and_then(|n| self.env.get(&n)).map(|b| b.mode)
472
- }
473
- Expr::Attribute(a) if matches!(a.attr, AttrKind::Field(_)) => Some(Mode::Borrowed),
474
- Expr::FnCall(_) | Expr::ClassCall(_) | Expr::Attribute(_) => Some(Mode::Owned),
475
- _ => None,
476
- }
477
- }
478
-
479
- /// Names a pattern binds to a value of own type, with that type. `ty` is the
480
- /// type of the value the pattern matches.
481
- fn patternBinds(
482
- &self,
483
- p: &CasePattern,
484
- ty: Option<String>,
485
- onlyOwn: bool,
486
- out: &mut Vec<(String, String)>,
487
- ) {
488
- match p {
489
- CasePattern::Name(n) => {
490
- if let Some(t) = ty.filter(|t| !onlyOwn || self.own.contains(t)) {
491
- out.push((n.clone(), t));
492
- }
493
- }
494
- CasePattern::Class { name, fields } => {
495
- let owner = self.enums.get(name).copied().or_else(|| {
496
- self.enums
497
- .values()
498
- .copied()
499
- .find(|e| e.variants.iter().any(|v| &v.name == name))
500
- });
501
- let Some(v) = owner.and_then(|e| e.variants.iter().find(|v| &v.name == name))
502
- else {
503
- return;
504
- };
505
- for (i, f) in fields.iter().enumerate() {
506
- let fty = match (v.fields.get(i), &v.member) {
507
- (Some(t), _) if !isGeneric(&t.name) => Some(t.name.clone()),
508
- (None, Some(t)) if i == 0 => Some(t.name.clone()),
509
- _ => None,
510
- };
511
- self.patternBinds(f, fty, onlyOwn, out);
512
- }
513
- }
514
- _ => {}
515
- }
516
- }
517
-
518
- // ---- statements --------------------------------------------------------
519
-
520
- fn walkBlock(&mut self, b: &Block) {
521
- self.walkStmts(&b.stmts, false);
522
- }
523
-
524
- /// `tail`: the last bare expression is the block's value, returned from the fn.
525
- fn walkStmts(&mut self, stmts: &[Stmt], tail: bool) {
526
- for (i, s) in stmts.iter().enumerate() {
527
- let last = tail && i + 1 == stmts.len();
528
- self.walkStmt(s, last);
529
- }
530
- }
531
-
532
- /// Runs `f` on a copy of `base` as one alternative path.
533
- fn branch(&mut self, base: &Env, f: impl FnOnce(&mut Self)) -> (Env, bool) {
534
- self.env = base.clone();
535
- let d0 = self.dead;
536
- self.dead = false;
537
- f(self);
538
- let out = (std::mem::replace(&mut self.env, base.clone()), self.dead);
539
- self.dead = d0;
540
- out
541
- }
542
-
543
- /// Joins alternative paths: dead ones (returned early) do not count.
544
- fn join(&mut self, base: Env, outs: Vec<(Env, bool)>) {
545
- let live: Vec<Env> = outs.into_iter().filter(|o| !o.1).map(|o| o.0).collect();
546
- if live.is_empty() {
547
- self.dead = true;
548
- self.env = base;
549
- } else {
550
- self.env = mergeBranches(base, live);
551
- }
552
- }
553
-
554
- fn walkStmt(&mut self, s: &Stmt, isTail: bool) {
555
- match s {
556
- Stmt::Assign(a) => self.walkAssign(a),
557
- Stmt::Break => {
558
- let env = self.env.clone();
559
- if let Some(f) = self.loops.last_mut() {
560
- f.exits.push(env);
561
- }
562
- self.dead = true;
563
- }
564
- Stmt::Continue => {
565
- let env = self.env.clone();
566
- if let Some(f) = self.loops.last_mut() {
567
- f.backs.push(env);
568
- }
569
- self.dead = true;
570
- }
571
- Stmt::Todo => {}
572
- Stmt::Assert(c) => self.walk(&c.cond),
573
- Stmt::ForLoop(l) => {
574
- self.walk(&l.source);
575
- self.walkLoopBody(&l.body);
576
- }
577
- Stmt::While(w) => {
578
- self.walk(&w.condition);
579
- self.walkLoopBody(&w.body);
580
- }
581
- Stmt::If(i) => {
582
- self.walk(&i.condition);
583
- let base = self.env.clone();
584
- let mut outs = Vec::new();
585
- outs.push(self.branch(&base, |cx| cx.walkStmts(&i.body.stmts, isTail)));
586
- for ei in &i.else_ifs {
587
- outs.push(self.branch(&base, |cx| {
588
- cx.walk(&ei.condition);
589
- cx.walkStmts(&ei.body.stmts, isTail);
590
- }));
591
- }
592
- if let Some(b) = &i.else_ {
593
- outs.push(self.branch(&base, |cx| cx.walkStmts(&b.stmts, isTail)));
594
- } else {
595
- outs.push((base.clone(), false));
596
- }
597
- self.join(base, outs);
598
- }
599
- Stmt::Match(m) => {
600
- for e in &m.subjects {
601
- self.walk(e);
602
- }
603
- // Pattern names of own type follow the scrutinee: owned when it is
604
- // owned (and the match consumes it), borrowed when it is borrowed.
605
- let modes: Vec<Option<Mode>> =
606
- m.subjects.iter().map(|e| self.subjectMode(e)).collect();
607
- let mut consumes = false;
608
- for c in &m.cases {
609
- for (i, p) in c.patterns.iter().enumerate() {
610
- let ty = m.subjects.get(i).and_then(|e| self.typeOf(e));
611
- let mut found = Vec::new();
612
- self.patternBinds(p, ty, true, &mut found);
613
- if modes.get(i) == Some(&Some(Mode::Owned)) && !found.is_empty() {
614
- consumes = true;
615
- }
616
- }
617
- }
618
- if consumes {
619
- let at = self.pos;
620
- for (e, mode) in m.subjects.iter().zip(&modes) {
621
- if *mode != Some(Mode::Owned) {
622
- continue;
623
- }
624
- if let Some(b) = nameOf(e).and_then(|n| self.env.get_mut(&n)) {
625
- b.moved = Some(at);
626
- }
627
- }
628
- }
629
- let base = self.env.clone();
630
- let mut outs = Vec::new();
631
- for c in &m.cases {
632
- outs.push(self.branch(&base, |cx| {
633
- let mut names = Vec::new();
634
- for p in &c.patterns {
635
- patternNames(p, &mut names);
636
- }
637
- for n in names {
638
- cx.env.remove(&n);
639
- cx.tys.remove(&n);
640
- cx.views.remove(&n);
641
- }
642
- // Binders holding own data read out of an owned binding are
643
- // views into it: they die when it is moved. (`match n` itself
644
- // consumes `n`, so its binders are moved out, not views.)
645
- let subjectRoot = m.subjects.iter().find_map(|e| match e {
646
- Expr::Var(_) | Expr::Self_ => nameOf(e)
647
- .and_then(|n| cx.views.get(&n).cloned()),
648
- e => cx.viewRoot(e),
649
- });
650
- if let Some(root) = subjectRoot {
651
- let mut vs = Vec::new();
652
- for p in &c.patterns {
653
- cx.viewBinds(p, &mut vs);
654
- }
655
- for n in vs {
656
- cx.views.insert(n, root.clone());
657
- }
658
- }
659
- // Every binder gets its declared type (own or not), so a method
660
- // call on it resolves to its real type's method.
661
- for (i, p) in c.patterns.iter().enumerate() {
662
- let ty = m.subjects.get(i).and_then(|e| cx.typeOf(e));
663
- let mut all = Vec::new();
664
- cx.patternBinds(p, ty, false, &mut all);
665
- for (n, t) in all {
666
- cx.tys.insert(n, t);
667
- }
668
- }
669
- for (i, p) in c.patterns.iter().enumerate() {
670
- let Some(Some(mode)) = modes.get(i).copied() else { continue };
671
- let ty = m.subjects.get(i).and_then(|e| cx.typeOf(e));
672
- let mut found = Vec::new();
673
- cx.patternBinds(p, ty, true, &mut found);
674
- for (n, t) in found {
675
- cx.tys.insert(n.clone(), t.clone());
676
- cx.env.insert(
677
- n,
678
- Binding {
679
- ty: Some(t),
680
- mode,
681
- moved: None,
682
- },
683
- );
684
- }
685
- }
686
- if let Some(g) = &c.guard {
687
- cx.walk(g);
688
- }
689
- cx.walkStmts(&c.body.stmts, isTail);
690
- }));
691
- }
692
- if outs.is_empty() {
693
- outs.push((base.clone(), false));
694
- }
695
- self.join(base, outs);
696
- }
697
- Stmt::Return(e) => {
698
- if let Some(e) = e {
699
- self.consume(e, None);
700
- }
701
- self.dead = true;
702
- }
703
- Stmt::Crash(e) => {
704
- self.walk(e);
705
- self.dead = true;
706
- }
707
- Stmt::Expr(e, l, c) => {
708
- if *l != 0 {
709
- self.pos = (*l, *c);
710
- }
711
- if isTail {
712
- self.consume(e, None);
713
- } else {
714
- self.walk(e);
715
- }
716
- }
717
- }
718
- }
719
-
720
- /// A loop body runs zero or more times. Moving an outer binding in the body
721
- /// without reviving it before the next iteration is an error at the move.
722
- fn walkLoopBody(&mut self, b: &Block) {
723
- let base = self.env.clone();
724
- let d0 = self.dead;
725
- self.loops.push(LoopFrame::default());
726
- self.walkBlock(b);
727
- let mut frame = self.loops.pop().unwrap_or_default();
728
- if !self.dead {
729
- frame.backs.push(self.env.clone());
730
- }
731
- self.dead = d0;
732
- for (name, bb) in base.iter() {
733
- if bb.moved.is_some() {
734
- continue;
735
- }
736
- for back in &frame.backs {
737
- if let Some(Binding { moved: Some((l, c)), .. }) = back.get(name) {
738
- let (l, c) = (*l, *c);
739
- self.err(
740
- (l, c),
741
- format!("\"{}\" used after it was moved at {}:{}", name, l, c),
742
- );
743
- break;
744
- }
745
- }
746
- }
747
- let mut outs: Vec<Env> = frame.backs;
748
- outs.extend(frame.exits);
749
- outs.push(base.clone());
750
- self.env = mergeBranches(base, outs);
751
- }
752
-
753
- /// A closure only borrows what it captures.
754
- fn walkClosure(&mut self, c: &Closure) {
755
- let base = self.env.clone();
756
- let (d0, loops) = (self.dead, std::mem::take(&mut self.loops));
757
- self.dead = false;
758
- for b in self.env.values_mut() {
759
- b.mode = Mode::Borrowed;
760
- }
761
- for p in &c.params {
762
- self.env.remove(p);
763
- self.tys.remove(p);
764
- }
765
- self.walkStmts(&c.body.stmts, true);
766
- self.env = base;
767
- self.dead = d0;
768
- self.loops = loops;
769
- }
770
-
771
- fn walkAssign(&mut self, a: &Assign) {
772
- // Swap form: `w.f = <uses w.f>` may move the field out and back in.
773
- let swap = match (a.targets.as_slice(), a.values.len()) {
774
- ([AssignTarget::Field(obj, f)], 1) => pathOf(obj).map(|p| format!("{p}.{f}")),
775
- _ => None,
776
- };
777
- let outerSwap = std::mem::replace(&mut self.swap, swap);
778
- for (i, v) in a.values.iter().enumerate() {
779
- let viewed = matches!(a.targets.get(i), Some(AssignTarget::Var(_)))
780
- && a.declare
781
- && self.elemRead(v).is_some();
782
- if viewed {
783
- self.walk(v);
784
- } else {
785
- self.consume(v, None);
786
- }
787
- }
788
- self.swap = outerSwap;
789
- for (i, t) in a.targets.iter().enumerate() {
790
- match t {
791
- AssignTarget::Var(x) => {
792
- let val = a.values.get(i).or(a.values.first());
793
- let view = val.and_then(|v| self.elemRead(v));
794
- self.views.remove(x);
795
- if let Some(root) = val.and_then(|v| self.elemViewRoot(v)) {
796
- self.views.insert(x.clone(), root);
797
- }
798
- let valTy = match &view {
799
- Some((_, el)) => Some(el.clone()),
800
- None => val.and_then(|v| self.typeOf(v)),
801
- };
802
- if a.declare {
803
- let ty = a.ty.as_ref().map(|t| t.name.clone()).or(valTy);
804
- match &a.ty {
805
- Some(t) => self.noteType(x, t),
806
- None => {
807
- self.elems.remove(x);
808
- match &ty {
809
- Some(n) => {
810
- self.tys.insert(x.clone(), n.clone());
811
- }
812
- None => {
813
- self.tys.remove(x);
814
- }
815
- }
816
- }
817
- }
818
- if self.isOwnTy(&ty) {
819
- self.env.insert(
820
- x.clone(),
821
- Binding {
822
- ty,
823
- mode: if view.is_some() { Mode::Borrowed } else { Mode::Owned },
824
- moved: None,
825
- },
826
- );
827
- } else {
828
- self.env.remove(x);
829
- }
830
- } else if let Some(b) = self.env.get_mut(x) {
831
- // Reassignment revives it (and takes ownership of the new value).
832
- b.moved = None;
833
- b.mode = Mode::Owned;
834
- }
835
- }
836
- AssignTarget::Field(obj, f) => {
837
- self.walk(obj);
838
- if let Some(root) = rootName(obj) {
839
- if let Some(b) = self.env.get(&root) {
840
- if b.mode == Mode::Borrowed {
841
- let p = self.pos;
842
- self.err(
843
- p,
844
- format!("cannot assign field \"{}\" of borrowed \"{}\"", f, root),
845
- );
846
- }
847
- }
848
- }
849
- }
850
- }
851
- }
852
- }
853
-
854
- // ---- expressions -------------------------------------------------------
855
-
856
- fn checkLive(&mut self, name: &str) {
857
- if let Some(root) = self.views.get(name).cloned() {
858
- if let Some((l, c)) = self.env.get(&root).and_then(|b| b.moved) {
859
- self.views.remove(name); // report once
860
- let p = self.pos;
861
- self.err(
862
- p,
863
- format!(
864
- "\"{}\" used after it was moved at {}:{} (it is a view into \"{}\")",
865
- name, l, c, root
866
- ),
867
- );
868
- }
869
- }
870
- let Some(b) = self.env.get_mut(name) else { return };
871
- if let Some((l, c)) = b.moved {
872
- b.moved = None; // report once
873
- let p = self.pos;
874
- self.err(p, format!("\"{}\" used after it was moved at {}:{}", name, l, c));
875
- }
876
- }
877
-
878
- /// `e` flows into a new owner (own param, field, list, constructor, alias,
879
- /// return, spread source): a tracked binding is moved; a borrowed one cannot.
880
- fn consume(&mut self, e: &Expr, pos: Option<(usize, usize)>) {
881
- let e = match e {
882
- Expr::Paren(i) => i,
883
- e => e,
884
- };
885
- match e {
886
- Expr::InlineIf(i) => {
887
- self.walk(&i.condition);
888
- let base = self.env.clone();
889
- let a = self.branch(&base, |cx| cx.consume(&i.then, pos));
890
- let b = self.branch(&base, |cx| cx.consume(&i.else_, pos));
891
- self.join(base, vec![a, b]);
892
- return;
893
- }
894
- Expr::Try(i) => {
895
- self.consume(i, pos);
896
- return;
897
- }
898
- Expr::Bang(b) => {
899
- self.consume(&b.value, pos);
900
- return;
901
- }
902
- Expr::Catch(c) => {
903
- self.consume(&c.value, pos);
904
- self.walkHandler(&c.handler, true);
905
- return;
906
- }
907
- Expr::Attribute(a) => {
908
- if let AttrKind::Field(f) = &a.attr {
909
- if self.isOwnTy(&self.typeOf(e)) {
910
- self.walk(&a.object);
911
- let path = pathOf(e);
912
- if path.is_some() && path == self.swap {
913
- return;
914
- }
915
- let at = pos.unwrap_or(self.pos);
916
- let root = rootName(&a.object).unwrap_or_else(|| "?".to_string());
917
- self.err(
918
- at,
919
- format!(
920
- "cannot move out of field \"{}\" of \"{}\"; use the swap form",
921
- f, root
922
- ),
923
- );
924
- return;
925
- }
926
- }
927
- if let Some((site, _)) = self.elemRead(e) {
928
- self.walk(&a.object);
929
- let at = pos.unwrap_or(self.pos);
930
- self.err(
931
- at,
932
- format!(
933
- "cannot store borrowed \"{}\"; pass it with own or move it",
934
- site
935
- ),
936
- );
937
- return;
938
- }
939
- }
940
- _ => {}
941
- }
942
- let Some(name) = nameOf(e) else {
943
- self.walk(e);
944
- return;
945
- };
946
- let at = pos.unwrap_or(self.pos);
947
- let Some(b) = self.env.get(&name) else {
948
- return;
949
- };
950
- let borrowed = b.mode == Mode::Borrowed;
951
- self.checkLive(&name);
952
- if borrowed {
953
- self.err(
954
- at,
955
- format!(
956
- "cannot store borrowed \"{}\"; pass it with own or move it",
957
- name
958
- ),
959
- );
960
- return;
961
- }
962
- if let Some(b) = self.env.get_mut(&name) {
963
- b.moved = Some(at);
964
- }
965
- }
966
-
967
- fn walkHandler(&mut self, h: &CatchHandler, consume: bool) {
968
- let base = self.env.clone();
969
- let out = self.branch(&base, |cx| match h {
970
- CatchHandler::Default(d) => {
971
- if consume {
972
- cx.consume(d, None)
973
- } else {
974
- cx.walk(d)
975
- }
976
- }
977
- CatchHandler::Block { binder, body } => {
978
- cx.env.remove(binder);
979
- cx.tys.remove(binder);
980
- cx.walkStmts(&body.stmts, consume);
981
- }
982
- });
983
- let skip = (base.clone(), false);
984
- self.join(base, vec![out, skip]);
985
- }
986
-
987
- fn walkArgs(&mut self, call: &FnCall, callee: Option<&Fn>, forceConsume: bool) {
988
- let at = if call.line != 0 { (call.line, call.column) } else { self.pos };
989
- for (i, a) in call.args.iter().enumerate() {
990
- let (value, param) = match a {
991
- Arg::Positional(v) => (v, callee.and_then(|f| paramAt(f, i))),
992
- Arg::Keyword { name, value } => (
993
- value,
994
- callee.and_then(|f| f.params.iter().find(|p| &p.name == name)),
995
- ),
996
- };
997
- let takes = forceConsume
998
- || match (callee, param) {
999
- (None, _) => true,
1000
- (Some(_), None) => false,
1001
- (Some(_), Some(p)) => {
1002
- p.isOwn
1003
- || matches!(&p.ty, ParamType::Type(t) | ParamType::Variadic(t) if isGeneric(&t.name))
1004
- }
1005
- };
1006
- if takes {
1007
- self.consume(value, Some(at));
1008
- } else {
1009
- self.walk(value);
1010
- }
1011
- }
1012
- }
1013
-
1014
- fn walk(&mut self, e: &Expr) {
1015
- match e {
1016
- Expr::Var(x) => self.checkLive(x),
1017
- Expr::Self_ => self.checkLive("self"),
1018
- Expr::Binary(b) => {
1019
- self.walk(&b.left);
1020
- self.walk(&b.right);
1021
- }
1022
- Expr::Unary(u) => self.walk(&u.operand),
1023
- Expr::Bool(b) => {
1024
- self.walk(&b.left);
1025
- self.walk(&b.right);
1026
- }
1027
- Expr::Not(i) | Expr::Paren(i) => self.walk(i),
1028
- Expr::Try(i) => self.consume(i, None),
1029
- Expr::Compare(c) => {
1030
- self.walk(&c.left);
1031
- self.walk(&c.right);
1032
- }
1033
- Expr::InlineIf(i) => {
1034
- self.walk(&i.condition);
1035
- let base = self.env.clone();
1036
- let a = self.branch(&base, |cx| cx.walk(&i.then));
1037
- let b = self.branch(&base, |cx| cx.walk(&i.else_));
1038
- self.join(base, vec![a, b]);
1039
- }
1040
- Expr::FnCall(c) => {
1041
- if c.line != 0 {
1042
- self.pos = (c.line, c.column);
1043
- }
1044
- let callee = self.fns.get(&c.name).copied();
1045
- let force = c.name.ends_with(plum_core::ctors::NEW_SUFFIX);
1046
- self.walkArgs(c, callee, force);
1047
- }
1048
- Expr::Yield(es) => {
1049
- for e in es {
1050
- self.consume(e, None);
1051
- }
1052
- }
1053
- Expr::ClassCall(c) => {
1054
- // Field values are read before the spread source is taken.
1055
- for f in &c.fields {
1056
- self.consume(&f.value, None);
1057
- }
1058
- if let Some(s) = &c.spread {
1059
- self.consume(s, None);
1060
- }
1061
- }
1062
- Expr::Attribute(a) => match &a.attr {
1063
- AttrKind::Field(_) => self.walk(&a.object),
1064
- AttrKind::Method(c) => {
1065
- let recvTy = match &a.object {
1066
- Expr::TypeName(t) => Some(t.clone()),
1067
- o => self.typeOf(o),
1068
- };
1069
- let callee = recvTy
1070
- .and_then(|t| self.findMethod(&t, &c.name, 0))
1071
- .or_else(|| self.uniqueTraitMethod(&c.name));
1072
- if c.line != 0 {
1073
- self.pos = (c.line, c.column);
1074
- }
1075
- let at = self.pos;
1076
- if callee.is_some_and(|f| f.ownSelf) {
1077
- self.consume(&a.object, Some(at));
1078
- } else {
1079
- self.walk(&a.object);
1080
- }
1081
- // `a.x = v` on a trait value lowers to `a.__set_x(v)`: a field write.
1082
- if let Some((true, field)) = accessorField(&c.name) {
1083
- if let Some(root) = rootName(&a.object) {
1084
- if self.env.get(&root).is_some_and(|b| b.mode == Mode::Borrowed) {
1085
- self.err(at, format!("cannot assign field \"{}\" of borrowed \"{}\"", field, root));
1086
- }
1087
- }
1088
- }
1089
- self.walkArgs(c, callee, false);
1090
- }
1091
- },
1092
- Expr::Bind(b) => {
1093
- self.walk(&b.value);
1094
- let mut names = Vec::new();
1095
- patternNames(b.pattern(), &mut names);
1096
- for n in names {
1097
- self.env.remove(&n);
1098
- self.views.remove(&n);
1099
- }
1100
- if let Some(g) = &b.case.guard {
1101
- self.walk(g);
1102
- }
1103
- }
1104
- Expr::Bang(b) => self.consume(&b.value, None),
1105
- Expr::Catch(c) => {
1106
- self.consume(&c.value, None);
1107
- self.walkHandler(&c.handler, false);
1108
- }
1109
- Expr::String(s) => {
1110
- for p in &s.parts {
1111
- if let StringPart::Interp(e) = p {
1112
- self.walk(e);
1113
- }
1114
- }
1115
- }
1116
- Expr::Closure(c) => self.walkClosure(c),
1117
- Expr::ErrorValue(_)
1118
- | Expr::Int(_)
1119
- | Expr::Float(_)
1120
- | Expr::BoolLit(_)
1121
- | Expr::TypeName(_) => {}
1122
- }
1123
- }
1124
- }
1125
-
1126
- /// Param `i`, clamped to a trailing variadic param.
1127
- /// Two methods take `self` and every param in the same way (as a call site sees it).
1128
- fn sameModes(a: &Fn, b: &Fn) -> bool {
1129
- let takes = |p: &Param| {
1130
- p.isOwn
1131
- || matches!(&p.ty, ParamType::Type(t) | ParamType::Variadic(t) if isGeneric(&t.name))
1132
- };
1133
- a.ownSelf == b.ownSelf
1134
- && a.params.len() == b.params.len()
1135
- && a.params.iter().zip(&b.params).all(|(x, y)| takes(x) == takes(y))
1136
- }
1137
-
1138
- fn paramAt(f: &Fn, i: usize) -> Option<&Param> {
1139
- f.params.get(i).or_else(|| {
1140
- f.params
1141
- .last()
1142
- .filter(|p| matches!(p.ty, ParamType::Variadic(_)))
1143
- })
1144
- }
1145
-
1146
- /// `a.b.c` as a string, for plain variable / field chains.
1147
- fn pathOf(e: &Expr) -> Option<String> {
1148
- match e {
1149
- Expr::Attribute(a) => match &a.attr {
1150
- AttrKind::Field(f) => Some(format!("{}.{}", pathOf(&a.object)?, f)),
1151
- _ => None,
1152
- },
1153
- Expr::Paren(i) => pathOf(i),
1154
- _ => nameOf(e),
1155
- }
1156
- }
1157
-
1158
- fn retTy(f: &Fn) -> Option<String> {
1159
- f.returns
1160
- .as_ref()
1161
- .filter(|t| !isGeneric(&t.name))
1162
- .map(|t| t.name.clone())
1163
- }
1164
-
1165
- fn patternNames(p: &CasePattern, out: &mut Vec<String>) {
1166
- match p {
1167
- CasePattern::Name(n) => out.push(n.clone()),
1168
- CasePattern::Class { fields, .. } => {
1169
- for f in fields {
1170
- patternNames(f, out);
1171
- }
1172
- }
1173
- _ => {}
1174
- }
1175
- }
1176
-
1177
- /// Join of alternative paths starting from `base`: a binding is moved after if
1178
- /// it was moved on ANY path; otherwise it is live (even if `base` had it moved,
1179
- /// when every path revived it).
1180
- fn mergeBranches(base: Env, outs: Vec<Env>) -> Env {
1181
- let mut merged = base;
1182
- for (name, b) in merged.iter_mut() {
1183
- let mut seen = false;
1184
- let mut moved = None;
1185
- for o in &outs {
1186
- if let Some(ob) = o.get(name) {
1187
- seen = true;
1188
- if moved.is_none() {
1189
- moved = ob.moved;
1190
- }
1191
- }
1192
- }
1193
- if seen {
1194
- b.moved = moved;
1195
- }
1196
- }
1197
- merged
1198
- }
plum-checker/tests/ownership_flow_test.rs DELETED
@@ -1,384 +0,0 @@
1
- #![allow(non_snake_case)]
2
- use plum_checker::ownership::checkOwnership;
3
- use plum_core::{ast::Source, AstParser};
4
-
5
- fn parse(src: &str) -> Source {
6
- let mut parser = tree_sitter::Parser::new();
7
- parser
8
- .set_language(&tree_sitter_plum::LANGUAGE.into())
9
- .unwrap();
10
- let tree = parser.parse(src, None).unwrap();
11
- assert!(
12
- !tree.root_node().has_error(),
13
- "embedded test source has parse errors:\n{src}"
14
- );
15
- AstParser::new(src).parseSource(tree.root_node())
16
- }
17
-
18
- const DEFS: &str = "\
19
- own record Sprite(x: Float) =
20
- fun step(own self) -> Sprite = Sprite(..self, x: self.x + 1.0)
21
- fun peek(self) -> Float = self.x
22
-
23
- fun take(own s: Sprite) -> Sprite = s
24
- fun look(s: Sprite) -> Float = s.x
25
- ";
26
-
27
- fn texts(body: &str) -> Vec<String> {
28
- texts_raw(&format!("{DEFS}\n{body}\n"))
29
- }
30
-
31
- fn texts_raw(src: &str) -> Vec<String> {
32
- checkOwnership(&parse(src))
33
- .into_iter()
34
- .map(|e| e.message)
35
- .collect()
36
- }
37
-
38
- fn hasMoved(t: &[String], name: &str) -> bool {
39
- t.iter()
40
- .any(|m| m.starts_with(&format!("\"{name}\" used after it was moved at ")))
41
- }
42
-
43
- #[test]
44
- fn moved_in_one_branch_used_after_join() {
45
- let t = texts("fun main(c: Bool) =\n s := Sprite(x: 1.0)\n if c\n t := take(s)\n s.x\n");
46
- assert!(hasMoved(&t, "s"), "{t:?}");
47
- }
48
-
49
- #[test]
50
- fn moved_in_both_branches_then_reassigned_ok() {
51
- let t = texts(
52
- "fun main(c: Bool) =\n s := Sprite(x: 1.0)\n if c\n t := take(s)\n else\n u := take(s)\n s = Sprite(x: 2.0)\n s.x\n",
53
- );
54
- assert!(t.is_empty(), "{t:?}");
55
- }
56
-
57
- #[test]
58
- fn early_return_move_ok() {
59
- let t = texts(
60
- "fun main(c: Bool) -> Float =\n s := Sprite(x: 1.0)\n if c\n t := take(s)\n return 0.0\n s.x\n",
61
- );
62
- assert!(t.is_empty(), "{t:?}");
63
- }
64
-
65
- #[test]
66
- fn loop_move_without_reassign() {
67
- let src = format!(
68
- "{DEFS}\nfun main() =\n s := Sprite(x: 1.0)\n for i := range 3\n t := take(s)\n"
69
- );
70
- let errs = checkOwnership(&parse(&src));
71
- let e = errs
72
- .iter()
73
- .find(|e| e.message.starts_with("\"s\" used after it was moved at "))
74
- .unwrap_or_else(|| panic!("{:?}", errs.iter().map(|e| &e.message).collect::<Vec<_>>()));
75
- let line = DEFS.lines().count() + 1 + 4;
76
- assert!(e.message.contains(&format!("moved at {line}:")), "{}", e.message);
77
- }
78
-
79
- #[test]
80
- fn loop_move_with_reassign_ok() {
81
- let t = texts(
82
- "fun main() =\n s := Sprite(x: 1.0)\n for i := range 3\n s = s.step()\n s.x\n",
83
- );
84
- assert!(t.is_empty(), "{t:?}");
85
- }
86
-
87
- #[test]
88
- fn closure_capture_borrows_only() {
89
- let bad = texts("fun main() =\n s := Sprite(x: 1.0)\n f := || take(s)\n f()\n");
90
- assert!(!bad.is_empty(), "{bad:?}");
91
- let ok = texts("fun main() =\n s := Sprite(x: 1.0)\n f := || look(s)\n f()\n s.x\n");
92
- assert!(ok.is_empty(), "{ok:?}");
93
- }
94
-
95
- const WORLD: &str = "\
96
- own record Stage(t: Float) =
97
- fun frame(own self, dt: Float) -> Stage = Stage(..self, t: self.t + dt)
98
-
99
- record World(stage: Stage, k: Int)
100
-
101
- fun grab(own s: Stage) -> Stage = s
102
- ";
103
-
104
- #[test]
105
- fn swap_field_form_ok() {
106
- let t = texts_raw(&format!(
107
- "{WORLD}\nfun run(own w: World, dt: Float) =\n w.stage = w.stage.frame(dt)\n"
108
- ));
109
- assert!(t.is_empty(), "{t:?}");
110
- let t = texts_raw(&format!(
111
- "{WORLD}\nfun main(dt: Float) =\n w := World(stage: Stage(t: 0.0), k: 1)\n w.stage = w.stage.frame(dt)\n w.stage = grab(w.stage)\n"
112
- ));
113
- assert!(t.is_empty(), "{t:?}");
114
- }
115
-
116
- #[test]
117
- fn field_move_without_swap_rejected() {
118
- let t = texts_raw(&format!(
119
- "{WORLD}\nfun main(dt: Float) =\n w := World(stage: Stage(t: 0.0), k: 1)\n s := w.stage.frame(dt)\n"
120
- ));
121
- assert!(t.iter().any(|m| m == "cannot move out of field \"stage\" of \"w\"; use the swap form"), "{t:?}");
122
- let t = texts_raw(&format!(
123
- "{WORLD}\nfun main() =\n w := World(stage: Stage(t: 0.0), k: 1)\n s := grab(w.stage)\n"
124
- ));
125
- assert!(t.iter().any(|m| m == "cannot move out of field \"stage\" of \"w\"; use the swap form"), "{t:?}");
126
- }
127
-
128
- #[test]
129
- fn embedding_own_makes_host_own() {
130
- let t = texts_raw(
131
- "own record Stage(t: Float)\n\nrecord World(Stage, k: Int)\n\nfun main() =\n w := World(t: 1.0, k: 2)\n a := w\n w.k\n",
132
- );
133
- assert!(hasMoved(&t, "w"), "{t:?}");
134
- }
135
-
136
- #[test]
137
- fn list_copy_of_own_rejected() {
138
- let t = texts("fun main(xs: Slice[Sprite], ys: Slice[Sprite]) =\n ys.push(xs.at(0))\n");
139
- assert!(
140
- t.iter().any(|m| m.starts_with("cannot store borrowed \"xs.at")),
141
- "{t:?}"
142
- );
143
- let t = texts(
144
- "fun main(xs: Slice[Sprite], ys: Slice[Sprite]) =\n s := xs.at(0)\n ys.push(s)\n",
145
- );
146
- assert!(
147
- t.iter().any(|m| m.starts_with("cannot store borrowed \"s\"")),
148
- "{t:?}"
149
- );
150
- let ok = texts("fun main(xs: Slice[Sprite]) -> Float =\n xs.at(0).x\n");
151
- assert!(ok.is_empty(), "{ok:?}");
152
- }
153
-
154
- const NEST: &str = "\
155
- own record Inner(x: Float) =
156
- fun step(own self) -> Inner = Inner(..self, x: self.x + 1.0)
157
-
158
- own record Outer(inner: Inner, k: Int)
159
- ";
160
-
161
- #[test]
162
- fn partial_move_out_of_field_rejected() {
163
- let msg = "cannot move out of field \"inner\" of \"w\"; use the swap form";
164
- let t = texts_raw(&format!(
165
- "{NEST}\nfun main() =\n w := Outer(inner: Inner(x: 1.0), k: 1)\n t := w.inner\n w.inner.x\n"
166
- ));
167
- assert!(t.iter().any(|m| m == msg), "{t:?}");
168
- let t = texts_raw(&format!(
169
- "{NEST}\nfun main() =\n w := Outer(inner: Inner(x: 1.0), k: 1)\n t := w.inner.step()\n w.inner.x\n"
170
- ));
171
- assert!(t.iter().any(|m| m == msg), "{t:?}");
172
- let t = texts_raw(&format!(
173
- "{NEST}\nfun main() =\n w := Outer(inner: Inner(x: 1.0), k: 1)\n w.inner = w.inner.step()\n w.inner.x\n"
174
- ));
175
- assert!(t.is_empty(), "{t:?}");
176
- }
177
-
178
- #[test]
179
- fn yield_moves_operand() {
180
- let t = texts("fun* gen() =\n s := Sprite(x: 1.0)\n yield s\n s.x\n");
181
- assert!(hasMoved(&t, "s"), "{t:?}");
182
- }
183
-
184
- #[test]
185
- fn inline_if_tail_consumes_branches() {
186
- let t = texts("fun f(s: Sprite, c: Bool) -> Sprite = if c then s else s\n");
187
- assert!(
188
- t.iter().any(|m| m.starts_with("cannot store borrowed \"s\"")),
189
- "{t:?}"
190
- );
191
- let t = texts(
192
- "fun f(s: Sprite, c: Bool) -> Sprite =\n if c\n s\n else\n Sprite(x: 0.0)\n",
193
- );
194
- assert!(
195
- t.iter().any(|m| m.starts_with("cannot store borrowed \"s\"")),
196
- "{t:?}"
197
- );
198
- }
199
-
200
- #[test]
201
- fn variadic_own_param_takes_every_arg() {
202
- let t = texts(
203
- "fun many(own xs: ...Sprite) =\n xs.length()\n\nfun main() =\n a := Sprite(x: 1.0)\n b := Sprite(x: 2.0)\n c := Sprite(x: 3.0)\n many(a, b, c)\n c.x\n",
204
- );
205
- assert!(hasMoved(&t, "c"), "{t:?}");
206
- }
207
-
208
- #[test]
209
- fn bang_moves_operand() {
210
- let t = texts("fun main() =\n s := Sprite(x: 1.0)\n t := s!\n s.x\n");
211
- assert!(hasMoved(&t, "s"), "{t:?}");
212
- }
213
-
214
- #[test]
215
- fn inherited_own_self_method_moves() {
216
- let t = texts_raw(
217
- "own record Base(x: Float) =\n fun step(own self) -> Base = Base(..self, x: self.x + 1.0)\n\nrecord Derived(Base(x), y: Float)\n\nfun main() =\n d := Derived(x: 1.0, y: 2.0)\n e := d.step()\n d.y\n",
218
- );
219
- assert!(hasMoved(&t, "d"), "{t:?}");
220
- }
221
-
222
- #[test]
223
- fn int_param_alias_not_tracked_with_own_present() {
224
- let t = texts_raw("own record Cell(x: Float)\n\nfun f(i: Int) -> Int =\n k := i\n k\n");
225
- assert!(t.is_empty(), "{t:?}");
226
- }
227
-
228
- #[test]
229
- fn std_str_checks_with_own_present() {
230
- let dir = std::env::temp_dir().join(format!("plum_own_std_{}", std::process::id()));
231
- std::fs::create_dir_all(&dir).unwrap();
232
- let path = dir.join("a.plum");
233
- std::fs::write(
234
- &path,
235
- "import std/Int\nimport std/Float\nimport std/Str\n\nown record Cell(x: Float)\n\nfun main() -> Int =\n 1\n",
236
- )
237
- .unwrap();
238
- let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
239
- let src = plum_core::loadAndMerge(&path, &root).unwrap();
240
- let t: Vec<String> = checkOwnership(&src).into_iter().map(|e| e.message).collect();
241
- assert!(t.is_empty(), "{t:?}");
242
- }
243
-
244
- // ---- own trait fields: generated __get_/__set_ accessors (F-T6-2) ----
245
-
246
- const TRAIT_DEFS: &str = "\
247
- own trait Actor(x: Float) =
248
- fun go(self) -> Float = 1.0
249
- fun bump(own self) -> Actor =
250
- self.x = self.x + 1.0
251
- self
252
-
253
- own record Ship(Actor(x), hp: Int)
254
-
255
- # Orc lists Mark first, so Actor stays a boxed trait (accessor dispatch).
256
- own trait Mark(m: Int)
257
-
258
- own record Orc(Mark(m), Actor(x))
259
- ";
260
-
261
- fn fullCheck(body: &str) -> Vec<String> {
262
- let mut s = parse(&format!("{TRAIT_DEFS}\n{body}\n"));
263
- assert!(plum_core::traits::lowerTraits(&mut s).is_empty());
264
- let errs = plum_core::unions::resolveMembers(&mut s);
265
- assert!(errs.is_empty(), "{errs:?}");
266
- plum_checker::checkSource(&s)
267
- .err()
268
- .unwrap_or_default()
269
- .into_iter()
270
- .map(|e| e.message)
271
- .collect()
272
- }
273
-
274
- fn lowTexts(body: &str) -> Vec<String> {
275
- let mut s = parse(&format!("{TRAIT_DEFS}\n{body}\n"));
276
- let errs = plum_core::traits::lowerTraits(&mut s);
277
- assert!(errs.is_empty(), "{errs:?}");
278
- checkOwnership(&s).into_iter().map(|e| e.message).collect()
279
- }
280
-
281
- #[test]
282
- fn own_trait_field_setter_not_flagged() {
283
- let t = lowTexts("fun main() -> Float =\n s := Ship(x: 1.0, hp: 2)\n s.x\n");
284
- assert!(t.is_empty(), "{t:?}");
285
- }
286
-
287
- /// Ownership messages for `fun f(<param>) = a.<tag>(1.0)` with the call renamed to
288
- /// `__set_x`, the form union lowering leaves for a field write on a trait value.
289
- fn setterCall(param: &str) -> Vec<String> {
290
- use plum_core::ast::{AttrKind, Expr, FnBody, Item};
291
- let mut s = parse(&format!("{TRAIT_DEFS}\nfun f({param}) = a.setx(1.0)\n"));
292
- assert!(plum_core::traits::lowerTraits(&mut s).is_empty());
293
- let mut renamed = false;
294
- for i in &mut s.items {
295
- if let Item::Fn(f) = i {
296
- if f.name == "f" {
297
- if let FnBody::Expr(Expr::Attribute(a)) = &mut f.body {
298
- if let AttrKind::Method(c) = &mut a.attr {
299
- c.name = plum_core::traits::setter("x");
300
- renamed = true;
301
- }
302
- }
303
- }
304
- }
305
- }
306
- assert!(renamed);
307
- checkOwnership(&s).into_iter().map(|e| e.message).collect()
308
- }
309
-
310
- #[test]
311
- fn own_trait_field_write_through_borrow_rejected() {
312
- let t = setterCall("a: Actor");
313
- assert_eq!(t, vec!["cannot assign field \"x\" of borrowed \"a\"".to_string()]);
314
- }
315
-
316
- #[test]
317
- fn own_trait_field_write_on_own_param_ok() {
318
- let t = setterCall("own a: Actor");
319
- assert!(t.is_empty(), "{t:?}");
320
- }
321
-
322
- #[test]
323
- fn own_trait_field_write_source_form_rejected() {
324
- // Full pipeline: `a.x = 1.0` on a boxed trait value.
325
- let t = fullCheck("fun f(a: Actor) =\n a.x = 1.0\n");
326
- assert!(t.iter().any(|m| m == "cannot assign field \"x\" of borrowed \"a\""), "{t:?}");
327
- let t = fullCheck("fun f(own a: Actor) =\n a.x = 1.0\n");
328
- assert!(t.is_empty(), "{t:?}");
329
- }
330
-
331
- #[test]
332
- fn own_trait_field_write_on_own_self_ok() {
333
- let t = lowTexts("fun main() -> Float =\n s := Ship(x: 1.0, hp: 2)\n s.bump().go()\n");
334
- assert!(t.is_empty(), "{t:?}");
335
- }
336
-
337
- const HANDLED: &str = "\
338
- own record Handled(actor: Sprite, events: Slice[Sprite]) =
339
- fun intoActor(own self) -> Sprite =
340
- match self
341
- Handled(a, _) => a
342
- ";
343
-
344
- #[test]
345
- fn match_own_scrutinee_moves_out_ok() {
346
- let t = texts(&format!("{HANDLED}\nfun main(own h: Handled) -> Sprite = h.intoActor()\n"));
347
- assert!(t.is_empty(), "{t:?}");
348
- }
349
-
350
- #[test]
351
- fn match_own_scrutinee_then_use_rejected() {
352
- let t = texts(
353
- "own record Handled(actor: Sprite, events: Slice[Sprite])\n\nfun main(own h: Handled) -> Slice[Sprite] =\n match h\n Handled(a, _) => take(a)\n h.events\n",
354
- );
355
- assert!(hasMoved(&t, "h"), "{t:?}");
356
- }
357
-
358
- #[test]
359
- fn match_borrowed_scrutinee_store_rejected() {
360
- let t = texts(
361
- "own record Handled(actor: Sprite, events: Slice[Sprite])\n\nfun main(h: Handled, xs: Slice[Sprite]) =\n match h\n Handled(a, _) => xs.push(a)\n",
362
- );
363
- assert!(t.iter().any(|m| m.starts_with("cannot store borrowed \"a\"")), "{t:?}");
364
- let ok = texts(
365
- "own record Handled(actor: Sprite, events: Slice[Sprite])\n\nfun main(h: Handled) -> Float =\n match h\n Handled(a, _) => a.x\n",
366
- );
367
- assert!(ok.is_empty(), "{ok:?}");
368
- }
369
-
370
- #[test]
371
- fn match_pattern_name_use_after_move() {
372
- let t = texts(
373
- "own record Handled(actor: Sprite, events: Slice[Sprite])\n\nfun main(own h: Handled) -> Float =\n match h\n Handled(a, _) =>\n t := take(a)\n a.x\n",
374
- );
375
- assert!(hasMoved(&t, "a"), "{t:?}");
376
- }
377
-
378
- #[test]
379
- fn own_union_member_use_after_move() {
380
- let t = texts_raw(
381
- "own record Token(id: Int)\nown record Stamp(id: Int)\nenum Mark = Token | Stamp\n\nfun takeMark(own m: Mark) -> Mark = m\n\nfun main() -> Int =\n t := Token(id: 3)\n m: Mark := takeMark(t)\n t.id\n",
382
- );
383
- assert!(hasMoved(&t, "t"), "{t:?}");
384
- }
plum-checker/tests/ownership_moves_test.rs DELETED
@@ -1,232 +0,0 @@
1
- #![allow(non_snake_case)]
2
- use plum_checker::ownership::checkOwnership;
3
- use plum_checker::{checkSource, types::CheckError};
4
- use plum_core::{ast::Source, AstParser};
5
-
6
- fn parse(src: &str) -> Source {
7
- let mut parser = tree_sitter::Parser::new();
8
- parser
9
- .set_language(&tree_sitter_plum::LANGUAGE.into())
10
- .unwrap();
11
- let tree = parser.parse(src, None).unwrap();
12
- assert!(
13
- !tree.root_node().has_error(),
14
- "embedded test source has parse errors:\n{src}"
15
- );
16
- AstParser::new(src).parseSource(tree.root_node())
17
- }
18
-
19
- const DEFS: &str = "\
20
- own record Sprite(x: Float) =
21
- fun step(own self) -> Sprite = Sprite(..self, x: self.x + 1.0)
22
- fun peek(self) -> Float = self.x
23
-
24
- fun take(own s: Sprite) -> Sprite = s
25
- fun look(s: Sprite) -> Float = s.x
26
- ";
27
-
28
- fn errors(body: &str) -> Vec<CheckError> {
29
- checkOwnership(&parse(&format!("{DEFS}\n{body}\n")))
30
- }
31
-
32
- fn texts(body: &str) -> Vec<String> {
33
- errors(body).into_iter().map(|e| e.message).collect()
34
- }
35
-
36
- #[test]
37
- fn use_after_move_to_own_param() {
38
- let t = texts("fun main() =\n s := Sprite(x: 1.0)\n t := take(s)\n s.x\n");
39
- assert!(
40
- t.iter().any(|m| m.starts_with("\"s\" used after it was moved at ")),
41
- "{t:?}"
42
- );
43
- }
44
-
45
- #[test]
46
- fn use_after_spread_move() {
47
- let t = texts("fun main() =\n s := Sprite(x: 2.0)\n t := Sprite(..s, x: 1.0)\n s.x\n");
48
- assert!(
49
- t.iter().any(|m| m.starts_with("\"s\" used after it was moved at ")),
50
- "{t:?}"
51
- );
52
- }
53
-
54
- #[test]
55
- fn use_after_own_self_call() {
56
- let t = texts("fun main() =\n s := Sprite(x: 2.0)\n t := s.step()\n s.x\n");
57
- assert!(
58
- t.iter().any(|m| m.starts_with("\"s\" used after it was moved at ")),
59
- "{t:?}"
60
- );
61
- }
62
-
63
- #[test]
64
- fn reassign_revives() {
65
- let t = texts("fun main() =\n s := Sprite(x: 2.0)\n s = s.step()\n s.x\n");
66
- assert!(t.is_empty(), "{t:?}");
67
- }
68
-
69
- #[test]
70
- fn borrow_param_field_write_rejected() {
71
- let t = texts("fun bump(s: Sprite) =\n s.x = 1.0\n");
72
- assert!(
73
- t.contains(&"cannot assign field \"x\" of borrowed \"s\"".to_string()),
74
- "{t:?}"
75
- );
76
- }
77
-
78
- #[test]
79
- fn own_param_field_write_allowed() {
80
- let t = texts("fun bump(own s: Sprite) =\n s.x = 1.0\n");
81
- assert!(t.is_empty(), "{t:?}");
82
- }
83
-
84
- #[test]
85
- fn borrow_reads_do_not_move() {
86
- let t = texts("fun main() =\n s := Sprite(x: 2.0)\n a := look(s)\n b := s.peek()\n c := s.x\n s.x\n");
87
- assert!(t.is_empty(), "{t:?}");
88
- }
89
-
90
- #[test]
91
- fn store_borrow_rejected() {
92
- let t = texts("fun keep(s: Sprite, xs: Slice[Sprite]) =\n xs.push(s)\n");
93
- assert!(
94
- t.contains(&"cannot store borrowed \"s\"; pass it with own or move it".to_string()),
95
- "{t:?}"
96
- );
97
- }
98
-
99
- #[test]
100
- fn ordinary_record_unaffected() {
101
- let src = "\
102
- record Plain(x: Float)
103
-
104
- fun take(p: Plain) -> Plain = p
105
-
106
- fun main() -> Float =
107
- a := Plain(x: 1.0)
108
- b := a
109
- c := take(a)
110
- a.x = 2.0
111
- a.x
112
- ";
113
- assert!(checkOwnership(&parse(src)).is_empty());
114
- let r = checkSource(&parse(src));
115
- assert!(r.is_ok(), "{:?}", r.err());
116
- }
117
-
118
- #[test]
119
- fn embedding_own_makes_own() {
120
- let src = "\
121
- own record Base(x: Float)
122
-
123
- record Derived(Base(x), y: Float)
124
-
125
- fun take(own d: Derived) -> Derived = d
126
-
127
- fun main() =
128
- d := Derived(x: 1.0, y: 2.0)
129
- e := take(d)
130
- d.y
131
- ";
132
- let t: Vec<String> = checkOwnership(&parse(src)).into_iter().map(|e| e.message).collect();
133
- assert!(
134
- t.iter().any(|m| m.starts_with("\"d\" used after it was moved at ")),
135
- "{t:?}"
136
- );
137
- }
138
-
139
- #[test]
140
- fn message_has_move_position() {
141
- let errs = errors("fun main() =\n s := Sprite(x: 1.0)\n t := take(s)\n s.x\n");
142
- let e = errs
143
- .iter()
144
- .find(|e| e.message.contains("used after it was moved at"))
145
- .expect("error");
146
- // DEFS is 6 lines + blank; `t := take(s)` is source line 10.
147
- let line = DEFS.lines().count() + 1 + 3;
148
- assert!(
149
- e.message.contains(&format!("moved at {line}:")),
150
- "{}",
151
- e.message
152
- );
153
- }
154
-
155
- #[test]
156
- fn alias_local_moves_source() {
157
- let t = texts("fun main() =\n s := Sprite(x: 1.0)\n t := s\n s.x\n");
158
- assert!(t.iter().any(|m| m.starts_with("\"s\" used after it was moved at ")), "{t:?}");
159
- }
160
-
161
- #[test]
162
- fn alias_borrowed_param_rejected() {
163
- let t = texts("fun f(s: Sprite) -> Float =\n t := s\n t.x\n");
164
- assert!(t.iter().any(|m| m.starts_with("cannot store borrowed \"s\"")), "{t:?}");
165
- }
166
-
167
- const NODE_DEFS: &str = "\
168
- import std/Float
169
- import std/Slice
170
-
171
- own record Node(id: Int, x: Float, kind: Kind)
172
-
173
- enum Kind =
174
- | Group(children: Slice[Node])
175
- | Custom(actor: Actor)
176
-
177
- trait Actor =
178
- fun act(self, own n: Node, dt: Float) -> Node
179
-
180
- fun consume(own n: Node) -> Node = n
181
- fun look(n: Node) -> Float = n.x
182
- fun first(n: Node) -> Node? = match n.kind
183
- Group(ks) => ks.at(0)
184
- _ => null
185
- fun actorOf(n: Node) -> Actor? = match n.kind
186
- Custom(a) => a
187
- _ => null
188
- ";
189
-
190
- fn node_texts(body: &str) -> Vec<String> {
191
- checkOwnership(&parse(&format!("{NODE_DEFS}\n{body}\n")))
192
- .into_iter()
193
- .map(|e| e.message)
194
- .collect()
195
- }
196
-
197
- #[test]
198
- fn own_field_used_after_move_rejected() {
199
- let t = node_texts("fun f(own n: Node) -> Float =\n m := consume(n)\n n.x\n");
200
- assert!(t.iter().any(|m| m.starts_with("\"n\" used after it was moved")), "{t:?}");
201
- }
202
-
203
- #[test]
204
- fn own_child_projection_after_move_rejected() {
205
- let t = node_texts(
206
- "fun f(own n: Node) -> Float =\n match n.kind\n Group(ks) =>\n m := consume(n)\n ks.len()\n _ => 0.0\n",
207
- );
208
- assert!(t.iter().any(|m| m.contains("used after it was moved")), "{t:?}");
209
- // An element read out of the child slice is a view too.
210
- let t2 = node_texts(
211
- "fun f(own n: Node) -> Float =\n match n.kind\n Group(ks) =>\n c := ks.at(0)\n m := consume(n)\n look(c)\n _ => 0.0\n",
212
- );
213
- assert!(t2.iter().any(|m| m.contains("used after it was moved")), "{t2:?}");
214
- // An own-typed payload binder is a view into the scrutinee's root.
215
- let defs = "own record Cell(id: Int, kind: Shape)\nenum Shape =\n | Pair(first: Cell)\n | Leaf(x: Float)\nfun eat(own n: Cell) -> Cell = n\nfun peek(n: Cell) -> Int = n.id\n";
216
- let body = "fun f(own n: Cell) -> Int =\n match n.kind\n Pair(k) =>\n m := eat(n)\n peek(k)\n _ => 0\n";
217
- let t3: Vec<String> = checkOwnership(&parse(&format!("{defs}\n{body}\n"))).into_iter().map(|e| e.message).collect();
218
- assert!(t3.iter().any(|m| m.contains("used after it was moved")), "{t3:?}");
219
- let ok = "fun f(own n: Cell) -> Int =\n match n.kind\n Pair(k) =>\n v := peek(k)\n m := eat(n)\n v\n _ => 0\n";
220
- let t4: Vec<String> = checkOwnership(&parse(&format!("{defs}\n{ok}\n"))).into_iter().map(|e| e.message).collect();
221
- assert!(t4.is_empty(), "{t4:?}");
222
- }
223
-
224
- #[test]
225
- fn non_own_value_read_before_move_ok() {
226
- let t = node_texts(
227
- "fun f(own n: Node, dt: Float) -> Node =\n if a := actorOf(n)\n return a.act(n, dt)\n n\n",
228
- );
229
- assert!(t.is_empty(), "{t:?}");
230
- let t2 = node_texts("fun g(own n: Node) -> Float =\n v := n.x\n m := consume(n)\n v\n");
231
- assert!(t2.is_empty(), "{t2:?}");
232
- }
plum-checker/tests/ownership_trait_args_test.rs DELETED
@@ -1,73 +0,0 @@
1
- #![allow(non_snake_case)]
2
- use plum_checker::ownership::checkOwnership;
3
- use plum_core::{ast::Source, AstParser};
4
-
5
- fn parse(src: &str) -> Source {
6
- let mut parser = tree_sitter::Parser::new();
7
- parser
8
- .set_language(&tree_sitter_plum::LANGUAGE.into())
9
- .unwrap();
10
- let tree = parser.parse(src, None).unwrap();
11
- assert!(
12
- !tree.root_node().has_error(),
13
- "embedded test source has parse errors:\n{src}"
14
- );
15
- AstParser::new(src).parseSource(tree.root_node())
16
- }
17
-
18
- const PROBE: &str = include_str!("fixtures/custom_actor_probe2.plum");
19
-
20
- fn texts(body: &str) -> Vec<String> {
21
- checkOwnership(&parse(&format!("{PROBE}\n{body}\n")))
22
- .into_iter()
23
- .map(|e| e.message)
24
- .collect()
25
- }
26
-
27
- #[test]
28
- fn trait_method_borrowed_arg_ok() {
29
- let t = texts("");
30
- assert!(t.is_empty(), "{t:?}");
31
- }
32
-
33
- #[test]
34
- fn trait_method_own_arg_moves() {
35
- let t = texts("fun g(own n: Node, a: Actor) -> Float =\n m := a.act(n, 0.1)\n n.x\n");
36
- assert!(t.iter().any(|m| m.starts_with("\"n\" used after it was moved")), "{t:?}");
37
- }
38
-
39
- #[test]
40
- fn borrowed_to_own_trait_param_rejected() {
41
- let t = texts("fun h(n: Node, a: Actor) -> Node = a.act(n, 0.1)\n");
42
- assert!(t.iter().any(|m| m.contains("cannot store borrowed \"n\"")), "{t:?}");
43
- }
44
-
45
- #[test]
46
- fn plain_fn_borrowed_arg_still_ok() {
47
- let t = texts("fun look(n: Node) -> Float = n.x\nfun k(n: Node) -> Float = look(n)\n");
48
- assert!(t.is_empty(), "{t:?}");
49
- }
50
-
51
- #[test]
52
- fn same_name_method_modes_disagree_is_conservative() {
53
- let t = texts(
54
- "trait Probe =\n fun f(self, n: Node) -> Float\n\nrecord Sq(Probe, w: Float) =\n override fun f(self, n: Node) -> Float = n.x\n\nrecord Other(w: Float) =\n fun f(self, own n: Node) -> Float = n.x\n\nfun u(n: Node) -> Float = match n.kind\n Custom(a) => a.f(n)\n _ => 0.0\n",
55
- );
56
- assert!(t.iter().any(|m| m.contains("cannot store borrowed \"n\"")), "{t:?}");
57
- }
58
-
59
- #[test]
60
- fn same_name_method_modes_agree_uses_them() {
61
- let t = texts(
62
- "record Other(w: Float) =\n fun score(self, n: Node) -> Float = n.x\n\nfun u(n: Node) -> Float = match n.kind\n Custom(a) => a.score(n)\n _ => 0.0\n",
63
- );
64
- assert!(t.is_empty(), "{t:?}");
65
- }
66
-
67
- #[test]
68
- fn pattern_bound_trait_receiver_resolves_despite_same_name_method() {
69
- let t = texts(
70
- "trait Drawer =\n fun draw(self, n: Node) -> Float\n\nrecord Pen(Drawer, w: Float) =\n override fun draw(self, n: Node) -> Float = n.x\n\nrecord Stage2(w: Float) =\n fun draw(self) -> Float = self.w\n\nenum Hold =\n | Mine(d: Drawer)\n\nown record Item(h: Hold)\n\nfun u(i: Item, n: Node) -> Float = match i.h\n Mine(d) => d.draw(n)\n",
71
- );
72
- assert!(t.is_empty(), "{t:?}");
73
- }
plum-core/tests/forwarder_own_test.rs DELETED
@@ -1,51 +0,0 @@
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
10
- .set_language(&tree_sitter_plum::LANGUAGE.into())
11
- .unwrap();
12
- let tree = parser.parse(src, None).unwrap();
13
- assert!(!tree.root_node().has_error(), "{}", tree.root_node().to_sexp());
14
- AstParser::new(src).parseSource(tree.root_node())
15
- }
16
-
17
- const SRC: &str = "struct Circle(r: Float) =\n fun grow(self, own c: Circle, k: Float) -> Float = 1.0\nstruct Square(w: Float) =\n fun grow(self, own c: Circle, k: Float) -> Float = 2.0\nenum Shape = Circle | Square\nfun f(s: Shape, c: Circle) -> Float =\n s.grow(c, 1.0)\n";
18
-
19
- #[test]
20
- fn synthesized_forwarder_copies_own_flags() {
21
- let mut s = parse(SRC);
22
- let call = s
23
- .items
24
- .iter()
25
- .find_map(|i| match i {
26
- Item::Fn(f) if f.name == "f" => match &f.body {
27
- FnBody::Block(b) => match &b.stmts[0] {
28
- Stmt::Expr(e, ..) => Some(e as *const Expr as usize),
29
- _ => None,
30
- },
31
- _ => None,
32
- },
33
- _ => None,
34
- })
35
- .unwrap();
36
- let mut plan = UnionPlan::default();
37
- plan.dispatches
38
- .insert(call, vec!["Shape__Circle".into(), "Shape__Square".into()]);
39
- applyPlan(&mut s, &plan);
40
- let g = s
41
- .items
42
- .iter()
43
- .find_map(|i| match i {
44
- Item::Fn(f) if f.name == "grow" && f.type_param.as_deref() == Some("Shape") => Some(f),
45
- _ => None,
46
- })
47
- .expect("no synthesized grow");
48
- let own: Vec<bool> = g.params.iter().map(|p| p.isOwn).collect();
49
- assert_eq!(own, vec![true, false]);
50
- assert!(!g.ownSelf);
51
- }
plum-core/tests/own_parse_test.rs DELETED
@@ -1,84 +0,0 @@
1
- #![allow(non_snake_case)]
2
- use plum_core::ast::*;
3
- use plum_core::{formatSource, AstParser};
4
-
5
- fn parseItems(src: &str) -> Vec<Item> {
6
- let mut parser = tree_sitter::Parser::new();
7
- parser
8
- .set_language(&tree_sitter_plum::LANGUAGE.into())
9
- .unwrap();
10
- let tree = parser.parse(src, None).unwrap();
11
- assert!(
12
- !tree.root_node().has_error(),
13
- "parse error:\n{}",
14
- tree.root_node().to_sexp()
15
- );
16
- AstParser::new(src).parseSourceRaw(tree.root_node()).items
17
- }
18
-
19
- fn enums(src: &str) -> Vec<Enum> {
20
- parseItems(src)
21
- .into_iter()
22
- .filter_map(|i| if let Item::Enum(e) = i { Some(e) } else { None })
23
- .collect()
24
- }
25
-
26
- fn traits(src: &str) -> Vec<TraitDecl> {
27
- parseItems(src)
28
- .into_iter()
29
- .filter_map(|i| if let Item::Trait(t) = i { Some(t) } else { None })
30
- .collect()
31
- }
32
-
33
- fn fns(src: &str) -> Vec<Fn> {
34
- parseItems(src)
35
- .into_iter()
36
- .filter_map(|i| if let Item::Fn(f) = i { Some(f) } else { None })
37
- .collect()
38
- }
39
-
40
- #[test]
41
- fn own_record_flag() {
42
- assert!(enums("own record Sprite(x: Float)\n")[0].isOwn);
43
- assert!(!enums("record Pt(x: Float)\n")[0].isOwn);
44
- }
45
-
46
- #[test]
47
- fn own_trait_flag() {
48
- assert!(traits("own trait Actor(x: Float)\n")[0].isOwn);
49
- assert!(!traits("trait Actor(x: Float)\n")[0].isOwn);
50
- }
51
-
52
- #[test]
53
- fn own_self_flag() {
54
- let f = &fns("fun act(own self, dt: Float) -> Actor = dt\n")[0];
55
- assert!(f.ownSelf && f.has_self);
56
- assert_eq!(f.params.len(), 1);
57
- assert_eq!(f.params[0].name, "dt");
58
- assert!(!f.params[0].isOwn);
59
- assert!(!fns("fun act(self, dt: Float) -> Actor = dt\n")[0].ownSelf);
60
- }
61
-
62
- #[test]
63
- fn own_param_flag() {
64
- let f = &fns("fun put(own v: Sprite, w: Sprite) = v\n")[0];
65
- assert!(!f.ownSelf);
66
- assert!(f.params[0].isOwn);
67
- assert!(!f.params[1].isOwn);
68
- }
69
-
70
- #[test]
71
- fn owner_identifier_still_parses() {
72
- let f = &fns("fun ownBy() =\n owner := 1\n owner\n")[0];
73
- assert_eq!(f.name, "ownBy");
74
- }
75
-
76
- #[test]
77
- fn own_record_round_trips_through_format() {
78
- let src = "own record Sprite(x: Float) =\n fun put(own self, own v: Sprite) = v\n";
79
- let out = formatSource(src).expect("format");
80
- assert!(out.contains("own record Sprite"), "{out}");
81
- assert!(out.contains("own self"), "{out}");
82
- assert!(out.contains("own v: Sprite"), "{out}");
83
- assert_eq!(formatSource(&out).unwrap(), out);
84
- }
plum-tooling/tree-sitter-plum/test/corpus/own.txt DELETED
@@ -1,72 +0,0 @@
1
- ==================
2
- own record
3
- ==================
4
- own record Sprite(x: Float)
5
- ---
6
-
7
- (source
8
- (record
9
- (type_identifier)
10
- (enum_named_field
11
- (var_identifier)
12
- (type
13
- (type_identifier)))))
14
-
15
- ==================
16
- own trait
17
- ==================
18
- own trait Actor(x: Float)
19
- ---
20
-
21
- (source
22
- (trait
23
- (type_identifier)
24
- (enum_param
25
- (var_identifier)
26
- (type
27
- (type_identifier)))))
28
-
29
- ==================
30
- own self and own param
31
- ==================
32
- fun act(own self, own v: Sprite, dt: Float) -> Actor = v
33
- ---
34
-
35
- (source
36
- (fn
37
- (fn_identifier)
38
- (self)
39
- (param
40
- (var_identifier)
41
- (type
42
- (type_identifier)))
43
- (param
44
- (var_identifier)
45
- (type
46
- (type_identifier)))
47
- (type
48
- (type_identifier))
49
- (expression
50
- (primary_expression
51
- (var_identifier)))))
52
-
53
- ==================
54
- own prefix identifiers
55
- ==================
56
- fun ownBy() =
57
- owner := 1
58
- owner
59
- ---
60
-
61
- (source
62
- (fn
63
- (fn_identifier)
64
- (body
65
- (assign
66
- (var_identifier)
67
- (expression
68
- (primary_expression
69
- (integer))))
70
- (expression
71
- (primary_expression
72
- (var_identifier))))))
plum-wasm-codegen/tests/{own_inplace_e2e_test.rs → unique_inplace_e2e_test.rs} RENAMED
@@ -42,23 +42,19 @@ fn runInt(name: &str, src: &str) -> i64 {
42
42
  }
43
43
 
44
44
 
45
+ /// True when the real pipeline (checker `typedSource`, fusion on) flags the spread in
45
- /// Number of `struct.new` instructions in the whole module.
46
+ /// fn `name` as unique, i.e. proved written in place by uniqueness alone.
46
- fn structNewCount(name: &str, src: &str) -> usize {
47
+ fn spreadIsUnique(name: &str, fnName: &str, src: &str) -> bool {
47
48
  let source = load(name, src).expect("load");
48
- let bytes = compileSource(&source).unwrap_or_else(|e| panic!("compile failed: {e}"));
49
+ let info = plum_checker::typedSource(&source).expect("checks");
49
- wasmparser::validate(&bytes).expect("valid wasm");
50
+ let low = info.lowered.as_ref().expect("lowered");
50
- let mut n = 0;
51
+ low.items
51
- for payload in wasmparser::Parser::new(0).parse_all(&bytes) {
52
- if let wasmparser::Payload::CodeSectionEntry(f) = payload.unwrap() {
53
- let mut r = f.get_operators_reader().unwrap();
54
- while !r.eof() {
52
+ .iter()
53
+ .find_map(|i| match i {
55
- if matches!(r.read().unwrap(), wasmparser::Operator::StructNew { .. }) {
54
+ plum_core::ast::Item::Fn(f) if f.name == fnName => Some(format!("{:?}", f.body).contains("unique: true")),
56
- n += 1;
55
+ _ => None,
57
- }
56
+ })
58
- }
59
- }
60
- }
61
- n
57
+ .expect("fn kept")
62
58
  }
63
59
 
64
60
  const PRE: &str = "import std/Str\nimport std/Int\nimport std/Float\nimport std/Slice\n\n";
@@ -78,16 +74,12 @@ fun main() -> Int =
78
74
  t.x * 100 + t.y
79
75
  ";
80
76
 
81
- /// The only difference between the two programs is `own`; the own one must
77
+ /// A spread of a parameter nobody else holds is proved unique, with no annotation.
82
- /// allocate one object fewer (the spread reuses its source).
83
78
  #[test]
84
- fn own_spread_reuses_object() {
79
+ fn unique_spread_reuses_object() {
85
- let plain = prog(SPREAD);
80
+ let unique = prog(SPREAD);
86
- let own = prog(&SPREAD.replace("record Cell", "own record Cell").replace("fun adv(s: Cell)", "fun adv(own s: Cell)"));
87
- let a = structNewCount("reuse_plain", &plain);
81
+ assert!(spreadIsUnique("reuse_flag", "adv", &unique));
88
- let b = structNewCount("reuse_own", &own);
89
- assert_eq!(a, b + 1, "plain {a} struct.new, own {b}");
90
- assert_eq!(runInt("reuse_run", &own), 207);
82
+ assert_eq!(runInt("reuse_run", &unique), 207);
91
83
  }
92
84
 
93
85
  #[test]
@@ -103,8 +95,8 @@ fun main() -> Int =
103
95
  }
104
96
 
105
97
  #[test]
106
- fn own_spread_sets_fields() {
98
+ fn unique_spread_sets_fields() {
107
- let src = prog("own record Cell(x: Int, y: Int, z: Int)
99
+ let src = prog("record Cell(x: Int, y: Int, z: Int)
108
100
 
109
101
  fun main() -> Int =
110
102
  s := Cell(x: 1, y: 2, z: 3)
@@ -116,8 +108,8 @@ fun main() -> Int =
116
108
 
117
109
  /// A value reading an already-overwritten field must still see the OLD value.
118
110
  #[test]
119
- fn own_spread_swap_reads_old_values() {
111
+ fn unique_spread_swap_reads_old_values() {
120
- let src = prog("own record Cell(x: Int, y: Int)
112
+ let src = prog("record Cell(x: Int, y: Int)
121
113
 
122
114
  fun main() -> Int =
123
115
  s := Cell(x: 1, y: 2)
@@ -128,26 +120,24 @@ fun main() -> Int =
128
120
  }
129
121
 
130
122
  #[test]
131
- fn own_trait_record_spread_in_place() {
132
- let own = prog("own trait Actor =\n fun go(self) -> Int = 7\n\nown record Ship(Actor, x: Int, hp: Int)\n\nfun hit(own s: Ship) -> Ship = Ship(..s, hp: s.hp - 1, x: s.x + 5)\n\nfun main() -> Int =\n s := Ship(x: 1, hp: 10)\n t := hit(s)\n t.go() * 100 + t.hp * 10 + t.x\n");
133
- let plain = prog("trait Actor =\n fun go(self) -> Int = 7\n\nrecord Ship(Actor, x: Int, hp: Int)\n\nfun hit(s: Ship) -> Ship = Ship(..s, hp: s.hp - 1, x: s.x + 5)\n\nfun main() -> Int =\n s := Ship(x: 1, hp: 10)\n t := hit(s)\n t.go() * 100 + t.hp * 10 + t.x\n");
134
- assert_eq!(runInt("own_trait_record_spread_in_place", &own), 700 + 90 + 6);
135
- assert_eq!(structNewCount("own_trait_record_spread_in_place_p", &plain), structNewCount("own_trait_record_spread_in_place_o", &own) + 1);
123
+ fn unique_trait_record_spread_in_place() {
124
+ let unique = prog("trait Actor =\n fun go(self) -> Int = 7\n\nrecord Ship(Actor, x: Int, hp: Int)\n\nfun hit(s: Ship) -> Ship = Ship(..s, hp: s.hp - 1, x: s.x + 5)\n\nfun main() -> Int =\n s := Ship(x: 1, hp: 10)\n t := hit(s)\n t.go() * 100 + t.hp * 10 + t.x\n");
125
+ assert_eq!(runInt("unique_trait_record_spread_in_place", &unique), 700 + 90 + 6);
126
+ assert!(spreadIsUnique("unique_trait_record_flag", "hit", &unique));
136
127
  }
137
128
 
138
- /// Generated `__set_x` accessors of an own trait field are not checked as user code (F-T6-2).
129
+ /// Generated `__set_x` accessors of a unique trait field are not checked as user code (F-T6-2).
139
130
  #[test]
140
- fn own_trait_field_record_spread_in_place() {
141
- let own = prog("own trait Actor(x: Int) =\n fun go(self) -> Int = 7\n\nown record Ship(Actor(x), hp: Int)\n\nfun hit(own s: Ship) -> Ship = Ship(..s, hp: s.hp - 1, x: s.x + 5)\n\nfun main() -> Int =\n s := Ship(x: 1, hp: 10)\n t := hit(s)\n t.go() * 100 + t.hp * 10 + t.x\n");
142
- let plain = prog("trait Actor(x: Int) =\n fun go(self) -> Int = 7\n\nrecord Ship(Actor(x), hp: Int)\n\nfun hit(s: Ship) -> Ship = Ship(..s, hp: s.hp - 1, x: s.x + 5)\n\nfun main() -> Int =\n s := Ship(x: 1, hp: 10)\n t := hit(s)\n t.go() * 100 + t.hp * 10 + t.x\n");
143
- assert_eq!(runInt("own_trait_field_record_spread_in_place", &own), 700 + 90 + 6);
144
- assert_eq!(structNewCount("own_trait_field_record_spread_in_place_p", &plain), structNewCount("own_trait_field_record_spread_in_place_o", &own) + 1);
131
+ fn unique_trait_field_record_spread_in_place() {
132
+ let unique = prog("trait Actor(x: Int) =\n fun go(self) -> Int = 7\n\nrecord Ship(Actor(x), hp: Int)\n\nfun hit(s: Ship) -> Ship = Ship(..s, hp: s.hp - 1, x: s.x + 5)\n\nfun main() -> Int =\n s := Ship(x: 1, hp: 10)\n t := hit(s)\n t.go() * 100 + t.hp * 10 + t.x\n");
133
+ assert_eq!(runInt("unique_trait_field_record_spread_in_place", &unique), 700 + 90 + 6);
134
+ assert!(spreadIsUnique("unique_trait_field_flag", "hit", &unique));
145
135
  }
146
136
 
147
137
  #[test]
148
- fn own_self_field_write() {
138
+ fn unique_self_field_write() {
149
- let src = prog("own record Cell(x: Int) =
139
+ let src = prog("record Cell(mut x: Int) =
150
- fun bump(own self) -> Cell =
140
+ fun bump(self) -> Cell =
151
141
  self.x = self.x + 1
152
142
  self
153
143
 
@@ -162,8 +152,8 @@ fun main() -> Int =
162
152
  /// A later value that reads a field an earlier one overwrote, or uses the source
163
153
  /// any other way (here a method call), must still run (copy fallback).
164
154
  #[test]
165
- fn own_spread_hazard_falls_back_to_copy() {
155
+ fn unique_spread_hazard_falls_back_to_copy() {
166
- let src = prog("own record Cell(x: Int, y: Int) =
156
+ let src = prog("record Cell(x: Int, y: Int) =
167
157
  fun sum(self) -> Int = self.x + self.y
168
158
 
169
159
  fun main() -> Int =
@@ -172,7 +162,7 @@ fun main() -> Int =
172
162
  t.x * 100 + t.y
173
163
  ");
174
164
  assert_eq!(runInt("hazard_call", &src), 1000 + 3);
175
- let src = prog("own record Cell(x: Int, y: Int, z: Int)
165
+ let src = prog("record Cell(x: Int, y: Int, z: Int)
176
166
 
177
167
  fun main() -> Int =
178
168
  s := Cell(x: 1, y: 2, z: 3)
@@ -182,11 +172,11 @@ fun main() -> Int =
182
172
  assert_eq!(runInt("hazard_ok_read", &src), 1000 + 20 + 5);
183
173
  }
184
174
 
185
- /// R5: arms binding differently named locals of different own record types.
175
+ /// R5: arms binding differently named locals of different record types.
186
176
  #[test]
187
177
  fn match_arm_rename_still_valid_wasm() {
188
- let src = prog("own record Pa(x: Int)
178
+ let src = prog("record Pa(x: Int)
189
- own record Pb(y: Int, z: Int)
179
+ record Pb(y: Int, z: Int)
190
180
 
191
181
  enum Pick = Wa | Wb
192
182
 
@@ -208,10 +198,10 @@ fun main() -> Int =
208
198
  /// F-T6-3: `s = Cell(..s, ...)` on a param must reassign the param's own slot, not
209
199
  /// a fresh shadow local that every read of `s` (the spread source included) sees unset.
210
200
  #[test]
211
- fn own_spread_reassign_same_var() {
201
+ fn unique_spread_reassign_same_var() {
212
- let body = "own record Cell(x: Int, y: Int)
202
+ let body = "record Cell(x: Int, y: Int)
213
203
 
214
- fun bump(own s: Cell) -> Cell =
204
+ fun bump(s: Cell) -> Cell =
215
205
  s = Cell(..s, x: s.x + 1)
216
206
  s
217
207
 
@@ -223,23 +213,14 @@ fun main() -> Int =
223
213
  t := bump(Cell(x: 1, y: 7))
224
214
  t.x * 100 + t.y + inc(40) * 1000
225
215
  ";
226
- let own = prog(body);
216
+ let unique = prog(body);
227
- assert_eq!(runInt("reassign_param", &own), 41000 + 207);
217
+ assert_eq!(runInt("reassign_param", &unique), 41000 + 207);
228
- let plain = prog(&body.replace("own record", "record").replace("own s", "s"));
229
- assert_eq!(runInt("reassign_param_plain", &plain), 41000 + 207);
218
+ assert!(spreadIsUnique("reassign_param_flag", "bump", &unique));
230
- let p = structNewCount("reassign_param_p", &plain);
231
- let o = structNewCount("reassign_param_o", &own);
232
- if !plum_checker::fusionOptions().enabled {
233
- assert_eq!(p, o + 1);
234
- } else {
235
- // Uniqueness proves the plain reassignment unique, so it is written in place too.
236
- assert_eq!(p, o);
237
- }
238
219
  }
239
220
 
240
221
  #[test]
241
- fn own_spread_reassign_local() {
222
+ fn unique_spread_reassign_local() {
242
- let src = prog("own record Cell(x: Int, y: Int)
223
+ let src = prog("record Cell(x: Int, y: Int)
243
224
 
244
225
  fun main() -> Int =
245
226
  s := Cell(x: 1, y: 7)
@@ -251,9 +232,9 @@ fun main() -> Int =
251
232
  }
252
233
 
253
234
  #[test]
254
- fn own_self_spread_return() {
235
+ fn unique_self_spread_return() {
255
- let src = prog("own record Cell(x: Int, y: Int) =
236
+ let src = prog("record Cell(x: Int, y: Int) =
256
- fun step(own self) -> Cell = Cell(..self, x: self.x + 1)
237
+ fun step(self) -> Cell = Cell(..self, x: self.x + 1)
257
238
 
258
239
  fun main() -> Int =
259
240
  t := Cell(x: 1, y: 7).step().step()