plum

#treesitter#compiler#wasm

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

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


272d6bd — Peter John 2026-11-06T15:49:17+05:30
Simpler language: no inheritance
Files changed (81) hide show
  1. compiler/checker/src/callcheck.rs +1 -7
  2. compiler/checker/src/fncheck.rs +1 -21
  3. compiler/checker/src/infer.rs +0 -5
  4. compiler/checker/src/lib.rs +0 -1
  5. compiler/checker/src/matchcheck.rs +10 -45
  6. compiler/checker/src/patterns.rs +0 -9
  7. compiler/checker/src/state.rs +2 -6
  8. compiler/checker/src/subtyping.rs +0 -103
  9. compiler/checker/src/tables.rs +0 -1
  10. compiler/checker/src/unification.rs +3 -6
  11. compiler/checker/tests/checker/features/mutability.rs +0 -8
  12. compiler/checker/tests/checker/main.rs +0 -1
  13. compiler/checker/tests/checker/question_tests.rs +0 -6
  14. compiler/checker/tests/checker/subtyping_tests.rs +0 -124
  15. compiler/checker/tests/fixtures/custom_actor_probe2.plum +0 -58
  16. compiler/core/src/ast.rs +2 -26
  17. compiler/core/src/closure_spec.rs +0 -9
  18. compiler/core/src/ctors.rs +15 -156
  19. compiler/core/src/drive.rs +7 -21
  20. compiler/core/src/embeds.rs +0 -517
  21. compiler/core/src/layout.rs +7 -28
  22. compiler/core/src/lib.rs +0 -2
  23. compiler/core/src/lower.rs +2 -6
  24. compiler/core/src/parser.rs +4 -27
  25. compiler/core/src/purity.rs +3 -14
  26. compiler/core/src/subtyping.rs +0 -92
  27. compiler/core/src/tests_lower.rs +0 -2
  28. compiler/core/tests/core/closure_spec_test.rs +0 -16
  29. compiler/core/tests/core/formatter_test.rs +3 -20
  30. compiler/core/tests/core/layout_test.rs +0 -20
  31. compiler/core/tests/core/loader_test.rs +1 -1
  32. compiler/core/tests/core/lower_test.rs +0 -17
  33. compiler/core/tests/core/main.rs +0 -3
  34. compiler/core/tests/core/override_parse_test.rs +0 -42
  35. compiler/core/tests/core/parser_test.rs +3 -15
  36. compiler/core/tests/core/purity_test.rs +0 -11
  37. compiler/core/tests/core/record_embed_test.rs +0 -230
  38. compiler/core/tests/core/record_init_parse_test.rs +17 -12
  39. compiler/core/tests/core/record_init_test.rs +5 -48
  40. compiler/core/tests/core/subtyping_test.rs +0 -43
  41. compiler/core/tests/core/uniqueness_test.rs +4 -16
  42. compiler/lsp/src/completion.rs +1 -23
  43. compiler/lsp/src/main.rs +8 -337
  44. compiler/tooling/tree-sitter-plum/grammar.js +5 -21
  45. compiler/tooling/tree-sitter-plum/queries/plum/format.scm +0 -2
  46. compiler/tooling/tree-sitter-plum/queries/plum/highlights.scm +0 -2
  47. compiler/tooling/tree-sitter-plum/src/grammar.json +0 -0
  48. compiler/tooling/tree-sitter-plum/src/node-types.json +0 -0
  49. compiler/tooling/tree-sitter-plum/src/parser.c +0 -0
  50. compiler/tooling/tree-sitter-plum/test/corpus/fip.txt +2 -2
  51. compiler/tooling/tree-sitter-plum/test/corpus/function.txt +6 -27
  52. compiler/tooling/tree-sitter-plum/test/corpus/struct_unified.txt +3 -10
  53. compiler/tooling/tree-sitter-plum/test/corpus/trait.txt +6 -26
  54. compiler/tooling/vscode-plum/syntaxes/plum.tmLanguage.json +1 -1
  55. compiler/wasm-codegen/src/calls.rs +0 -80
  56. compiler/wasm-codegen/src/context.rs +0 -3
  57. compiler/wasm-codegen/src/driver.rs +2 -12
  58. compiler/wasm-codegen/src/emit.rs +0 -2
  59. compiler/wasm-codegen/src/fnbody.rs +1 -10
  60. compiler/wasm-codegen/src/gctypes.rs +5 -78
  61. compiler/wasm-codegen/src/inference.rs +0 -7
  62. compiler/wasm-codegen/src/keys.rs +0 -6
  63. compiler/wasm-codegen/src/lib.rs +1 -1
  64. compiler/wasm-codegen/tests/wasm_codegen/e2e_mem/unique_inplace.rs +0 -15
  65. compiler/wasm-codegen/tests/wasm_codegen/e2e_records/record_init.rs +2 -11
  66. compiler/wasm-codegen/tests/wasm_codegen/e2e_types/mod.rs +0 -1
  67. compiler/wasm-codegen/tests/wasm_codegen/e2e_types/subtyping.rs +0 -169
  68. compiler/wasm-codegen/tests/wasm_codegen/fusion_alloc_test.rs +3 -432
  69. docs/language.md +0 -32
  70. examples/structs_records.plum +10 -18
  71. examples/subtyping.plum +0 -97
  72. examples/unions.plum +10 -13
  73. tests/fbip/aliasing.plum +8 -24
  74. tests/wasm_codegen/e2e_records/base_through_embedded_struct.plum +0 -22
  75. tests/wasm_codegen/e2e_records/embedded_record_standalone_and_unselected_set_by_init.plum +0 -21
  76. tests/wasm_codegen/e2e_records/field_less_struct_embedding_field_less_base_dispatches_in_a_slice.plum +0 -29
  77. tests/wasm_codegen/e2e_records/fresh_list_per_construction.plum +2 -6
  78. tests/wasm_codegen/e2e_records/{inherited_fields_methods_and_init_order.plum → init_fields_and_order.plum} +4 -10
  79. tests/wasm_codegen/e2e_records/override_calls_original.plum +0 -24
  80. tests/wasm_codegen/e2e_records/ship_example_values.plum +0 -26
  81. tests/wasm_codegen/e2e_records/through_a_base_value.plum +0 -24
compiler/checker/src/callcheck.rs CHANGED
@@ -294,13 +294,7 @@ pub(crate) fn inferMethodCall(
294
294
  }
295
295
  return Ok(PlumType::TVar("_".to_string()));
296
296
  };
297
- // A base struct (or trait) value calls through its subtypes: the method's signature is the
298
- // first subtype's that defines it.
299
- let Some(sig) = ctx
300
- .methods
301
- .get(&(class_name.to_string(), name.to_string()))
297
+ let Some(sig) = ctx.methods.get(&(class_name.to_string(), name.to_string())) else {
302
- .or_else(|| subtyping::firstSubtype(&class_name, |sub| ctx.methods.get(&(sub.to_string(), name.to_string()))))
303
- else {
304
298
  // `rec.add(a, b)` where `add` is an fn-typed FIELD (a "record of
305
299
  // functions"): calls the stored closure. A method of the same name wins
306
300
  // (it is found just above), so this is only reached when there is none.
compiler/checker/src/fncheck.rs CHANGED
@@ -139,30 +139,10 @@ pub(crate) fn checkFnBody(f: &ast::Fn, global_env: &TypeEnv, ctx: &CheckCtx) ->
139
139
  return errors;
140
140
  }
141
141
  (false, ast::FnBody::Extern) => {
142
- // A bodiless instance method of a struct is abstract only when written
143
- // `abstract fun` (its overrides are checked by the embed lowering).
144
- if f.is_abstract {
145
- if !f.has_self || f.type_param.is_none() {
146
- errors.push(CheckError::at(
147
- f.line,
148
- f.column,
149
- format!("fn '{}': abstract is only allowed on a struct instance method", f.name),
150
- ));
151
- }
152
- return errors;
153
- }
154
142
  errors.push(CheckError::resolution(
155
143
  f.line,
156
144
  f.column,
157
- format!("fn '{}': missing a body (mark it abstract or extern)", f.name),
145
+ format!("fn '{}': missing a body (mark it extern)", f.name),
158
- ));
159
- return errors;
160
- }
161
- (false, _) if f.is_abstract => {
162
- errors.push(CheckError::at(
163
- f.line,
164
- f.column,
165
- format!("fn '{}': abstract fn must not have a body", f.name),
166
146
  ));
167
147
  return errors;
168
148
  }
compiler/checker/src/infer.rs CHANGED
@@ -447,11 +447,6 @@ pub(crate) fn inferExprInner(
447
447
  let tenv = bindCondEnv(&t.condition, env, ctx)?;
448
448
  let tt = inferExprWith(&t.then, &tenv, ctx, expected)?;
449
449
  let et = inferExprWith(&t.else_, env, ctx, expected)?;
450
- if tt != et {
451
- if let Some(anc) = subtyping::commonAncestor(&tt, &et) {
452
- return Ok(anc);
453
- }
454
- }
455
450
  unify(&tt, &et, ctx).map_err(|e| format!("inline if branches: {}", e))?;
456
451
  // Each branch pins what the other leaves open (`if c then Ok(4) else
457
452
  // Err(5)`: `Result[Int, _t1]` / `Result[_t2, Int]`); binding them both
compiler/checker/src/lib.rs CHANGED
@@ -21,7 +21,6 @@ mod stmt;
21
21
  mod tables;
22
22
  mod typeconv;
23
23
  mod unification;
24
- pub mod subtyping;
25
24
  pub mod types;
26
25
  pub use types::{CheckErrorExt, DiagKind};
27
26
 
compiler/checker/src/matchcheck.rs CHANGED
@@ -214,37 +214,6 @@ pub(crate) fn checkMatch(
214
214
  return errors; // a subject failed to type — cases can't be checked meaningfully
215
215
  }
216
216
 
217
- // Exhaustiveness: enum / Bool / error-name subjects must cover every
218
- // constructor (recursively through variant payloads) unless a catch-all
219
- // arm exists. Literal (Int/Str/Float) subjects are left alone.
220
- if let [PlumType::TNamed(tn)] = subject_types.as_slice() {
221
- // A base struct: a subtype pattern needs a `_` arm (the other subtypes are not covered).
222
- if subtyping::hasSubtypes(tn) {
223
- let hasWildcard = m.cases.iter().any(|c| {
224
- c.guard.is_none()
225
- && c.patterns.len() == 1
226
- && matches!(c.patterns[0], ast::CasePattern::Wildcard)
227
- });
228
- let namesSubtype = m.cases.iter().any(|c| {
229
- c.patterns.iter().any(|p| match p {
230
- ast::CasePattern::Class { name, .. } | ast::CasePattern::Name(name) => {
231
- name != tn && subtyping::isSubtype(name, tn)
232
- }
233
- _ => false,
234
- })
235
- });
236
- if !hasWildcard && namesSubtype {
237
- errors.push(CheckError::at(
238
- fn_pos.0,
239
- fn_pos.1,
240
- format!(
241
- "fn '{}': match on {} needs a `_` arm: other subtypes of {} are not covered",
242
- fn_name, tn, tn
243
- ),
244
- ));
245
- }
246
- }
247
- }
248
217
  if let [subject_ty] = subject_types.as_slice() {
249
218
  let rows: Vec<Vec<&ast::CasePattern>> = m
250
219
  .cases
@@ -252,23 +221,19 @@ pub(crate) fn checkMatch(
252
221
  .filter(|c| c.guard.is_none() && c.patterns.len() == 1)
253
222
  .map(|c| vec![&c.patterns[0]])
254
223
  .collect();
255
- let isBaseSubject =
256
- matches!(subject_ty, PlumType::TNamed(tn) if subtyping::hasSubtypes(tn));
257
224
  if let Err(CoverFail::Reported(label, missing)) =
258
225
  coverRows(rows, std::slice::from_ref(subject_ty), ctx)
259
226
  {
260
- if !isBaseSubject {
261
- errors.push(CheckError::at(
227
+ errors.push(CheckError::at(
262
- fn_pos.0,
228
+ fn_pos.0,
263
- fn_pos.1,
229
+ fn_pos.1,
264
- format!(
230
+ format!(
265
- "fn '{}': non-exhaustive match on {}: missing {}",
231
+ "fn '{}': non-exhaustive match on {}: missing {}",
266
- fn_name,
232
+ fn_name,
267
- label,
233
+ label,
268
- missing.join(", ")
234
+ missing.join(", ")
269
- ),
235
+ ),
270
- ));
236
+ ));
271
- }
272
237
  }
273
238
  }
274
239
 
compiler/checker/src/patterns.rs CHANGED
@@ -77,15 +77,6 @@ pub(crate) fn checkPattern(
77
77
  }
78
78
  }
79
79
  ast::CasePattern::Class { name, fields, .. } => match ctx.enum_variants.get(name) {
80
- Some(_)
81
- if nominalName(subject_ty)
82
- .and_then(|sn| subtyping::patternError(name, sn, ctx))
83
- .is_some() =>
84
- {
85
- Err(nominalName(subject_ty)
86
- .and_then(|sn| subtyping::patternError(name, sn, ctx))
87
- .unwrap_or_default())
88
- }
89
80
  // Also how a record-shaped, single-variant enum (the `type X = ...`
90
81
  // replacement) destructures — its one variant already IS the whole
91
82
  // value, so there's no tag to check, just its declared fields (in
compiler/checker/src/state.rs CHANGED
@@ -50,10 +50,6 @@ pub(crate) struct CheckState {
50
50
  /// Operator expressions (by node id) on non-primitive operands -> the synthetic method call
51
51
  /// (`a + b` -> `a.add(b)`) they resolve to.
52
52
  pub(crate) op_calls: RefCell<HashMap<plum_core::ast::NodeId, std::rc::Rc<ast::FnCall>>>,
53
- /// Nesting depth inside type arguments / fn types: conversion applies only at depth 0.
54
- pub(crate) depth: Cell<usize>,
55
- /// Struct name -> its strict ancestors, nearest first (filled by `subtyping::register`).
56
- pub(crate) ancestors: RefCell<BTreeMap<String, Vec<String>>>,
57
53
  /// Set around the top-level pattern of a `Pat := v` test: the next `checkPattern`
58
54
  /// call (that top pattern) takes it, so only that one may be a bare payload variant.
59
55
  pub(crate) allow_bare_top: Cell<bool>,
@@ -124,8 +120,8 @@ mod tests {
124
120
  // a borrow held across a call that itself reaches the state (e.g. through
125
121
  // inference) must not double-borrow the whole state
126
122
  let n = withCheck(|outer| {
127
- outer.depth.set(1);
123
+ outer.fresh.set(1);
128
- withCheck(|inner| inner.depth.get() + 1)
124
+ withCheck(|inner| inner.fresh.get() + 1)
129
125
  });
130
126
  assert_eq!(n, 2);
131
127
  resetCheck();
compiler/checker/src/subtyping.rs DELETED
@@ -1,103 +0,0 @@
1
- //! Struct subtyping in the checker. A struct whose first embed names another
2
- //! non-generic type is its subtype (`StructInfo::base`, recorded by the parser).
3
- //! `register` keeps each struct's strict ancestors (nearest first) in the
4
- //! check state so `unify` needs no `Source`.
5
-
6
- use crate::state::withCheck;
7
- use crate::types::PlumType;
8
- use crate::CheckCtx;
9
- use plum_core::ast;
10
- use std::collections::BTreeMap;
11
-
12
- /// Records every struct's strict ancestors, nearest first.
13
- pub fn register(source: &ast::Source) {
14
- let enums: BTreeMap<&str, &ast::Enum> = source
15
- .items
16
- .iter()
17
- .filter_map(|i| match i {
18
- ast::Item::Enum(e) => Some((e.name.as_str(), e)),
19
- _ => None,
20
- })
21
- .collect();
22
- let mut out: BTreeMap<String, Vec<String>> = BTreeMap::new();
23
- for e in enums.values().filter(|e| e.isStruct()) {
24
- let mut chain: Vec<String> = Vec::new();
25
- let mut cur: &ast::Enum = e;
26
- while let Some(b) = cur.base() {
27
- let Some(be) = enums.get(b).copied() else { break };
28
- if !be.generics.is_empty() || b == e.name || chain.iter().any(|c| c == b) {
29
- break;
30
- }
31
- chain.push(b.to_string());
32
- cur = be;
33
- }
34
- if !chain.is_empty() {
35
- out.insert(e.name.clone(), chain);
36
- }
37
- }
38
- withCheck(|s| *s.ancestors.borrow_mut() = out);
39
- }
40
-
41
- /// Is `sub` the same struct as `base`, or one of its subtypes?
42
- pub fn isSubtype(sub: &str, base: &str) -> bool {
43
- sub == base
44
- || withCheck(|s| s.ancestors.borrow().get(sub).is_some_and(|v| v.iter().any(|n| n == base)))
45
- }
46
-
47
- /// Does `name` have at least one subtype (is it used as a base)?
48
- pub fn hasSubtypes(name: &str) -> bool {
49
- withCheck(|s| s.ancestors.borrow().values().any(|v| v.iter().any(|n| n == name)))
50
- }
51
-
52
- /// The first subtype of `base` (name order) for which `f` finds something.
53
- pub fn firstSubtype<'a, R>(base: &str, f: impl Fn(&str) -> Option<R> + 'a) -> Option<R> {
54
- let subs: Vec<String> = withCheck(|s| {
55
- s.ancestors
56
- .borrow()
57
- .iter()
58
- .filter(|(_, v)| v.iter().any(|n| n == base))
59
- .map(|(k, _)| k.clone())
60
- .collect()
61
- });
62
- subs.iter().find_map(|n| f(n))
63
- }
64
-
65
- /// May a value of type `actual` stand where `expected` is wanted? Top level
66
- /// only: the caller (`unify`) skips this inside type arguments.
67
- pub fn accepts(expected: &PlumType, actual: &PlumType) -> bool {
68
- match (expected, actual) {
69
- (PlumType::TNamed(e), PlumType::TNamed(a)) => e != a && isSubtype(a, e),
70
- _ => false,
71
- }
72
- }
73
-
74
- /// The nearest struct both types are subtypes of (or equal to), as a type.
75
- pub fn commonAncestor(a: &PlumType, b: &PlumType) -> Option<PlumType> {
76
- let (PlumType::TNamed(x), PlumType::TNamed(y)) = (a, b) else { return None };
77
- if isSubtype(x, y) {
78
- return Some(b.clone());
79
- }
80
- if isSubtype(y, x) {
81
- return Some(a.clone());
82
- }
83
- let anc = withCheck(|s| s.ancestors.borrow().get(x).cloned().unwrap_or_default());
84
- anc.into_iter().find(|n| isSubtype(y, n)).map(PlumType::TNamed)
85
- }
86
-
87
- /// Error for a pattern `pat` on a subject of struct type `subject` when `pat`
88
- /// is a struct that is not a subtype of it.
89
- pub fn patternError(pat: &str, subject: &str, ctx: &CheckCtx) -> Option<String> {
90
- let isStructPat = ctx.enum_variants.get(pat).is_some_and(|i| i.enum_name == pat);
91
- if isStructPat && pat != subject && hasSubtypes(subject) && !isSubtype(pat, subject) {
92
- return Some(format!("pattern {pat} is not a subtype of {subject}"));
93
- }
94
- None
95
- }
96
-
97
- /// Runs `f` (a unify of a nested type position), where no subtype conversion applies.
98
- pub fn nested<R>(f: impl FnOnce() -> R) -> R {
99
- withCheck(|s| s.depth.set(s.depth.get() + 1));
100
- let r = f();
101
- withCheck(|s| s.depth.set(s.depth.get() - 1));
102
- r
103
- }
compiler/checker/src/tables.rs CHANGED
@@ -110,7 +110,6 @@ pub struct CheckCtx<'a> {
110
110
  /// `plum-wasm-codegen`, which needs the same tables to resolve `self`, field access,
111
111
  /// and method dispatch during code generation.
112
112
  pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, MethodEnv, EnumVariants) {
113
- subtyping::register(source);
114
113
  let conv = plumTypeFromAstG;
115
114
  let mut global_env: TypeEnv = TypeEnv::new();
116
115
  let mut methods: MethodEnv = BTreeMap::new();
compiler/checker/src/unification.rs CHANGED
@@ -147,7 +147,7 @@ pub fn unify(t1: &PlumType, t2: &PlumType, ctx: &CheckCtx) -> Result<(), String>
147
147
  // Structural generics: same base, arguments unify pairwise.
148
148
  (PlumType::TApp(a, xs), PlumType::TApp(b, ys)) if a == b && xs.len() == ys.len() => {
149
149
  for (x, y) in xs.iter().zip(ys.iter()) {
150
- subtyping::nested(|| unify(x, y, ctx))
150
+ unify(x, y, ctx)
151
151
  .map_err(|e| format!("type mismatch: expected {}, found {} ({})", t1, t2, e))?;
152
152
  }
153
153
  Ok(())
@@ -192,12 +192,10 @@ pub fn unify(t1: &PlumType, t2: &PlumType, ctx: &CheckCtx) -> Result<(), String>
192
192
  }
193
193
  (PlumType::TFun(ps1, r1), PlumType::TFun(ps2, r2)) if ps1.len() == ps2.len() => {
194
194
  for (p1, p2) in ps1.iter().zip(ps2.iter()) {
195
- subtyping::nested(|| unify(p1, p2, ctx))?;
195
+ unify(p1, p2, ctx)?;
196
196
  }
197
- subtyping::nested(|| unify(r1, r2, ctx))
197
+ unify(r1, r2, ctx)
198
198
  }
199
- // A subtype where its base struct is expected (top level only).
200
- _ if withCheck(|s| s.depth.get()) == 0 && subtyping::accepts(t1, t2) => Ok(()),
201
199
  _ => Err(format!("type mismatch: expected {}, found {}", t1, t2)),
202
200
  }
203
201
  }
@@ -238,7 +236,6 @@ pub(crate) fn unifyArg(
238
236
  unify(expected, actual, ctx).map_err(|e| format!("{}{}", e, wrapHint(expected, actual, ctx)))?;
239
237
  if let (PlumType::TNamed(variant_name), PlumType::TNamed(actual_name)) = (expected, actual) {
240
238
  if variant_name != actual_name
241
- && !subtyping::accepts(expected, actual)
242
239
  && ctx.enum_variants.contains_key(variant_name)
243
240
  && !exprIsVariant(actual_expr, variant_name)
244
241
  {
compiler/checker/tests/checker/features/mutability.rs CHANGED
@@ -23,14 +23,6 @@ fn mut_field_assign_ok() {
23
23
  run("struct Node(mut x: Int)\nfun f(n: Node) -> Int =\n n.x = 3\n n.x\n").unwrap();
24
24
  }
25
25
 
26
- #[test]
27
- fn embedded_mut_field_ok() {
28
- let defs = "struct Pos(mut x: Int, y: Int)\nstruct Hut(Pos(x, y), k: Int)\n";
29
- run(&format!("{defs}fun f(h: Hut) -> Int =\n h.x = 3\n h.x\n")).unwrap();
30
- let e = run(&format!("{defs}fun f(h: Hut) -> Int =\n h.y = 3\n h.y\n")).unwrap_err();
31
- assert!(e.contains("Hut.y is immutable"), "{e}");
32
- }
33
-
34
26
  #[test]
35
27
  fn struct_local_assign_ok() {
36
28
  run("struct Vec2(x: Int)\nfun f() -> Int =\n v := Vec2(x: 1)\n v.x = 3\n v.x\n").unwrap();
compiler/checker/tests/checker/main.rs CHANGED
@@ -10,4 +10,3 @@ mod fip_test;
10
10
  mod fusion_pipeline_test;
11
11
  mod inline_variants_tests;
12
12
  mod question_tests;
13
- mod subtyping_tests;
compiler/checker/tests/checker/question_tests.rs CHANGED
@@ -12,7 +12,6 @@ enum Result[T, E] =
12
12
  | Err(error: E)
13
13
 
14
14
  struct Actor(x: Int)
15
- struct Ship(Actor(x), s: Int)
16
15
  enum ErrA =
17
16
  | NoSuchPath
18
17
  | Other
@@ -72,11 +71,6 @@ fn question_in_closure_errors() {
72
71
  );
73
72
  }
74
73
 
75
- #[test]
76
- fn subtype_into_option() {
77
- ok("fun f(s: Ship) -> Option[Actor] = Some(s)");
78
- }
79
-
80
74
  #[test]
81
75
  fn unit_value() {
82
76
  ok("fun f() -> Result[Unit, ErrA] = Ok(Unit)");
compiler/checker/tests/checker/subtyping_tests.rs DELETED
@@ -1,124 +0,0 @@
1
- #![allow(non_snake_case)]
2
- //! Struct subtyping in the checker: a subtype is accepted where its base is expected
3
- //! (top level only), `if` branches join to the nearest common ancestor, and a
4
- //! subtype pattern on a base subject needs a `_`.
5
- use plum_checker::checkSource;
6
-
7
- const DEFS: &str = "\
8
- struct Actor(x: Int) =
9
- fun draw(self) -> Int = 1
10
-
11
- struct Ship(Actor(x), s: Int) =
12
- override fun draw(self) -> Int = 2
13
-
14
- struct Boat(Actor(x), w: Int) =
15
- override fun draw(self) -> Int = 3
16
-
17
- struct Plain(q: Int)
18
- ";
19
-
20
- fn run(body: &str) -> Result<(), String> {
21
- let src = format!("{DEFS}\n{body}\n");
22
- let mut source = plum_test_support::parseRawSource(&src);
23
- plum_core::lower::frontEndDiag(&mut source).map_err(|e| format!("lower: {e:?}"))?;
24
- checkSource(&source).map_err(|e| e.into_iter().map(|x| x.message).collect::<Vec<_>>().join(" | "))
25
- }
26
-
27
- #[test]
28
- fn subtypeArgumentAccepted() {
29
- run("fun f(a: Actor) -> Int = a.draw()\nfun main() -> Int = f(Ship(x: 1, s: 2))").unwrap();
30
- }
31
-
32
- #[test]
33
- fn baseArgumentToSubtypeParamRejected() {
34
- assert!(run("fun f(a: Ship) -> Int = a.s\nfun main() -> Int = f(Actor(x: 1))").is_err());
35
- }
36
-
37
- #[test]
38
- fn unrelatedStructRejected() {
39
- assert!(run("fun f(a: Actor) -> Int = a.x\nfun main() -> Int = f(Plain(q: 1))").is_err());
40
- }
41
-
42
- #[test]
43
- fn sliceElementAccepted() {
44
- run("fun main() -> Int =\n xs := Slice[Actor]()\n xs.push(Ship(x: 1, s: 2))\n 0").unwrap();
45
- }
46
-
47
- #[test]
48
- fn nestedPositionRejected() {
49
- let r = run("fun f(a: Slice[Actor]) -> Int = 0\nfun g(xs: Slice[Ship]) -> Int = f(xs)");
50
- assert!(r.is_err(), "Slice[Ship] must not be a Slice[Actor]");
51
- }
52
-
53
- #[test]
54
- fn ifBranchesJoinToAncestor() {
55
- run("fun f(c: Bool) -> Actor = if c then Ship(x: 1, s: 2) else Boat(x: 1, w: 3)").unwrap();
56
- run("fun main(c: Bool) -> Int =\n a := if c then Ship(x: 1, s: 2) else Boat(x: 1, w: 3)\n a.draw()").unwrap();
57
- }
58
-
59
- #[test]
60
- fn subtypePatternWithWildcard() {
61
- run("fun f(a: Actor) -> Int = match a\n Ship(x, s) => s\n _ => 0").unwrap();
62
- }
63
-
64
- #[test]
65
- fn subtypePatternWithoutWildcardErrors() {
66
- assert!(run("fun f(a: Actor) -> Int = match a\n Ship(x, s) => s").is_err());
67
- }
68
-
69
- #[test]
70
- fn unrelatedPatternErrors() {
71
- assert!(run("fun f(a: Actor) -> Int = match a\n Plain(q) => q\n _ => 0").is_err());
72
- }
73
-
74
- #[test]
75
- fn matchArmsAgainstBaseReturn() {
76
- run("fun f(c: Bool) -> Actor = match c\n true => Ship(x: 1, s: 2)\n false => Boat(x: 1, w: 3)").unwrap();
77
- }
78
-
79
- #[test]
80
- fn noWildcardMessageIsClear() {
81
- let e = run("fun f(a: Actor) -> Int = match a\n Ship(x, s) => s").unwrap_err();
82
- assert!(e.contains("match on Actor needs a `_` arm"), "{e}");
83
- }
84
-
85
- #[test]
86
- fn abstractStructMethodAccepted() {
87
- let src = "struct Actor(x: Int) =\n abstract fun draw(self) -> Int\n\nstruct Ship(Actor(x)) =\n override fun draw(self) -> Int = 2\n\nfun f(a: Actor) -> Int = a.draw()\n";
88
- let mut source = plum_test_support::parseRawSource(src);
89
- plum_core::lower::frontEndDiag(&mut source).unwrap();
90
- checkSource(&source).unwrap();
91
- }
92
-
93
- #[test]
94
- fn bodilessTopLevelFnStillErrors() {
95
- let src = "fun g() -> Int\n";
96
- let mut source = plum_test_support::parseRawSource(src);
97
- plum_core::lower::frontEndDiag(&mut source).unwrap();
98
- let e = checkSource(&source).unwrap_err();
99
- assert!(e.iter().any(|x| x.message.contains("missing a body")));
100
- }
101
-
102
- fn checkErrs(src: &str) -> Vec<String> {
103
- let mut source = plum_test_support::parseRawSource(src);
104
- plum_core::lower::frontEndDiag(&mut source).unwrap();
105
- checkSource(&source).unwrap_err().into_iter().map(|x| x.message).collect()
106
- }
107
-
108
- #[test]
109
- fn bodilessMethodWithoutAbstractErrors() {
110
- let e = checkErrs("struct Actor(x: Int) =\n fun draw(self) -> Int\n");
111
- assert!(e.iter().any(|m| m.contains("fn 'draw': missing a body (mark it abstract or extern)")), "{e:?}");
112
- }
113
-
114
- #[test]
115
- fn abstractWithBodyErrors() {
116
- let e = checkErrs("struct Actor(x: Int) =\n abstract fun draw(self) -> Int = 1\n");
117
- assert!(e.iter().any(|m| m.contains("abstract fn must not have a body")), "{e:?}");
118
- }
119
-
120
- #[test]
121
- fn abstractTopLevelErrors() {
122
- let e = checkErrs("abstract fun g() -> Int\n");
123
- assert!(e.iter().any(|m| m.contains("abstract is only allowed on a struct instance method")), "{e:?}");
124
- }
compiler/checker/tests/fixtures/custom_actor_probe2.plum DELETED
@@ -1,58 +0,0 @@
1
- import std/Int
2
- import std/Float
3
- import std/Str
4
- import std/Slice
5
-
6
- struct Node(id: Int, mut x: Float, y: Float, kind: Kind)
7
-
8
- enum Kind =
9
- | Group(children: Slice[Node])
10
- | Label(text: Str, size: Float)
11
- | Custom(actor: Actor)
12
-
13
- # Behaviour only: the game's structs embed it as their base.
14
- struct Actor() =
15
- abstract fun act(self, n: Node, dt: Float) -> Node
16
- abstract fun score(self, n: Node) -> Float
17
-
18
- struct Ship(Actor, mut clock: Float) =
19
- override fun act(self, n: Node, dt: Float) -> Node =
20
- self.clock = self.clock + dt
21
- n
22
- override fun score(self, n: Node) -> Float = self.clock
23
-
24
- struct Orb(Actor, vx: Float) =
25
- override fun act(self, n: Node, dt: Float) -> Node =
26
- n.x = n.x + self.vx * dt
27
- n
28
- override fun score(self, n: Node) -> Float = n.x
29
-
30
- fun actorOf(n: Node) -> Actor? = match n.kind
31
- Custom(a) => a
32
- _ => null
33
-
34
- fun update(n: Node, dt: Float) -> Node =
35
- if a := actorOf(n)
36
- return a.act(n, dt)
37
- match n.kind
38
- Group(ks) =>
39
- _ := ks.mapInPlace(|k| update(k, dt))
40
- _ => 0
41
- n
42
-
43
- fun total(n: Node) -> Float = match n.kind
44
- Group(ks) =>
45
- s := 0.0
46
- for i := range ks.length()
47
- s = s + total(ks.at(i))
48
- s
49
- Custom(a) => a.score(n)
50
- Label(_, _) => 0.0
51
-
52
- test "custom_actor"
53
- ks := Slice[Node]()
54
- ks.push(Node(id: 1, x: 1.0, y: 0.0, kind: Custom(actor: Orb(vx: 2.0))))
55
- ks.push(Node(id: 2, x: 0.0, y: 0.0, kind: Custom(actor: Ship(clock: 0.0))))
56
- root := Node(id: 0, x: 0.0, y: 0.0, kind: Group(children: ks))
57
- r := update(root, 0.5)
58
- assert total(r) == 2.0 + 0.5
compiler/core/src/ast.rs CHANGED
@@ -443,20 +443,9 @@ pub enum TypeKind {
443
443
 
444
444
  #[derive(Debug, Clone, PartialEq, Default)]
445
445
  pub struct StructInfo {
446
- /// Always `Boxed` — generated from an inline variant, embedding (lowering
446
+ /// Always `Boxed` — generated from an inline variant or a `fun init(self)`
447
- /// later clears `embeds`, so the parser records it here) or a
448
- /// `fun init(self)` method. Otherwise the layout is decided only by
447
+ /// method. Otherwise the layout is decided only by `layout::structLayout`.
449
- /// `layout::structLayout`.
450
448
  pub forceBoxed: bool,
451
- /// Structs listed first in a `struct` field list (`struct H(E, k: Int)`).
452
- pub embeds: Vec<String>,
453
- /// The same structs with the fields each exposes in the header
454
- /// (`Actor(x, y)` -> ("Actor", ["x","y"]); bare `Actor` -> empty list).
455
- pub embedFields: Vec<(String, Vec<String>)>,
456
- /// The FIRST embedded name of a struct: the struct it subtypes when that
457
- /// is a (non-generic) struct item. The parser records it because lowering
458
- /// clears `embeds`.
459
- pub base: Option<String>,
460
449
  }
461
450
 
462
451
  impl Enum {
@@ -475,14 +464,6 @@ impl Enum {
475
464
  TypeKind::Enum => None,
476
465
  }
477
466
  }
478
- /// The embedded structs with their exposed fields; empty for an enum.
479
- pub fn embedFields(&self) -> &[(String, Vec<String>)] {
480
- self.structInfo().map_or(&[], |i| i.embedFields.as_slice())
481
- }
482
- /// The struct this one subtypes, if it is a struct that embeds one.
483
- pub fn base(&self) -> Option<&str> {
484
- self.structInfo()?.base.as_deref()
485
- }
486
467
  }
487
468
 
488
469
  #[derive(Debug, Clone, PartialEq)]
@@ -538,11 +519,6 @@ pub struct Fn {
538
519
  pub is_export: bool,
539
520
  /// A `test` block lowered to a fn (`tests_lower`); its name is generated.
540
521
  pub is_test: bool,
541
- /// `override fun`: replaces a trait or embedded-record method (checked by the embed lowering).
542
- pub is_override: bool,
543
- /// `abstract fun m(self) -> T` (no body): an abstract struct method that every
544
- /// subtype must override.
545
- pub is_abstract: bool,
546
522
  /// `fip fun`: every spread and `Array.with` in the body must be proved in place (driver check).
547
523
  pub is_fip: bool,
548
524
  /// The fn declared `self` as its first parameter (an instance method). A
compiler/core/src/closure_spec.rs CHANGED
@@ -60,7 +60,6 @@ pub fn specialiseWith(src: &mut Source, hints: &Hints) -> Vec<String> {
60
60
  ne,
61
61
  fns: HashMap::new(),
62
62
  methodsByName: HashMap::new(),
63
- virt: crate::subtyping::virtualMethods(src),
64
63
  specs: Vec::new(),
65
64
  copies: HashMap::new(),
66
65
  perCallee: HashMap::new(),
@@ -158,8 +157,6 @@ struct Ctx<'h> {
158
157
  ne: HashMap<String, Vec<usize>>,
159
158
  fns: HashMap<String, Fn>,
160
159
  methodsByName: HashMap<String, Vec<String>>,
161
- /// `(type, method)` pairs some subtype overrides: calls on them dispatch dynamically.
162
- virt: HashSet<(String, String)>,
163
160
  specs: Vec<Spec>,
164
161
  /// (callee key, param index, spec) -> copy fn name; `None` when it cannot be made.
165
162
  copies: HashMap<(String, usize, usize), Option<String>>,
@@ -245,16 +242,10 @@ impl<'h> Ctx<'h> {
245
242
  };
246
243
  match ty {
247
244
  Some(t) => {
248
- if self.virt.contains(&(t.clone(), c.name.clone())) {
249
- return None;
250
- }
251
245
  let key = format!("{}.{}", t, c.name);
252
246
  self.fns.contains_key(&key).then_some(key)
253
247
  }
254
248
  None => {
255
- if self.virt.iter().any(|(_, m)| *m == c.name) {
256
- return None;
257
- }
258
249
  let v = self.methodsByName.get(&c.name)?;
259
250
  if v.len() == 1 {
260
251
  Some(v[0].clone())
compiler/core/src/ctors.rs CHANGED
@@ -1,12 +1,12 @@
1
- //! Constructor lowering for structs that embed a struct or have an `init`.
1
+ //! Constructor lowering for structs that have an `init`.
2
2
  //!
3
- //! A struct `Ship(Actor(x, y), tex: Int)` is constructed through a generated fn
3
+ //! A struct with `fun init(self)` is constructed through a generated fn
4
- //! `Ship__new(x, y, tex) -> Ship`: the top-level `self.f = expr` statements of the
4
+ //! `Ship__new(x, tex) -> Ship`: the top-level `self.f = expr` statements of the
5
- //! embedded struct's `init` and then the struct's `init` become locals, the struct is
5
+ //! struct's `init` become locals, the struct is built by ONE constructor call once
6
- //! built by ONE constructor call once every field has a value, and the remaining init
6
+ //! every field has a value, and the remaining init statements run afterwards as a
7
- //! statements run afterwards as generated methods (`__init_<Owner>`) with `self` bound.
7
+ //! generated method (`__init_<Owner>`) with `self` bound.
8
8
  //! Every user `Ship(...)` call becomes `Ship__new(...)`. Idempotent: lowering
9
- //! clears `embedFields` and removes the `init`s, so a second run finds nothing.
9
+ //! removes the `init`s, so a second run finds nothing.
10
10
  use std::collections::{HashMap, HashSet};
11
11
 
12
12
  use crate::ast::*;
@@ -30,88 +30,6 @@ struct Plan {
30
30
  params: Vec<(String, Type)>,
31
31
  }
32
32
 
33
- /// What an embedded struct contributes to its embedder's constructor: the fields it
34
- /// declares itself (header plus `self.f: T = ..` in its `init`; never the ones it
35
- /// embedded) and its `init`. Private to this pass.
36
- struct EmbedInit {
37
- #[allow(dead_code)]
38
- name: String,
39
- fields: Vec<EnumParam>,
40
- init: Option<Fn>,
41
- }
42
-
43
- /// An `EmbedInit` for every struct that some `embedFields` entry names, computed from
44
- /// the post-`lowerEmbeds` items (a struct's variant holds spliced fields, then its own).
45
- fn embedInits(source: &Source) -> HashMap<String, EmbedInit> {
46
- let structs: HashMap<&str, &Enum> = source
47
- .items
48
- .iter()
49
- .filter_map(|i| match i {
50
- Item::Enum(e) if e.isStruct() => Some((e.name.as_str(), e)),
51
- _ => None,
52
- })
53
- .collect();
54
- let mut out: HashMap<String, EmbedInit> = HashMap::new();
55
- let wanted: Vec<String> = structs
56
- .values()
57
- .flat_map(|e| e.embedFields().iter().map(|(n, _)| n.clone()))
58
- .filter(|n| structs.contains_key(n.as_str()))
59
- .collect();
60
- for n in wanted {
61
- embedInitOf(source, &structs, &n, &mut out, &mut Vec::new());
62
- }
63
- out
64
- }
65
-
66
- fn embedInitOf(
67
- source: &Source,
68
- structs: &HashMap<&str, &Enum>,
69
- name: &str,
70
- out: &mut HashMap<String, EmbedInit>,
71
- stack: &mut Vec<String>,
72
- ) {
73
- if out.contains_key(name) || stack.iter().any(|s| s == name) {
74
- return;
75
- }
76
- let Some(e) = structs.get(name) else { return };
77
- stack.push(name.to_string());
78
- let mut taken: HashSet<String> = HashSet::new();
79
- for (n, _) in e.embedFields() {
80
- if structs.contains_key(n.as_str()) {
81
- embedInitOf(source, structs, n, out, stack);
82
- if let Some(c) = out.get(n) {
83
- taken.extend(c.fields.iter().map(|p| p.name.clone()));
84
- }
85
- }
86
- }
87
- let mut fields: Vec<EnumParam> = Vec::new();
88
- if let Some(v) = e.variants.first() {
89
- for (i, (n, t)) in v.field_names.iter().zip(v.fields.iter()).enumerate() {
90
- if !taken.contains(n) {
91
- fields.push(EnumParam {
92
- name: n.clone(),
93
- ty: t.clone(),
94
- isMut: v.mut_fields.get(i).copied().unwrap_or(false),
95
- });
96
- }
97
- }
98
- }
99
- let init = initOf(&source.items, name).cloned();
100
- if let Some(init) = &init {
101
- for st in initStmts(init) {
102
- let Some((fname, val, ann)) = fieldAssign(&st) else { continue };
103
- if taken.contains(fname) || fields.iter().any(|p| p.name == fname) {
104
- continue;
105
- }
106
- if let Some(t) = ann.cloned().or_else(|| inferType(val)) {
107
- fields.push(EnumParam { name: fname.to_string(), isMut: false, ty: t });
108
- }
109
- }
110
- }
111
- stack.pop();
112
- out.insert(name.to_string(), EmbedInit { name: name.to_string(), fields, init });
113
- }
114
-
115
33
  fn initOf<'a>(items: &'a [Item], rec: &str) -> Option<&'a Fn> {
116
34
  items.iter().find_map(|i| match i {
117
35
  Item::Fn(f) if f.type_param.as_deref() == Some(rec) && f.name == "init" && f.has_self => {
@@ -197,19 +115,6 @@ fn mentionsSelf(s: &Stmt) -> bool {
197
115
 
198
116
  pub fn lowerCtors(source: &mut Source) -> Vec<String> {
199
117
  let mut errors: Vec<String> = Vec::new();
200
- let embeded = embedInits(source);
201
-
202
- // Embedded-struct inits (cloned).
203
- let mut traitInits: HashMap<String, Fn> = HashMap::new();
204
-
205
- for (n, ei) in &embeded {
206
- if let Some(f) = &ei.init {
207
- if !f.params.is_empty() || f.returns.is_some() {
208
- errors.push("init takes only self".to_string());
209
- }
210
- traitInits.insert(n.clone(), f.clone());
211
- }
212
- }
213
118
 
214
119
  // Step A: constructor records and their parameters.
215
120
  let mut plans: HashMap<String, Plan> = HashMap::new();
@@ -220,47 +125,20 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
220
125
  continue;
221
126
  }
222
127
  let init = initOf(&source.items, &e.name);
223
- if e.embedFields().is_empty() && init.is_none() {
224
- continue;
225
- }
226
- if let Some(f) = init {
128
+ let Some(f) = init else { continue };
227
- if !f.params.is_empty() || f.returns.is_some() {
129
+ if !f.params.is_empty() || f.returns.is_some() {
228
- errors.push("init takes only self".to_string());
130
+ errors.push("init takes only self".to_string());
229
- }
230
131
  }
231
132
  if !e.generics.is_empty() {
232
133
  errors.push(format!(
233
- "record {} is generic: init and embedding are not supported on generic records",
134
+ "record {} is generic: init is not supported on generic records",
234
135
  e.name
235
136
  ));
236
137
  continue;
237
138
  }
238
139
  let v = &e.variants[0];
239
- let mut traitFieldCount = 0;
240
- let mut params: Vec<(String, Type)> = Vec::new();
140
+ let params: Vec<(String, Type)> =
241
- let typeOf = |n: &str| -> Option<Type> {
242
- v.field_names
243
- .iter()
244
- .position(|x| x == n)
245
- .map(|i| v.fields[i].clone())
246
- };
247
- for (tname, exposed) in e.embedFields() {
248
- let (kind, tfields) = match embeded.get(tname) {
249
- Some(ei) => ("struct", &ei.fields),
250
- None => continue,
251
- };
252
- traitFieldCount += tfields.len();
253
- for f in exposed {
254
- if !tfields.iter().any(|tf| tf.name == *f) {
255
- errors.push(format!("{} {} has no field {}", kind, tname, f));
256
- } else if let Some(ft) = typeOf(f) {
257
- params.push((f.clone(), ft));
258
- }
259
- }
260
- }
261
- for i in traitFieldCount.min(v.field_names.len())..v.field_names.len() {
262
- params.push((v.field_names[i].clone(), v.fields[i].clone()));
141
+ v.field_names.iter().cloned().zip(v.fields.iter().cloned()).collect();
263
- }
264
142
  plans.insert(e.name.clone(), Plan { params });
265
143
  order.push(ii);
266
144
  }
@@ -281,22 +159,18 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
281
159
  _ => {}
282
160
  }
283
161
  }
284
- for m in traitInits.values_mut() {
285
- rewriteFn(m, &plans, &mut errors);
286
- }
287
162
  }
288
163
 
289
164
  // Step C: build each constructor.
290
165
  let mut newItems: Vec<Item> = Vec::new();
291
166
  let mut dropInits: HashSet<String> = HashSet::new();
292
167
  for ii in order {
293
- let (rname, line, column, embeds, names0, tys0, nparams, egens) = {
168
+ let (rname, line, column, names0, tys0, nparams, egens) = {
294
169
  let Item::Enum(e) = &source.items[ii] else { continue };
295
170
  (
296
171
  e.name.clone(),
297
172
  e.line,
298
173
  e.column,
299
- e.embedFields().to_vec(),
300
174
  e.variants[0].field_names.clone(),
301
175
  e.variants[0].fields.clone(),
302
176
  plans[&e.name].params.clone(),
@@ -306,11 +180,6 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
306
180
  let recInit = initOf(&source.items, &rname).cloned();
307
181
  let file = recInit.as_ref().map(|f| f.file.clone()).unwrap_or_else(|| source.file.clone());
308
182
  let mut inits: Vec<(String, Fn)> = Vec::new();
309
- for (tname, _) in &embeds {
310
- if let Some(f) = traitInits.get(tname) {
311
- inits.push((tname.clone(), f.clone()));
312
- }
313
- }
314
183
  if let Some(f) = recInit {
315
184
  inits.push((rname.clone(), f));
316
185
  }
@@ -367,10 +236,7 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
367
236
  let firstUnset = names.iter().find(|n| !set.contains(*n)).cloned().unwrap_or_default();
368
237
  if let Some((fname, val, _)) = fieldAssign(&st) {
369
238
  if nparams.iter().any(|(n, _)| n == fname) {
370
- if *owner == rname {
371
- errors.push(format!("{} init sets constructor parameter {}", rname, fname));
239
+ errors.push(format!("{} init sets constructor parameter {}", rname, fname));
372
- }
373
- // An embedded init never overrides what the caller passed.
374
240
  continue;
375
241
  }
376
242
  if !names.iter().any(|n| n == fname) {
@@ -482,8 +348,6 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
482
348
  is_extern: false,
483
349
  is_export: false,
484
350
  is_test: false,
485
- is_override: false,
486
- is_abstract: false,
487
351
  is_fip: false,
488
352
  has_self: true,
489
353
  params: vec![],
@@ -502,8 +366,6 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
502
366
  is_extern: false,
503
367
  is_export: false,
504
368
  is_test: false,
505
- is_override: false,
506
- is_abstract: false,
507
369
  is_fip: false,
508
370
  has_self: false,
509
371
  params: nparams
@@ -519,9 +381,6 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
519
381
  }));
520
382
 
521
383
  if let Item::Enum(e) = &mut source.items[ii] {
522
- if let Some(info) = e.structInfoMut() {
523
- info.embedFields.clear();
524
- }
525
384
  e.variants[0].field_names = names;
526
385
  e.variants[0].fields = tys;
527
386
  }
compiler/core/src/drive.rs CHANGED
@@ -99,14 +99,12 @@ struct Driver {
99
99
  scope: Scope,
100
100
  /// record -> (its generics, (field name, field type)), for typing `x.field`
101
101
  recFields: HashMap<String, (Vec<String>, Vec<(String, Type)>)>,
102
- /// `Type.method` of every method with a receiver, externs and overridden ones included ->
102
+ /// `Type.method` of every method with a receiver, externs included ->
103
103
  /// the type name of each parameter (`None`: a generic, so any type)
104
104
  selfMethods: HashMap<String, Vec<Option<String>>>,
105
105
  /// name -> every method with a receiver of that name: what a call on an unknown
106
106
  /// receiver may run. `methods` is the part of it that may be inlined or fused.
107
107
  allMethods: HashMap<String, Vec<String>>,
108
- /// `(Type, method)` -> the overrides a call on that type may run (`subtyping::overrideTargets`)
109
- overrides: HashMap<(String, String), Vec<String>>,
110
108
  }
111
109
 
112
110
  fn keyOf(f: &Fn) -> String {
@@ -491,7 +489,6 @@ impl Driver {
491
489
  recFields: HashMap::new(),
492
490
  selfMethods: HashMap::new(),
493
491
  allMethods: HashMap::new(),
494
- overrides: crate::subtyping::overrideTargets(src),
495
492
  };
496
493
  let flat = crate::layout::flatStructs(&src.items);
497
494
  for item in &src.items {
@@ -507,12 +504,6 @@ impl Driver {
507
504
  d.selfMethods.insert(keyOf(f), params.collect());
508
505
  d.allMethods.entry(f.name.clone()).or_default().push(keyOf(f));
509
506
  }
510
- // A method that may dispatch to an override is opaque: never inlined or fused.
511
- if let Some(t) = &f.type_param {
512
- if d.overrides.contains_key(&(t.clone(), f.name.clone())) {
513
- continue;
514
- }
515
- }
516
507
  // A `fip fun` stays a call, so the uniqueness proof sees every caller of it.
517
508
  if f.is_extern || f.is_export || f.is_fip {
518
509
  continue;
@@ -1602,16 +1593,13 @@ impl Driver {
1602
1593
  out
1603
1594
  }
1604
1595
 
1605
- /// The fns a method call `object.name(..)` may run: the receiver type's own method and
1596
+ /// The fns a method call `object.name(..)` may run: the receiver type's own method when
1606
- /// every override below it when the receiver's type is known (`io.clear` is `Io.clear`),
1607
- /// else every method of that name. Empty (an unknown callee) when there is none.
1597
+ /// the receiver's type is known (`io.clear` is `Io.clear`), else every method of that name. Empty (an unknown callee) when there is none.
1608
1598
  fn methodKeys(&self, object: &Expr, name: &str) -> Vec<String> {
1609
1599
  if let Some(t) = self.varType(object) {
1610
1600
  let key = format!("{}.{}", t.name, name);
1611
1601
  if self.selfMethods.contains_key(&key) {
1612
- let mut keys = vec![key];
1613
- keys.extend(self.overrides.get(&(t.name.clone(), name.to_string())).into_iter().flatten().cloned());
1614
- return keys;
1602
+ return vec![key];
1615
1603
  }
1616
1604
  }
1617
1605
  self.allMethods.get(name).cloned().unwrap_or_default()
@@ -1628,7 +1616,7 @@ impl Driver {
1628
1616
 
1629
1617
  /// Is `p`, the one candidate of the producer call `e` with the right result type, what
1630
1618
  /// `e` runs? A method found by name alone is only if fusion sees every method of the name:
1631
- /// one it cannot (overridden, extern) may have that result type too.
1619
+ /// one it cannot (extern) may have that result type too.
1632
1620
  fn resolvedProducer(&self, e: &Expr, p: &Call) -> bool {
1633
1621
  let ExprKind::Attribute(a) = &e.kind else { return true };
1634
1622
  let AttrKind::Method(m) = &a.attr else { return true };
@@ -1647,7 +1635,7 @@ impl Driver {
1647
1635
  fn onlyTaker(&self, c: &Call, k: usize, hint: &str) -> bool {
1648
1636
  let takes = |params: &Vec<Option<String>>| {
1649
1637
  params.len() == c.f.params.len()
1650
- && params[k - 1].as_ref().is_none_or(|t| crate::subtyping::isSubtype(&self.snap, hint, t))
1638
+ && params[k - 1].as_ref().is_none_or(|t| hint == t)
1651
1639
  };
1652
1640
  let others = self.allMethods.get(&c.f.name).into_iter().flatten().filter(|m| **m != c.key);
1653
1641
  others.into_iter().all(|m| !self.selfMethods.get(m).is_some_and(takes))
@@ -1672,7 +1660,7 @@ impl Driver {
1672
1660
  }
1673
1661
  let recv = self.recvType(&a.object).or_else(|| self.resultName(&a.object));
1674
1662
  let typed = recv.zip(c.f.type_param.as_ref());
1675
- typed.is_some_and(|(t, owner)| crate::subtyping::isSubtype(&self.snap, &t, owner))
1663
+ typed.is_some_and(|(t, owner)| t == *owner)
1676
1664
  || self.allMethods.get(&m.name).is_some_and(|v| v.len() == 1)
1677
1665
  }
1678
1666
 
@@ -1848,8 +1836,6 @@ impl Driver {
1848
1836
  is_extern: false,
1849
1837
  is_export: false,
1850
1838
  is_test: false,
1851
- is_override: false,
1852
- is_abstract: false,
1853
1839
  is_fip: false,
1854
1840
  has_self: false,
1855
1841
  params,
compiler/core/src/embeds.rs DELETED
@@ -1,517 +0,0 @@
1
- //! Record embedding and `override` checking.
2
- //!
3
- //! `struct Ship(Actor(x), s: Int)` embeds ONE struct, its base: the embedded struct's
4
- //! fields are copied in (before the struct's own), its instance methods are copied, and
5
- //! its `init` runs (as an `EmbedInit`, see `ctors.rs`) before the struct's own `init`.
6
- //! A second embedded struct is an error.
7
- //!
8
- //! A method that comes from the embedded struct must be written `override fun`. A
9
- //! replaced original stays reachable under the hidden name `<Owner>__<method>`;
10
- //! `self.Owner.m(args)` is rewritten to call it.
11
- //!
12
- //! Runs from `lower::frontEnd` (public for direct use). Idempotent: a
13
- //! processed record carries a marker entry in `embedFields`.
14
- use std::collections::{HashMap, HashSet};
15
-
16
- use crate::ast::*;
17
- use crate::ctors::{fieldAssign, inferType, initStmts};
18
- use crate::forms::mapExprs;
19
-
20
- /// Marker in `embedFields` of a record this pass already handled (ignored by `lowerCtors`).
21
- const DONE: &str = "#embedded";
22
-
23
- struct Ctx {
24
- done: HashSet<String>,
25
- stack: Vec<String>,
26
- /// Fields a record itself contributes when embedded: its header fields plus the
27
- /// fields its `init` declares (`self.f: T = ..`); never the ones it embedded.
28
- own: HashMap<String, Vec<EnumParam>>,
29
- errors: Vec<String>,
30
- }
31
-
32
- pub fn lowerEmbeds(source: &mut Source) -> Vec<String> {
33
- let mut cx = Ctx {
34
- done: HashSet::new(),
35
- stack: Vec::new(),
36
- own: HashMap::new(),
37
- errors: Vec::new(),
38
- };
39
- let names: Vec<String> = source
40
- .items
41
- .iter()
42
- .filter_map(|i| match i {
43
- Item::Enum(e) => Some(e.name.clone()),
44
- _ => None,
45
- })
46
- .collect();
47
- for n in &names {
48
- processRecord(source, n, &mut cx);
49
- }
50
- reportAbstractConstruction(source, &mut cx.errors);
51
- // `override` on a free function.
52
- for i in &source.items {
53
- if let Item::Fn(f) = i {
54
- if f.is_override && f.type_param.is_none() {
55
- cx.errors.push(format!(
56
- "{}: override is only allowed on a record instance method",
57
- f.name
58
- ));
59
- }
60
- }
61
- }
62
- let mut seen = HashSet::new();
63
- cx.errors.retain(|e| seen.insert(e.clone()));
64
- cx.errors
65
- }
66
-
67
- fn enumIdx(source: &Source, name: &str) -> Option<usize> {
68
- source
69
- .items
70
- .iter()
71
- .position(|i| matches!(i, Item::Enum(e) if e.name == name))
72
- }
73
-
74
- fn defines(source: &Source, rec: &str, name: &str) -> bool {
75
- source.items.iter().any(
76
- |i| matches!(i, Item::Fn(f) if f.type_param.as_deref() == Some(rec) && f.name == name),
77
- )
78
- }
79
-
80
- fn instanceMethods(source: &Source, rec: &str) -> Vec<Fn> {
81
- source
82
- .items
83
- .iter()
84
- .filter_map(|i| match i {
85
- Item::Fn(f)
86
- if f.type_param.as_deref() == Some(rec) && f.has_self && f.name != "init" =>
87
- {
88
- Some(f.clone())
89
- }
90
- _ => None,
91
- })
92
- .collect()
93
- }
94
-
95
- /// An `abstract fun` instance method of a struct.
96
- fn isAbstract(f: &Fn) -> bool {
97
- f.has_self && f.is_abstract
98
- }
99
-
100
- fn processRecord(source: &mut Source, rname: &str, cx: &mut Ctx) {
101
- if cx.done.contains(rname) {
102
- return;
103
- }
104
- if cx.stack.iter().any(|s| s == rname) {
105
- cx.errors.push(format!("struct {} embeds itself", rname));
106
- return;
107
- }
108
- let Some(idx) = enumIdx(source, rname) else { return };
109
- cx.stack.push(rname.to_string());
110
- let (isStruct, embeds, embedFields, headerNames, headerTys, headerMuts) =
111
- match &source.items[idx] {
112
- Item::Enum(e) => (
113
- e.isStruct(),
114
- e.structInfo().map(|i| i.embeds.clone()).unwrap_or_default(),
115
- e.structInfo().map(|i| i.embedFields.clone()).unwrap_or_default(),
116
- e.variants.first().map(|v| v.field_names.clone()).unwrap_or_default(),
117
- e.variants.first().map(|v| v.fields.clone()).unwrap_or_default(),
118
- e.variants.first().map(|v| v.mut_fields.clone()).unwrap_or_default(),
119
- ),
120
- _ => unreachable!(),
121
- };
122
- let already = embedFields.iter().any(|(n, _)| n == DONE);
123
- let hasHeader = !embedFields.is_empty() && !already;
124
- let mut recEntries: Vec<(String, Vec<String>)> = embedFields
125
- .iter()
126
- .filter(|(n, _)| n != DONE && !embeds.contains(n))
127
- .cloned()
128
- .collect();
129
- let mut spliced: Vec<EnumParam> = Vec::new();
130
- // Record methods copied in, per source record: (owner, method).
131
- let mut recMethods: Vec<(String, Fn)> = Vec::new();
132
- let mut headerRecords: Vec<String> = Vec::new();
133
- let mut seenField: Vec<(String, String)> = Vec::new();
134
-
135
- if hasHeader {
136
- // 1. Header entries, in order.
137
- let entries: Vec<(String, Vec<String>)> = embedFields
138
- .iter()
139
- .filter(|(n, _)| embeds.contains(n))
140
- .cloned()
141
- .collect();
142
- if let [first, second, ..] = &entries[..] {
143
- cx.errors.push(format!(
144
- "struct {} embeds {} and {}; a struct can embed only one struct \u{2014} use a field ({}: {})",
145
- rname,
146
- first.0,
147
- second.0,
148
- second.0.to_lowercase(),
149
- second.0
150
- ));
151
- }
152
- for (ename, exposed) in entries.iter().take(1) {
153
- let Some(eidx) = enumIdx(source, ename) else {
154
- cx.errors.push(format!("type {} cannot embed {}: it is not a struct", rname, ename));
155
- continue;
156
- };
157
- // Embedding works on any struct, whatever its layout.
158
- let (eStruct, eGeneric) = match &source.items[eidx] {
159
- Item::Enum(e) => (e.isStruct(), !e.generics.is_empty()),
160
- _ => unreachable!(),
161
- };
162
- if !eStruct {
163
- cx.errors.push(format!("type {} cannot embed {}: it is not a struct", rname, ename));
164
- continue;
165
- }
166
- if !isStruct {
167
- cx.errors.push(format!(
168
- "type {} cannot embed struct {}; only structs embed structs",
169
- rname, ename
170
- ));
171
- continue;
172
- }
173
- if eGeneric {
174
- cx.errors
175
- .push(format!("cannot embed generic struct {}", ename));
176
- continue;
177
- }
178
- processRecord(source, ename, cx);
179
- if !cx.done.contains(ename) {
180
- continue;
181
- }
182
- // E's processed state: its chain of embedded structs.
183
- let eChain: Vec<String> = match &source.items[eidx] {
184
- Item::Enum(e) => e
185
- .embedFields()
186
- .iter()
187
- .filter(|(n, _)| n != DONE)
188
- .map(|(n, _)| n.clone())
189
- .collect(),
190
- _ => unreachable!(),
191
- };
192
- let mut chain = eChain;
193
- chain.push(ename.clone());
194
- // Route each selected name to the chain struct that owns it.
195
- let mut recExp: HashMap<String, Vec<String>> = HashMap::new();
196
- for n in exposed {
197
- if let Some(c) = chain
198
- .iter()
199
- .find(|c| cx.own.get(*c).is_some_and(|f| f.iter().any(|p| p.name == *n)))
200
- {
201
- recExp.entry(c.clone()).or_default().push(n.clone());
202
- } else {
203
- cx.errors
204
- .push(format!("struct {} has no field {}", ename, n));
205
- }
206
- }
207
- for c in &chain {
208
- if recEntries.iter().any(|(n, _)| n == c) {
209
- cx.errors
210
- .push(format!("struct {} embeds {} more than once", rname, c));
211
- continue;
212
- }
213
- recEntries.push((
214
- c.clone(),
215
- recExp.get(c).cloned().unwrap_or_default(),
216
- ));
217
- for p in cx.own.get(c).cloned().unwrap_or_default() {
218
- if let Some((_, other)) = seenField.iter().find(|(n, _)| *n == p.name) {
219
- cx.errors.push(format!(
220
- "{} gets field {} from both {} and {}",
221
- rname, p.name, other, c
222
- ));
223
- continue;
224
- }
225
- if headerNames.contains(&p.name) {
226
- cx.errors.push(format!(
227
- "struct {} field {} is already declared by struct {}",
228
- rname, p.name, c
229
- ));
230
- continue;
231
- }
232
- seenField.push((p.name.clone(), c.clone()));
233
- spliced.push(p);
234
- }
235
- }
236
- headerRecords.push(ename.clone());
237
- for m in instanceMethods(source, ename) {
238
- recMethods.push((ename.clone(), m));
239
- }
240
- }
241
- }
242
-
243
- // 3. The record's own methods, before anything is copied in.
244
- let ownMethods: Vec<Fn> = source
245
- .items
246
- .iter()
247
- .filter_map(|i| match i {
248
- Item::Fn(f) if f.type_param.as_deref() == Some(rname) => Some(f.clone()),
249
- _ => None,
250
- })
251
- .collect();
252
-
253
- // Providers: the embedded struct's instance methods, (method, owner).
254
- let mut providers: Vec<(String, String)> = Vec::new();
255
- for (owner, m) in &recMethods {
256
- if !providers.iter().any(|(n, _)| *n == m.name) {
257
- providers.push((m.name.clone(), owner.clone()));
258
- }
259
- }
260
- if !already {
261
- for f in &ownMethods {
262
- if f.name == "init" && f.has_self {
263
- if f.is_override {
264
- cx.errors.push(format!("{}.init overrides nothing", rname));
265
- }
266
- continue;
267
- }
268
- if f.is_override && !f.has_self {
269
- cx.errors.push(format!(
270
- "{}.{}: override is only allowed on an instance method",
271
- rname, f.name
272
- ));
273
- continue;
274
- }
275
- if !f.has_self {
276
- continue;
277
- }
278
- let found = providers.iter().find(|(n, _)| *n == f.name);
279
- match (found, f.is_override) {
280
- (Some((_, owner)), false) => cx.errors.push(format!(
281
- "{}.{} hides {}.{}; mark it override",
282
- rname, f.name, owner, f.name
283
- )),
284
- (None, true) => cx
285
- .errors
286
- .push(format!("{}.{} overrides nothing", rname, f.name)),
287
- _ => {}
288
- }
289
- }
290
- }
291
-
292
- // Every abstract method of an embedded struct needs an `override` with a body here.
293
- if !already {
294
- for (owner, m) in &recMethods {
295
- if !isAbstract(m) {
296
- continue;
297
- }
298
- let implemented = ownMethods
299
- .iter()
300
- .any(|f| f.name == m.name && f.has_self && !isAbstract(f));
301
- if !implemented {
302
- cx.errors.push(format!(
303
- "struct {} embeds {} but does not override abstract method {}",
304
- rname, owner, m.name
305
- ));
306
- }
307
- }
308
- }
309
-
310
- // 4. Copy embedded records' methods; replaced ones go under `<Owner>__<m>`.
311
- let mut copies: Vec<Fn> = Vec::new();
312
- let mut copiedFrom: Vec<(String, String)> = Vec::new(); // (method, owner)
313
- for (owner, m) in &recMethods {
314
- if isAbstract(m) {
315
- // Never copied: the embedder overrides it (or has been reported above).
316
- continue;
317
- }
318
- let ownDefines = ownMethods.iter().any(|f| f.name == m.name && f.has_self);
319
- let mut c = m.clone();
320
- refreshFnIds(&mut c);
321
- c.type_param = Some(rname.to_string());
322
- if ownDefines {
323
- c.name = format!("{}__{}", owner, m.name);
324
- c.is_override = false;
325
- if !defines(source, rname, &c.name) && !copies.iter().any(|x| x.name == c.name) {
326
- copies.push(c);
327
- }
328
- } else if let Some((_, other)) = copiedFrom.iter().find(|(n, _)| *n == m.name) {
329
- cx.errors.push(format!(
330
- "{} gets method {} from both {} and {}; define {}.{} with override",
331
- rname, m.name, other, owner, rname, m.name
332
- ));
333
- } else if !defines(source, rname, &m.name) {
334
- copiedFrom.push((m.name.clone(), owner.clone()));
335
- copies.push(c);
336
- }
337
- }
338
-
339
- // 6. `self.X.m(args)` in the record's own methods.
340
- if !already {
341
- let ownDefined: HashSet<String> = ownMethods.iter().map(|f| f.name.clone()).collect();
342
- let mut errs: Vec<String> = Vec::new();
343
- for it in source.items.iter_mut() {
344
- let Item::Fn(f) = it else { continue };
345
- if f.type_param.as_deref() != Some(rname) {
346
- continue;
347
- }
348
- let fname = f.name.clone();
349
- let mut calls: Vec<String> = Vec::new();
350
- mapExprs(&mut f.body, &mut |e| {
351
- let ExprKind::Attribute(a) = &mut e.kind else { return };
352
- let AttrKind::Method(call) = &a.attr else { return };
353
- let ExprKind::Attribute(inner) = &a.object.kind else { return };
354
- let (Expr { kind: ExprKind::Self_, .. }, AttrKind::Field(x)) = (&inner.object, &inner.attr) else {
355
- return;
356
- };
357
- if !x.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
358
- return;
359
- }
360
- let m = call.name.clone();
361
- let res: Result<String, String> = if headerRecords.iter().any(|r| r == x) {
362
- if recMethods.iter().any(|(o, mm)| o == x && mm.name == m) {
363
- if ownDefined.contains(&m) {
364
- Ok(format!("{}__{}", x, m))
365
- } else {
366
- Ok(m.clone())
367
- }
368
- } else {
369
- Err(format!(
370
- "{}.{} calls self.{}.{}, but {} has no method {}",
371
- rname, fname, x, m, x, m
372
- ))
373
- }
374
- } else {
375
- Err(format!(
376
- "{}.{} calls self.{}.{}, but {} does not embed {}",
377
- rname, fname, x, m, rname, x
378
- ))
379
- };
380
- match &res {
381
- Ok(newName) => {
382
- let call = call.clone();
383
- *e = Expr::Attribute(Box::new(AttributeExpr {
384
- object: Expr::Self_,
385
- attr: AttrKind::Method(FnCall {
386
- name: newName.clone(),
387
- ..call
388
- }),
389
- }));
390
- }
391
- Err(msg) => calls.push(msg.clone()),
392
- }
393
- });
394
- errs.extend(calls);
395
- }
396
- cx.errors.extend(errs);
397
- }
398
-
399
- // 7. Write back.
400
- for it in std::mem::take(&mut copies) {
401
- source.items.push(Item::Fn(it));
402
- }
403
- // Contribution when embedded: header plus init-declared fields.
404
- if !already {
405
- let mut own: Vec<EnumParam> = headerNames
406
- .iter()
407
- .zip(headerTys.iter())
408
- .enumerate()
409
- .map(|(i, (n, t))| EnumParam {
410
- name: n.clone(),
411
- ty: t.clone(),
412
- isMut: headerMuts.get(i).copied().unwrap_or(false),
413
- })
414
- .collect();
415
- let init = source.items.iter().find_map(|i| match i {
416
- Item::Fn(f)
417
- if f.type_param.as_deref() == Some(rname) && f.name == "init" && f.has_self =>
418
- {
419
- Some(f.clone())
420
- }
421
- _ => None,
422
- });
423
- if let Some(init) = &init {
424
- let known: HashSet<String> = own
425
- .iter()
426
- .map(|p| p.name.clone())
427
- .chain(spliced.iter().map(|p| p.name.clone()))
428
- .collect();
429
- for st in initStmts(init) {
430
- let Some((fname, val, ann)) = fieldAssign(&st) else { continue };
431
- if known.contains(fname) || own.iter().any(|p| p.name == fname) {
432
- continue;
433
- }
434
- if let Some(t) = ann.cloned().or_else(|| inferType(val)) {
435
- own.push(EnumParam {
436
- name: fname.to_string(),
437
- isMut: false,
438
- ty: t,
439
- });
440
- }
441
- }
442
- }
443
- cx.own.insert(rname.to_string(), own);
444
- }
445
-
446
- if hasHeader {
447
- if let Item::Enum(e) = &mut source.items[idx] {
448
- let mut fields: Vec<(String, Vec<String>)> = recEntries.iter().cloned().collect();
449
- fields.push((DONE.to_string(), vec![]));
450
- if let Some(info) = e.structInfoMut() {
451
- info.embeds = Vec::new();
452
- info.embedFields = fields;
453
- }
454
- if let Some(v) = e.variants.first_mut() {
455
- let mut tys: Vec<Type> = spliced.iter().map(|p| p.ty.clone()).collect();
456
- tys.extend(std::mem::take(&mut v.fields));
457
- let mut names: Vec<String> = spliced.iter().map(|p| p.name.clone()).collect();
458
- names.extend(std::mem::take(&mut v.field_names));
459
- let mut muts: Vec<bool> = spliced.iter().map(|p| p.isMut).collect();
460
- let own = std::mem::take(&mut v.mut_fields);
461
- muts.extend(own.into_iter().chain(std::iter::repeat(false)).take(tys.len() - muts.len()));
462
- v.fields = tys;
463
- v.field_names = names;
464
- v.mut_fields = muts;
465
- }
466
- }
467
- }
468
- cx.stack.pop();
469
- cx.done.insert(rname.to_string());
470
- }
471
-
472
- /// `B(...)` where `B` has an `abstract fun` method: B is abstract.
473
- fn reportAbstractConstruction(source: &mut Source, errors: &mut Vec<String>) {
474
- let mut abstractOf: HashMap<String, String> = HashMap::new();
475
- for i in &source.items {
476
- if let Item::Fn(f) = i {
477
- if let Some(t) = &f.type_param {
478
- if isAbstract(f) && !abstractOf.contains_key(t) {
479
- abstractOf.insert(t.clone(), f.name.clone());
480
- }
481
- }
482
- }
483
- }
484
- if abstractOf.is_empty() {
485
- return;
486
- }
487
- let mut found: Vec<String> = Vec::new();
488
- let mut visit = |e: &mut Expr| {
489
- if let ExprKind::ClassCall(c) = &mut e.kind {
490
- if let Some(m) = abstractOf.get(&c.type_name) {
491
- found.push(format!("cannot construct {}: {} has no body", c.type_name, m));
492
- }
493
- }
494
- };
495
- for it in source.items.iter_mut() {
496
- match it {
497
- Item::Fn(f) => {
498
- mapExprs(&mut f.body, &mut visit);
499
- for p in f.params.iter_mut() {
500
- if let Some(d) = &mut p.default {
501
- crate::forms::mapExprsIn(d, &mut visit);
502
- }
503
- }
504
- }
505
- Item::Const(c) => crate::forms::mapExprsIn(&mut c.value, &mut visit),
506
- Item::Test(t) => {
507
- let mut body = FnBody::Block(std::mem::replace(&mut t.body, Block { stmts: vec![] }));
508
- mapExprs(&mut body, &mut visit);
509
- if let FnBody::Block(b) = body {
510
- t.body = b;
511
- }
512
- }
513
- _ => {}
514
- }
515
- }
516
- errors.extend(found);
517
- }
compiler/core/src/layout.rs CHANGED
@@ -1,14 +1,12 @@
1
1
  //! The one place a struct's runtime layout is decided.
2
2
  //!
3
3
  //! A struct is `Flat` (today's `struct` layout: unboxed, copied field-wise)
4
- //! when it has fields but no generics, no embedding, no `mut` field, is not recursive and
4
+ //! when it has fields but no generics, no `mut` field, is not recursive and
5
5
  //! every field is flat-able (a scalar primitive or another flat struct).
6
6
  //! Everything else is `Boxed` (a heap struct). A
7
7
  //! struct with a `fun init(self)` method (which may declare internal fields)
8
8
  //! is always `Boxed`; the parser records it in `StructInfo::forceBoxed`.
9
9
  //! A struct generated from an inline variant is always `Boxed` too.
10
- //! A struct that another struct uses as its `base` (first embed) is `Boxed`:
11
- //! its subtypes are wasm-gc subtypes of its heap struct.
12
10
 
13
11
  use crate::ast::{Enum, Item};
14
12
  use std::collections::{HashMap, HashSet};
@@ -26,8 +24,7 @@ const FLAT_SCALARS: [&str; 4] = ["Int", "Float", "Bool", "Byte"];
26
24
  /// (used to resolve fields naming other structs).
27
25
  pub fn structLayout(e: &Enum, all: &[Enum]) -> Layout {
28
26
  let find = |n: &str| all.iter().find(|o| o.name == n);
29
- let bases = basesOf(all.iter());
30
- if isFlat(e, &find, &bases, &mut Vec::new()) {
27
+ if isFlat(e, &find, &mut Vec::new()) {
31
28
  Layout::Flat
32
29
  } else {
33
30
  Layout::Boxed
@@ -37,8 +34,7 @@ pub fn structLayout(e: &Enum, all: &[Enum]) -> Layout {
37
34
  /// [`structLayout`] over a program's items.
38
35
  pub fn itemLayout(e: &Enum, items: &[Item]) -> Layout {
39
36
  let find = |n: &str| enumIn(items, n);
40
- let bases = basesOf(enums(items));
41
- if isFlat(e, &find, &bases, &mut Vec::new()) {
37
+ if isFlat(e, &find, &mut Vec::new()) {
42
38
  Layout::Flat
43
39
  } else {
44
40
  Layout::Boxed
@@ -64,10 +60,9 @@ pub fn flatStructs(items: &[Item]) -> HashSet<String> {
64
60
  }
65
61
  }
66
62
  let find = |n: &str| byName.get(n).copied();
67
- let bases = basesOf(enums(items));
68
63
  byName
69
64
  .values()
70
- .filter(|e| isFlat(e, &find, &bases, &mut Vec::new()))
65
+ .filter(|e| isFlat(e, &find, &mut Vec::new()))
71
66
  .map(|e| e.name.clone())
72
67
  .collect()
73
68
  }
@@ -79,35 +74,19 @@ fn enumIn<'a>(items: &'a [Item], name: &str) -> Option<&'a Enum> {
79
74
  })
80
75
  }
81
76
 
82
- fn enums(items: &[Item]) -> impl Iterator<Item = &Enum> {
83
- items.iter().filter_map(|i| match i {
84
- Item::Enum(e) => Some(e),
85
- _ => None,
86
- })
87
- }
88
-
89
- /// Names some struct uses as its base (first embed).
90
- fn basesOf<'a>(all: impl IntoIterator<Item = &'a Enum>) -> HashSet<String> {
91
- all.into_iter()
92
- .filter(|e| e.isStruct())
93
- .filter_map(|e| e.base().map(str::to_string))
94
- .collect()
95
- }
96
-
97
77
  fn isFlat<'a, F>(
98
78
  e: &'a Enum,
99
79
  find: &F,
100
- bases: &HashSet<String>,
101
80
  visiting: &mut Vec<&'a str>,
102
81
  ) -> bool
103
82
  where
104
83
  F: Fn(&str) -> Option<&'a Enum>,
105
84
  {
106
85
  let Some(info) = e.structInfo() else { return false };
107
- if info.forceBoxed || e.variants.len() != 1 || bases.contains(&e.name) {
86
+ if info.forceBoxed || e.variants.len() != 1 {
108
87
  return false;
109
88
  }
110
- if !e.generics.is_empty() || !info.embeds.is_empty() || !info.embedFields.is_empty() {
89
+ if !e.generics.is_empty() {
111
90
  return false;
112
91
  }
113
92
  let v = &e.variants[0];
@@ -123,7 +102,7 @@ where
123
102
  let ok = v.fields.iter().all(|t| {
124
103
  t.generics.is_empty()
125
104
  && (FLAT_SCALARS.contains(&t.name.as_str())
126
- || find(&t.name).is_some_and(|o| isFlat(o, find, bases, visiting)))
105
+ || find(&t.name).is_some_and(|o| isFlat(o, find, visiting)))
127
106
  });
128
107
  visiting.pop();
129
108
  ok
compiler/core/src/lib.rs CHANGED
@@ -9,7 +9,6 @@ pub mod closure_spec;
9
9
  pub mod ctors;
10
10
  pub mod diag;
11
11
  pub mod drive;
12
- pub mod embeds;
13
12
  pub mod enum_checks;
14
13
  pub mod formatter;
15
14
  pub mod fip;
@@ -26,7 +25,6 @@ pub mod rename;
26
25
  pub mod reuse;
27
26
  pub mod session;
28
27
  pub mod simplify;
29
- pub mod subtyping;
30
28
  pub mod tail;
31
29
  pub mod tests_lower;
32
30
  pub mod trmc;
compiler/core/src/lower.rs CHANGED
@@ -20,15 +20,11 @@ pub fn perFileDiag(source: &mut Source) -> Result<(), Vec<Diagnostic>> {
20
20
  Ok(())
21
21
  }
22
22
 
23
- /// Front-end lowering of a merged program: embeds, constructors, inline-variant
23
+ /// Front-end lowering of a merged program: constructors, inline-variant
24
24
  /// type checks, union-enum errors, then forms. The first failing stage returns its
25
25
  /// errors.
26
26
  pub fn frontEndDiag(source: &mut Source) -> Result<(), Vec<Diagnostic>> {
27
27
  crate::session::begin();
28
- let embedErrors = crate::embeds::lowerEmbeds(source);
29
- if !embedErrors.is_empty() {
30
- return Err(diags(DiagKind::Type, embedErrors));
31
- }
32
28
  let ctorErrors = crate::ctors::lowerCtors(source);
33
29
  if !ctorErrors.is_empty() {
34
30
  return Err(diags(DiagKind::Type, ctorErrors));
@@ -42,7 +38,7 @@ pub fn frontEndDiag(source: &mut Source) -> Result<(), Vec<Diagnostic>> {
42
38
  return Err(diags(DiagKind::Type, memberErrors));
43
39
  }
44
40
  crate::forms::prepareForms(source);
45
- // The stages above build nodes (embedded calls, constructors) with no id.
41
+ // The stages above build nodes (constructors) with no id.
46
42
  crate::ast::numberIds(source);
47
43
  Ok(())
48
44
  }
compiler/core/src/parser.rs CHANGED
@@ -307,8 +307,8 @@ impl<'a> AstParser<'a> {
307
307
  // struct: "struct" name generics? ("(" field,* ")")? ("=" body)?
308
308
  // Its layout is chosen by `layout::structLayout`.
309
309
  let mut e = self.parseCtor(node); // always a `TypeKind::Struct`
310
- // Embedding and `fun init(self)` (internal fields) need a boxed struct;
310
+ // `fun init(self)` (internal fields) needs a boxed struct; lowering later
311
- // lowering later clears both, so record it here.
311
+ // removes the init, so record it here.
312
312
  let hasInit = self
313
313
  .childrenOfKind(node, "fn")
314
314
  .into_iter()
@@ -317,7 +317,7 @@ impl<'a> AstParser<'a> {
317
317
  f.name == "init" && f.has_self
318
318
  });
319
319
  if let Some(info) = e.structInfoMut() {
320
- info.forceBoxed = !info.embeds.is_empty() || hasInit;
320
+ info.forceBoxed = hasInit;
321
321
  }
322
322
  e
323
323
  }
@@ -331,27 +331,9 @@ impl<'a> AstParser<'a> {
331
331
  let generics = self.parseGenericsField(node);
332
332
  // The enum_named_field children, in order, are the constructor's fields.
333
333
  let variant = self.parseEnumVariant(node);
334
- let mut cursor = node.walk();
335
- let embedFields: Vec<(String, Vec<String>)> = node
336
- .children_by_field_name("embed", &mut cursor)
337
- .map(|n| {
338
- let name = n
339
- .child_by_field_name("name")
340
- .map(|c| self.text(c))
341
- .unwrap_or_default();
342
- let mut c2 = n.walk();
343
- let exposed = n
344
- .children_by_field_name("exposed", &mut c2)
345
- .map(|c| self.text(c))
346
- .collect();
347
- (name, exposed)
348
- })
349
- .collect();
350
- let embeds: Vec<String> = embedFields.iter().map(|(n, _)| n.clone()).collect();
351
- let base = embeds.first().cloned();
352
334
  let pos = node.start_position();
353
335
  Enum {
354
- kind: TypeKind::Struct(StructInfo { forceBoxed: false, embeds, embedFields, base }),
336
+ kind: TypeKind::Struct(StructInfo { forceBoxed: false }),
355
337
  name,
356
338
  line: pos.row + 1,
357
339
  column: pos.column + 1,
@@ -479,8 +461,6 @@ impl<'a> AstParser<'a> {
479
461
 
480
462
  let is_extern = node.child_by_field_name("externKw").is_some();
481
463
  let is_export = node.child_by_field_name("exportKw").is_some();
482
- let is_override = node.child_by_field_name("overrideKw").is_some();
483
- let is_abstract = node.child_by_field_name("abstractKw").is_some();
484
464
  let is_fip = node.child_by_field_name("fipKw").is_some();
485
465
 
486
466
  let generics: Vec<String> = node
@@ -504,8 +484,6 @@ impl<'a> AstParser<'a> {
504
484
  is_extern,
505
485
  is_export,
506
486
  is_test: false,
507
- is_override,
508
- is_abstract,
509
487
  is_fip,
510
488
  has_self: !self.childrenOfKind(node, "self").is_empty(),
511
489
  params,
@@ -1890,7 +1868,6 @@ mod removedSyntaxTests {
1890
1868
  fn structAcceptsRecordCapabilities() {
1891
1869
  let cases = [
1892
1870
  ("Box", "struct Box[T](value: T)\n"),
1893
- ("House", "struct Actor(x: Int)\nstruct House(Actor(x), k: Int)\n"),
1894
1871
  ("Node", "struct Node(mut count: Int)\n"),
1895
1872
  ("Io", "struct Io\n"),
1896
1873
  ("Io", "struct Io() =\n fun one(self) -> Int = 1\n"),
compiler/core/src/purity.rs CHANGED
@@ -8,7 +8,7 @@
8
8
  //! So is `x.f(..)` where `f` may be an fn-typed field. A method call on `self`, on a
9
9
  //! parameter of a named type (never rebound or shadowed) or on a field read of one
10
10
  //! resolves by that type (`io.clear(..)` is `Io.clear`, not `Slice.clear`): to the type's
11
- //! method and every override below it, the call being as pure as all of them together.
11
+ //! method.
12
12
  //! A method call whose receiver type is unknown is pure only if every method of that name
13
13
  //! is. Solved as a greatest fixpoint, so (mutual) recursion with no impure effect stays pure.
14
14
  use crate::ast::*;
@@ -53,12 +53,6 @@ pub fn pureFns(src: &Source) -> HashSet<String> {
53
53
  if reachesIo(f, &ioTypes) {
54
54
  info.impure = true;
55
55
  }
56
- // A call to `Base.m` may run any overrider: opaque, so never assumed pure.
57
- if let Some(t) = &f.type_param {
58
- if receivers.overrides.contains_key(&(t.clone(), f.name.clone())) {
59
- info.impure = true;
60
- }
61
- }
62
56
  infos.push(info);
63
57
  }
64
58
  // (type, name) -> index, for `Type.m(..)` calls.
@@ -249,8 +243,6 @@ struct Info {
249
243
  struct Receivers<'a> {
250
244
  /// struct -> field -> the name of its type; a field typed by the struct's generic is left out
251
245
  fields: HashMap<&'a str, HashMap<&'a str, &'a str>>,
252
- /// `(Type, method)` -> the overrides such a call may run
253
- overrides: HashMap<(String, String), Vec<String>>,
254
246
  /// names of the fn-typed fields of all types: `x.f(..)` may call that closure
255
247
  fnFields: HashSet<&'a str>,
256
248
  /// `Type.method` of every method: on such a receiver the method wins over a field
@@ -278,7 +270,7 @@ impl<'a> Receivers<'a> {
278
270
  let known = v.field_names.iter().zip(&v.fields).filter(|(_, t)| !e.generics.iter().any(|g| g.name == t.name));
279
271
  fields.insert(e.name.as_str(), known.map(|(n, t)| (n.as_str(), t.name.as_str())).collect());
280
272
  }
281
- Receivers { fields, overrides: crate::subtyping::overrideTargets(src), fnFields, methods }
273
+ Receivers { fields, fnFields, methods }
282
274
  }
283
275
  }
284
276
 
@@ -523,10 +515,7 @@ impl<'a> Walker<'a> {
523
515
  self.mutates = true;
524
516
  }
525
517
  Some(t) => {
526
- let below = self.receivers.overrides.get(&(t.clone(), c.name.clone()));
527
- let keys = below.into_iter().flatten().cloned();
528
- let calls = std::iter::once(format!("{}.{}", t, c.name)).chain(keys).map(|k| Call::Typed(k, c.name.clone()));
518
+ self.calls.push(Call::Typed(format!("{}.{}", t, c.name), c.name.clone()));
529
- self.calls.extend(calls);
530
519
  }
531
520
  None => self.calls.push(Call::Method(c.name.clone())),
532
521
  },
compiler/core/src/subtyping.rs DELETED
@@ -1,92 +0,0 @@
1
- //! Struct subtyping: a struct embedding another struct FIRST is its subtype.
2
- //!
3
- //! The base is recorded by the parser in `StructInfo::base`. Only a non-generic
4
- //! struct item counts as a base (traits and generic structs never do).
5
-
6
- use crate::ast::{Enum, Item, Source};
7
- use std::collections::{HashMap, HashSet};
8
-
9
- fn structNamed<'a>(source: &'a Source, name: &str) -> Option<&'a Enum> {
10
- source.items.iter().find_map(|i| match i {
11
- Item::Enum(e) if e.name == name && e.isStruct() => Some(e),
12
- _ => None,
13
- })
14
- }
15
-
16
- /// The struct `name` subtypes directly, if its first embed is a valid base.
17
- fn baseOf<'a>(source: &'a Source, name: &str) -> Option<&'a str> {
18
- let b = structNamed(source, name)?.base()?;
19
- let be = structNamed(source, b)?;
20
- be.generics.is_empty().then_some(b)
21
- }
22
-
23
- /// Strict ancestors of `name`, nearest first (cycle-safe).
24
- fn ancestors<'a>(source: &'a Source, name: &str) -> Vec<&'a str> {
25
- let mut out: Vec<&str> = Vec::new();
26
- let mut cur = name;
27
- while let Some(b) = baseOf(source, cur) {
28
- if b == name || out.contains(&b) {
29
- break;
30
- }
31
- out.push(b);
32
- cur = b;
33
- }
34
- out
35
- }
36
-
37
- /// Is `a` the same struct as `b` or a (transitive) subtype of it?
38
- pub fn isSubtype(source: &Source, a: &str, b: &str) -> bool {
39
- a == b || ancestors(source, a).contains(&b)
40
- }
41
-
42
- fn declaresOverride(source: &Source, s: &str, m: &str) -> bool {
43
- source.items.iter().any(|i| match i {
44
- Item::Fn(f) => f.is_override && f.name == m && f.type_param.as_deref() == Some(s),
45
- _ => false,
46
- })
47
- }
48
-
49
- /// Strict subtypes of `base` whose own method `m` is an `override`, deepest first.
50
- pub fn overriders(source: &Source, base: &str, m: &str) -> Vec<String> {
51
- let mut found: Vec<(usize, String)> = Vec::new();
52
- for i in &source.items {
53
- let Item::Enum(e) = i else { continue };
54
- if e.name == base || !isSubtype(source, &e.name, base) {
55
- continue;
56
- }
57
- if declaresOverride(source, &e.name, m) && !found.iter().any(|(_, n)| *n == e.name) {
58
- found.push((ancestors(source, &e.name).len(), e.name.clone()));
59
- }
60
- }
61
- found.sort_by(|a, b| b.0.cmp(&a.0));
62
- found.into_iter().map(|(_, n)| n).collect()
63
- }
64
-
65
- /// Every `(type, method)` a call may dispatch dynamically: each ancestor of an
66
- /// overriding struct paired with the overridden method.
67
- pub fn virtualMethods(source: &Source) -> HashSet<(String, String)> {
68
- let mut out = HashSet::new();
69
- for i in &source.items {
70
- let Item::Fn(f) = i else { continue };
71
- if !f.is_override {
72
- continue;
73
- }
74
- let Some(s) = f.type_param.as_deref() else { continue };
75
- for a in ancestors(source, s) {
76
- out.insert((a.to_string(), f.name.clone()));
77
- }
78
- }
79
- out
80
- }
81
-
82
- /// Every call that may run an override: `(type, method)` -> the `Type.method` key of each
83
- /// override below that type, deepest first. A call on the type runs its own method or one of these.
84
- pub fn overrideTargets(source: &Source) -> HashMap<(String, String), Vec<String>> {
85
- virtualMethods(source)
86
- .into_iter()
87
- .map(|(t, m)| {
88
- let keys = overriders(source, &t, &m).into_iter().map(|s| format!("{s}.{m}")).collect();
89
- ((t, m), keys)
90
- })
91
- .collect()
92
- }
compiler/core/src/tests_lower.rs CHANGED
@@ -114,8 +114,6 @@ fn desugarTestsToFns(source: &crate::ast::Source) -> (crate::ast::Source, Vec<(S
114
114
  is_extern: false,
115
115
  is_export: false,
116
116
  is_test: true,
117
- is_override: false,
118
- is_abstract: false,
119
117
  is_fip: false,
120
118
  has_self: true,
121
119
  params: ioParam.clone(),
compiler/core/tests/core/closure_spec_test.rs CHANGED
@@ -419,19 +419,3 @@ fn identical_closures_share_a_copy() {
419
419
  assert!(!names.contains(&"List.map$c1".to_string()), "{names:?}");
420
420
  assert_eq!(runInt("dedup", &src), 18);
421
421
  }
422
-
423
- fn specialised(body: &str) -> Vec<String> {
424
- let mut s = plum_test_support::parseRawSource(body);
425
- plum_core::closure_spec::specialiseWith(&mut s, &plum_core::closure_spec::Hints::new())
426
- }
427
-
428
- const EACH: &str = "struct Actor(w: Int) =\n fun each(self, f: fn(Int) -> Int) -> Int = f(self.w)\n\nfun main(a: Actor) -> Int = a.each(|x| x + 1)\n";
429
-
430
- #[test]
431
- fn virtual_method_is_not_specialised() {
432
- let over = "struct Ship(Actor(w), t: Int) =\n override fun each(self, f: fn(Int) -> Int) -> Int = f(self.t)\n\n";
433
- let plain = specialised(EACH);
434
- assert!(!plain.is_empty(), "control: a non-virtual method is specialised");
435
- let src = EACH.replacen("fun main", &format!("{over}fun main"), 1);
436
- assert!(specialised(&src).is_empty());
437
- }
compiler/core/tests/core/formatter_test.rs CHANGED
@@ -265,17 +265,9 @@ fn formatAnnotatedDeclaration() {
265
265
  }
266
266
 
267
267
  #[test]
268
- fn format_record_embeds() {
268
+ fn format_struct_header() {
269
- let out = formatSource("struct House(Ent,Cl,k:Int)\n").expect("format");
269
+ let out = formatSource("struct House(x:Int,k:Int)\n").expect("format");
270
- assert!(out.contains("struct House(Ent, Cl, k: Int)"), "got: {out}");
270
+ assert!(out.contains("struct House(x: Int, k: Int)"), "got: {out}");
271
- }
272
-
273
- #[test]
274
- fn format_record_header_selection() {
275
- let once = formatSource("struct Ship(Actor( x,y ),tex:Int)\n").expect("format");
276
- assert!(once.contains("struct Ship(Actor(x, y), tex: Int)"), "got: {once}");
277
- let twice = formatSource(&once).expect("second");
278
- assert_eq!(once, twice, "not idempotent:\n{once}");
279
271
  }
280
272
 
281
273
  #[test]
@@ -287,15 +279,6 @@ fn format_typed_self_assign() {
287
279
  assert_eq!(once, twice, "not idempotent:\n{once}");
288
280
  }
289
281
 
290
- #[test]
291
- fn format_override_fun() {
292
- let input = "struct Rr(a: Int) =\n override fun draw(self) = 1\n";
293
- let once = formatSource(input).expect("format");
294
- assert!(once.contains(" override fun draw(self) = 1"), "got: {once}");
295
- let twice = formatSource(&once).expect("second");
296
- assert_eq!(once, twice, "not idempotent:\n{once}");
297
- }
298
-
299
282
  #[test]
300
283
  fn formatMatchBodyOneLine() {
301
284
  let input = "fun f(x: Int) -> Int = match x\n 0 => 1\n _ => 2\n";
compiler/core/tests/core/layout_test.rs CHANGED
@@ -37,12 +37,6 @@ fn recursive_is_boxed() {
37
37
  assert_eq!(layoutOf("struct Loop(next: Loop, n: Int)\n", "Loop"), Layout::Boxed);
38
38
  }
39
39
 
40
- #[test]
41
- fn embedding_is_boxed() {
42
- let src = "struct Actor(x: Float)\nstruct Walker(Actor, speed: Float)\n";
43
- assert_eq!(layoutOf(src, "Walker"), Layout::Boxed);
44
- }
45
-
46
40
  #[test]
47
41
  fn mut_field_is_boxed() {
48
42
  assert_eq!(layoutOf("struct Counter(mut n: Int)\n", "Counter"), Layout::Boxed);
@@ -105,17 +99,3 @@ fn std_and_examples_layouts() {
105
99
  }
106
100
  }
107
101
 
108
- #[test]
109
- fn baseIsBoxed() {
110
- let src = "struct Actor(x: Int)\nstruct Ship(Actor(x), s: Int)\nstruct Plain(x: Int)\n";
111
- assert_eq!(layoutOf(src, "Actor"), Layout::Boxed);
112
- assert_eq!(layoutOf(src, "Ship"), Layout::Boxed);
113
- assert_eq!(layoutOf(src, "Plain"), Layout::Flat);
114
- }
115
-
116
- #[test]
117
- fn secondEmbedStaysFlat() {
118
- let src = "struct Aa(x: Int)\nstruct Bb(y: Int)\nstruct Cc(Aa(x), Bb(y))\n";
119
- assert_eq!(layoutOf(src, "Bb"), Layout::Flat);
120
- assert_eq!(layoutOf(src, "Aa"), Layout::Boxed);
121
- }
compiler/core/tests/core/loader_test.rs CHANGED
@@ -545,7 +545,7 @@ fn single_file_equals_loader() {
545
545
  let programs = [
546
546
  "fun id(a: Unit) -> Unit = a\nfun main(u: Unit) = id(u)\n",
547
547
  "struct Pt(x: Unit, y: Unit)\nfun mk(u: Unit) = Pt(x: u, y: u)\n",
548
- "struct Actor(x: Unit)\nstruct Boss(Actor(x), hp: Unit)\nfun main(u: Unit) = Boss(x: u, hp: u)\n",
548
+ "struct Boss(x: Unit, hp: Unit)\nfun main(u: Unit) = Boss(x: u, hp: u)\n",
549
549
  "enum Shape = Circle | Dot\nfun f(s: Shape) = s\n",
550
550
  "enum Shape = Circle(r: Unit) | Sq\nfun f(s: Shape) = s\n",
551
551
  ];
compiler/core/tests/core/lower_test.rs CHANGED
@@ -1,13 +1,4 @@
1
1
  #![allow(non_snake_case)]
2
- use plum_core::lower::frontEndDiag;
3
-
4
- #[test]
5
- fn lowersEmbeds() {
6
- let mut s = plum_test_support::parseRawSource(
7
- "struct Actor(x: Int) =\n fun up(self) -> Int = 0\n\nstruct Hut(Actor(x), k: Int) =\n override fun up(self) -> Int = 1\n",
8
- );
9
- assert_eq!(frontEndDiag(&mut s), Ok(()));
10
- }
11
2
 
12
3
  #[test]
13
4
  fn trait_declaration_is_a_parse_error() {
@@ -17,11 +8,3 @@ fn trait_declaration_is_a_parse_error() {
17
8
  assert!(parser.parse(src, None).unwrap().root_node().has_error());
18
9
  }
19
10
 
20
- #[test]
21
- fn second_embed_errors() {
22
- let mut s = plum_test_support::parseRawSource(
23
- "struct Aa(x: Int)\nstruct Bb(y: Int)\nstruct Ss(Aa(x), Bb(y))\n",
24
- );
25
- let e = frontEndDiag(&mut s).unwrap_err();
26
- assert!(e.iter().any(|d| d.message.contains("struct Ss embeds Aa and Bb; a struct can embed only one struct")), "{e:?}");
27
- }
compiler/core/tests/core/main.rs CHANGED
@@ -13,17 +13,14 @@ mod list_literal_test;
13
13
  mod loader_test;
14
14
  mod lower_test;
15
15
  mod match_body_parse_test;
16
- mod override_parse_test;
17
16
  mod parser_test;
18
17
  mod purity_test;
19
18
  mod question_parse_test;
20
- mod record_embed_test;
21
19
  mod record_init_parse_test;
22
20
  mod record_init_test;
23
21
  mod rename_test;
24
22
  mod session_test;
25
23
  mod simplify_test;
26
- mod subtyping_test;
27
24
  mod tail_test;
28
25
  mod tests_lower_test;
29
26
  mod trmc_test;
compiler/core/tests/core/override_parse_test.rs DELETED
@@ -1,42 +0,0 @@
1
- #![allow(non_snake_case)]
2
- use plum_core::ast::*;
3
-
4
- fn parseFns(src: &str) -> Vec<Fn> {
5
- let source = plum_test_support::parseRawSource(src);
6
- source
7
- .items
8
- .iter()
9
- .filter_map(|i| match i {
10
- Item::Fn(f) => Some(f.clone()),
11
- _ => None,
12
- })
13
- .collect()
14
- }
15
-
16
- #[test]
17
- fn overrideFunParses() {
18
- let fns = parseFns("struct Rr(a: Int) =\n override fun draw(self) = 1\n");
19
- let f = fns.iter().find(|f| f.name == "draw").expect("draw");
20
- assert_eq!(f.type_param.as_deref(), Some("Rr"));
21
- assert!(f.is_override);
22
- }
23
-
24
- #[test]
25
- fn plainFunIsNotOverride() {
26
- let fns = parseFns("struct Rr(a: Int) =\n fun draw(self) = 1\n");
27
- let f = fns.iter().find(|f| f.name == "draw").expect("draw");
28
- assert!(!f.is_override);
29
- }
30
-
31
- #[test]
32
- fn attributeOnAttributeParses() {
33
- let fns = parseFns("fun main() =\n self.Stage.draw()\n");
34
- assert_eq!(fns.len(), 1);
35
- }
36
-
37
- #[test]
38
- fn overrideStaysAnIdentifier() {
39
- let fns = parseFns("fun main() =\n overrides := 1\n overrider := overrides\n override := 2\n");
40
- assert_eq!(fns.len(), 1);
41
- assert!(!fns[0].is_override);
42
- }
compiler/core/tests/core/parser_test.rs CHANGED
@@ -813,25 +813,13 @@ fn groupingKeepsPrecedenceWithoutNode() {
813
813
  assert!(matches!(b.right, Expr { kind: ExprKind::Int(3), .. }));
814
814
  }
815
815
 
816
- #[test]
817
- fn abstractFunSetsFlag() {
818
- let s = parseRaw("struct Aa(a: Int) =\n abstract fun draw(self) -> Int\n fun other(self) -> Int = 1\n");
819
- let fns: Vec<&Fn> = s
820
- .items
821
- .iter()
822
- .filter_map(|i| if let Item::Fn(f) = i { Some(f) } else { None })
823
- .collect();
824
- assert!(fns.iter().any(|f| f.name == "draw" && f.is_abstract && matches!(f.body, FnBody::Extern)));
825
- assert!(fns.iter().any(|f| f.name == "other" && !f.is_abstract));
826
- }
827
-
828
816
  #[test]
829
817
  fn fipFunSetsFlag() {
830
- let s = parseRaw("fip fun a1() -> Int = 1\nfun b1() -> Int = 2\nstruct Sq(x: Int) =\n override fip fun d(self) -> Int = self.x\n");
818
+ let s = parseRaw("fip fun a1() -> Int = 1\nfun b1() -> Int = 2\nstruct Sq(x: Int) =\n fip fun d(self) -> Int = self.x\n");
831
819
  let fns: Vec<&Fn> = s.items.iter().filter_map(|i| if let Item::Fn(f) = i { Some(f) } else { None }).collect();
832
- assert!(fns.iter().any(|f| f.name == "a1" && f.is_fip && !f.is_override));
820
+ assert!(fns.iter().any(|f| f.name == "a1" && f.is_fip));
833
821
  assert!(fns.iter().any(|f| f.name == "b1" && !f.is_fip));
834
- assert!(fns.iter().any(|f| f.name == "d" && f.is_fip && f.is_override));
822
+ assert!(fns.iter().any(|f| f.name == "d" && f.is_fip));
835
823
  }
836
824
 
837
825
  #[test]
compiler/core/tests/core/purity_test.rs CHANGED
@@ -101,17 +101,6 @@ fn closure_capturing_io_impure() {
101
101
  assert_eq!(pure(&s), set(&["c"]));
102
102
  }
103
103
 
104
- #[test]
105
- fn virtual_call_through_base_is_impure() {
106
- let s = format!(
107
- "{P}struct Actor(w: Int) =\n fun go(self, io: Io) -> Int = 1\n\nstruct Ship(Actor(w), t: Int) =\n override fun go(self, io: Io) -> Int =\n io.printLn(\"x\")\n 2\n\nfun run(a: Actor, io: Io) -> Int = a.go(io)\nfun leaf(s: Ship, io: Io) -> Int = s.go(io)\nfun plain(a: Actor) -> Int = a.w\n"
108
- );
109
- let p = pure(&s);
110
- assert!(!p.contains("Actor.go"), "{p:?}");
111
- assert!(!p.contains("run"), "{p:?}");
112
- assert!(p.contains("plain"), "{p:?}");
113
- }
114
-
115
104
  #[test]
116
105
  fn nonvirtual_method_stays_pure() {
117
106
  let s = format!("{P}struct Actor(w: Int) =\n fun go(self) -> Int = 1\n\nfun run(a: Actor) -> Int = a.go()\n");
compiler/core/tests/core/record_embed_test.rs DELETED
@@ -1,230 +0,0 @@
1
- #![allow(non_snake_case)]
2
- use plum_core::ast::*;
3
- use plum_core::ctors::lowerCtors;
4
- use plum_core::embeds::lowerEmbeds;
5
-
6
- fn parseRaw(src: &str) -> Source {
7
- plum_test_support::parseRawSource(src)
8
- }
9
-
10
- fn lowerAll(src: &str) -> (Source, Vec<String>) {
11
- let mut s = parseRaw(src);
12
- let mut errs = lowerEmbeds(&mut s);
13
- errs.extend(lowerCtors(&mut s));
14
- (s, errs)
15
- }
16
-
17
- fn enumOf<'a>(s: &'a Source, name: &str) -> &'a Enum {
18
- s.items
19
- .iter()
20
- .find_map(|i| match i {
21
- Item::Enum(e) if e.name == name => Some(e),
22
- _ => None,
23
- })
24
- .unwrap()
25
- }
26
-
27
- fn fnOf<'a>(s: &'a Source, rec: &str, name: &str) -> Option<&'a Fn> {
28
- s.items.iter().find_map(|i| match i {
29
- Item::Fn(f) if f.type_param.as_deref() == Some(rec) && f.name == name => Some(f),
30
- _ => None,
31
- })
32
- }
33
-
34
- const STAGE: &str = "struct Stage(camScale: Float) =
35
- fun init(self) =
36
- self.root: Int = 0
37
- self.camX = 0.0
38
-
39
- fun act(self, dt: Float) -> Int = 1
40
-
41
- fun draw(self) -> Int = 2
42
-
43
- fun create() -> Int = 3
44
-
45
- ";
46
-
47
- fn world(extra: &str) -> String {
48
- format!(
49
- "{STAGE}struct World(Stage(camScale), score: Int) =\n fun init(self) =\n self.best = 0\n\n{extra}"
50
- )
51
- }
52
-
53
- #[test]
54
- fn fieldOrderAndCopiedMethods() {
55
- let (s, errs) = lowerAll(&world(" override fun draw(self) -> Int = self.Stage.draw()\n"));
56
- assert!(errs.is_empty(), "{errs:?}");
57
- let w = enumOf(&s, "World");
58
- assert_eq!(
59
- w.variants[0].field_names,
60
- vec!["camScale", "root", "camX", "score", "best"]
61
- );
62
- assert!(fnOf(&s, "World", "act").is_some());
63
- assert!(fnOf(&s, "World", "create").is_none(), "statics are not inherited");
64
- assert!(fnOf(&s, "World", "init").is_none());
65
- assert!(fnOf(&s, "World", "Stage__draw").is_some());
66
- let draw = fnOf(&s, "World", "draw").unwrap();
67
- assert!(draw.is_override);
68
- let FnBody::Expr(Expr { kind: ExprKind::Attribute(a), .. }) = &draw.body else { panic!("{:?}", draw.body) };
69
- let AttrKind::Method(c) = &a.attr else { panic!() };
70
- assert_eq!(c.name, "Stage__draw");
71
- assert!(matches!(a.object.kind, ExprKind::Self_));
72
- assert!(fnOf(&s, "Stage", "draw").is_some());
73
- }
74
-
75
- #[test]
76
- fn notReplacedKeepsPlainName() {
77
- let (s, errs) = lowerAll(&world(" fun run(self) -> Int = self.Stage.draw()\n"));
78
- assert!(errs.is_empty(), "{errs:?}");
79
- assert!(fnOf(&s, "World", "Stage__draw").is_none());
80
- assert!(fnOf(&s, "World", "draw").is_some());
81
- }
82
-
83
- #[test]
84
- fn embeddedRecordStillStandalone() {
85
- let (s, errs) = lowerAll(&world(""));
86
- assert!(errs.is_empty(), "{errs:?}");
87
- assert_eq!(
88
- enumOf(&s, "Stage").variants[0].field_names,
89
- vec!["camScale", "root", "camX"]
90
- );
91
- }
92
-
93
- #[test]
94
- fn hidesWithoutOverride() {
95
- let (_, errs) = lowerAll(&world(" fun draw(self) -> Int = 5\n"));
96
- assert_eq!(errs, vec!["World.draw hides Stage.draw; mark it override"]);
97
- }
98
-
99
- #[test]
100
- fn methodNeedsOverride() {
101
- let src = "struct Actor(x: Int) =\n fun act(self, dt: Float) = 0\n\nstruct Ship(Actor(x), tex: Int) =\n fun act(self, dt: Float) = 1\n";
102
- let (_, errs) = lowerAll(src);
103
- assert_eq!(errs, vec!["Ship.act hides Actor.act; mark it override"]);
104
- let ok = src.replace(" fun act(self, dt: Float) = 1", " override fun act(self, dt: Float) = 1");
105
- let (_, errs) = lowerAll(&ok);
106
- assert!(errs.is_empty(), "{errs:?}");
107
- }
108
-
109
- #[test]
110
- fn overrideNothing() {
111
- let (_, errs) = lowerAll(&world(" override fun zap(self) = 1\n"));
112
- assert_eq!(errs, vec!["World.zap overrides nothing"]);
113
- let (_, errs) = lowerAll("struct Plain(a: Int) =\n override fun zap(self) = 1\n");
114
- assert_eq!(errs, vec!["Plain.zap overrides nothing"]);
115
- }
116
-
117
- #[test]
118
- fn overrideOnFreeFnAndStatic() {
119
- let (_, errs) = lowerAll("override fun zap() = 1\n");
120
- assert_eq!(errs.len(), 1, "{errs:?}");
121
- let (_, errs) = lowerAll("struct Plain(a: Int) =\n override fun make() = 1\n");
122
- assert_eq!(errs.len(), 1, "{errs:?}");
123
- }
124
-
125
- #[test]
126
- fn baseMethodReplacement() {
127
- let src = "struct Actor(x: Int) =\n fun go(self) -> Int = 1\n\nstruct Ship(Actor(x), tex: Int) =\n override fun go(self) -> Int = self.Actor.go()\n";
128
- let (s, errs) = lowerAll(src);
129
- assert!(errs.is_empty(), "{errs:?}");
130
- assert!(fnOf(&s, "Ship", "Actor__go").is_some());
131
- let (_, errs) = lowerAll(&src.replace("override fun go", "fun go"));
132
- assert_eq!(errs, vec!["Ship.go hides Actor.go; mark it override"]);
133
- }
134
-
135
- #[test]
136
- fn deepBaseThroughEmbeddedStruct() {
137
- let src = "struct Actor(x: Int) =\n fun go(self) -> Int = 1\n\nstruct Mid(Actor(x), m: Int) =\n override fun go(self) -> Int = 2\n\nstruct Top(Mid(x, m), t: Int) =\n fun init(self) =\n self.u = 1\n";
138
- let (s, errs) = lowerAll(src);
139
- assert!(errs.is_empty(), "{errs:?}");
140
- let top = enumOf(&s, "Top");
141
- assert_eq!(top.variants[0].field_names, vec!["x", "m", "t", "u"]);
142
- assert_eq!(enumOf(&s, "Top").base(), Some("Mid"));
143
- assert_eq!(enumOf(&s, "Mid").base(), Some("Actor"));
144
- assert!(fnOf(&s, "Top", "go").is_some());
145
- assert!(fnOf(&s, "Top", "Actor__go").is_some());
146
- }
147
-
148
- #[test]
149
- fn recordChainOrder() {
150
- let src = "struct Aa(a: Int) =\n fun act(self) = 1\n\nstruct Bb(Aa(a), b: Int) =\n fun bact(self) = 1\n\nstruct Cc(Bb(a, b), c: Int) =\n fun init(self) =\n self.d = 4\n";
151
- let (s, errs) = lowerAll(src);
152
- assert!(errs.is_empty(), "{errs:?}");
153
- assert_eq!(enumOf(&s, "Cc").variants[0].field_names, vec!["a", "b", "c", "d"]);
154
- assert!(fnOf(&s, "Cc", "act").is_some() && fnOf(&s, "Cc", "bact").is_some());
155
- }
156
-
157
- #[test]
158
- fn unselectedHeaderFieldNeverSet() {
159
- let src = "struct Aa(a: Int, b: Int) =\n fun act(self) = 1\n\nstruct Cc(Aa(a), c: Int) =\n fun init(self) =\n self.d = 1\n";
160
- let (_, errs) = lowerAll(src);
161
- assert!(
162
- errs.iter().any(|e| e == "Cc field b is never set: pass it or set it in init"),
163
- "{errs:?}"
164
- );
165
- }
166
-
167
- #[test]
168
- fn secondEmbedIsAnError() {
169
- let src = "struct Aa(a: Int) =\n fun f(self) = 1\n\nstruct Bb(b: Int) =\n fun f(self) = 2\n\nstruct Cc(Aa(a), Bb(b), c: Int)\n";
170
- let (_, errs) = lowerAll(src);
171
- assert!(
172
- errs.contains(&"struct Cc embeds Aa and Bb; a struct can embed only one struct \u{2014} use a field (bb: Bb)".to_string()),
173
- "{errs:?}"
174
- );
175
- }
176
-
177
- #[test]
178
- fn embeddingANonStructIsAnError() {
179
- let (_, errs) = lowerAll("enum Color = Red | Blue\n\nstruct Cc(Color, c: Int)\n");
180
- assert!(errs.contains(&"type Cc cannot embed Color: it is not a struct".to_string()), "{errs:?}");
181
- }
182
-
183
- #[test]
184
- fn selfXNotEmbedded() {
185
- let (_, errs) = lowerAll(&world(" fun run(self) -> Int = self.Nope.draw()\n"));
186
- assert_eq!(errs, vec!["World.run calls self.Nope.draw, but World does not embed Nope"]);
187
- }
188
-
189
- #[test]
190
- fn genericRecordCannotBeEmbedded() {
191
- let src = "struct Box[T](v: T) =\n fun get(self) -> T = self.v\n\nstruct Cc(Box(v), c: Int)\n";
192
- let (_, errs) = lowerAll(src);
193
- assert!(errs.contains(&"cannot embed generic struct Box".to_string()), "{errs:?}");
194
- }
195
-
196
- #[test]
197
- fn idempotent() {
198
- let mut s = parseRaw(&world(" override fun draw(self) -> Int = self.Stage.draw()\n"));
199
- assert!(lowerEmbeds(&mut s).is_empty());
200
- let once = s.clone();
201
- assert!(lowerEmbeds(&mut s).is_empty());
202
- assert_eq!(once.items, s.items);
203
- }
204
-
205
- #[test]
206
- fn staticsNotCopied() {
207
- let (s, errs) = lowerAll(&world(""));
208
- assert!(errs.is_empty(), "{errs:?}");
209
- assert!(fnOf(&s, "World", "create").is_none());
210
- }
211
-
212
- const ABSTRACT: &str = "struct Actor(x: Int) =\n abstract fun draw(self) -> Int\n\n";
213
-
214
- #[test]
215
- fn abstractMethodNeedsOverride() {
216
- let src = format!("{ABSTRACT}struct Ship(Actor(x), tex: Int) =\n fun zap(self) = 1\n");
217
- let (_, errs) = lowerAll(&src);
218
- assert_eq!(errs, vec!["struct Ship embeds Actor but does not override abstract method draw"]);
219
- let ok = format!("{ABSTRACT}struct Ship(Actor(x), tex: Int) =\n override fun draw(self) -> Int = 1\n");
220
- let (s, errs) = lowerAll(&ok);
221
- assert!(errs.is_empty(), "{errs:?}");
222
- assert!(fnOf(&s, "Ship", "Actor__draw").is_none());
223
- }
224
-
225
- #[test]
226
- fn abstractStructCannotBeConstructed() {
227
- let src = format!("{ABSTRACT}fun make() -> Actor = Actor(x: 1)\n");
228
- let (_, errs) = lowerAll(&src);
229
- assert_eq!(errs, vec!["cannot construct Actor: draw has no body"]);
230
- }
compiler/core/tests/core/record_init_parse_test.rs CHANGED
@@ -25,21 +25,26 @@ fn enumOf<'a>(s: &'a Source, name: &str) -> &'a Enum {
25
25
  }
26
26
 
27
27
  #[test]
28
- fn header_selects_trait_fields() {
28
+ fn embedded_struct_fields_are_a_syntax_error() {
29
- let s = tryParse("struct Ship(Actor(x, y), tex: Int)\n").unwrap();
29
+ assert!(tryParse("struct Ship(Actor(x, y), tex: Int)\n").is_none());
30
- let e = enumOf(&s, "Ship");
31
- assert_eq!(
32
- e.structInfo().unwrap().embedFields,
33
- vec![("Actor".to_string(), vec!["x".to_string(), "y".to_string()])]
34
- );
35
- assert_eq!(e.structInfo().unwrap().embeds, vec!["Actor"]);
30
+ assert!(tryParse("struct Ship(Actor, tex: Int)\n").is_none());
36
- assert_eq!(e.variants[0].field_names, vec!["tex"]);
31
+ assert!(tryParse("struct A(B(x))\n").is_none());
37
32
  }
38
33
 
39
34
  #[test]
35
+ fn override_is_a_syntax_error() {
36
+ assert!(tryParse("struct Rr(a: Int) =\n override fun draw(self) = 1\n").is_none());
37
+ }
38
+
39
+ #[test]
40
+ fn abstract_is_a_syntax_error() {
41
+ assert!(tryParse("struct Aa(a: Int) =\n abstract fun draw(self) -> Int\n").is_none());
42
+ }
43
+
44
+ #[test]
40
- fn bare_trait_has_no_exposed_fields() {
45
+ fn a_struct_header_keeps_only_its_own_fields() {
41
- let s = tryParse("struct Ship(Actor, tex: Int)\n").unwrap();
46
+ let s = tryParse("struct Ship(x: Int, tex: Int)\n").unwrap();
42
- assert_eq!(enumOf(&s, "Ship").structInfo().unwrap().embedFields, vec![("Actor".to_string(), vec![])]);
47
+ assert_eq!(enumOf(&s, "Ship").variants[0].field_names, vec!["x", "tex"]);
43
48
  }
44
49
 
45
50
  #[test]
compiler/core/tests/core/record_init_test.rs CHANGED
@@ -1,7 +1,6 @@
1
1
  #![allow(non_snake_case)]
2
2
  use plum_core::ast::*;
3
3
  use plum_core::ctors::lowerCtors;
4
- use plum_core::embeds::lowerEmbeds;
5
4
 
6
5
  fn parseRaw(src: &str) -> Source {
7
6
  plum_test_support::parseRawSource(src)
@@ -9,8 +8,6 @@ fn parseRaw(src: &str) -> Source {
9
8
 
10
9
  fn lower(src: &str) -> (Source, Vec<String>) {
11
10
  let mut s = parseRaw(src);
12
- let te = lowerEmbeds(&mut s);
13
- assert!(te.is_empty(), "{te:?}");
14
11
  let e = lowerCtors(&mut s);
15
12
  (s, e)
16
13
  }
@@ -32,19 +29,14 @@ fn fnNamed<'a>(s: &'a Source, name: &str) -> Option<&'a Fn> {
32
29
  })
33
30
  }
34
31
 
35
- const ACTOR: &str = "struct Actor(x: Float, y: Float) =
36
- fun init(self) =
37
- self.width: Float = 1.0
38
- self.height: Float = 2.0
39
-
40
- ";
41
-
42
32
  fn ship(extra: &str) -> String {
43
33
  format!(
44
- "{ACTOR}struct Ship(Actor(x, y), tex: Int) =
34
+ "struct Ship(x: Float, y: Float, tex: Int) =
45
35
  fun init(self) =
36
+ self.width: Float = 1.0
37
+ self.height: Float = 2.0
46
38
  self.height = self.width
47
- self.vx = 0.0
39
+ self.vx: Float = 0.0
48
40
  {extra}
49
41
  fun main() =
50
42
  s := Ship(x: 1.0, y: 2.0, tex: 3)
@@ -57,9 +49,8 @@ fn ship_example_lowers() {
57
49
  let (s, e) = lower(&ship(""));
58
50
  assert!(e.is_empty(), "{e:?}");
59
51
  let en = enumOf(&s, "Ship");
60
- assert_eq!(en.variants[0].field_names, vec!["x", "y", "width", "height", "tex", "vx"]);
52
+ assert_eq!(en.variants[0].field_names, vec!["x", "y", "tex", "width", "height", "vx"]);
61
53
  assert_eq!(en.variants[0].fields.last().unwrap().name, "Float");
62
- assert!(en.structInfo().unwrap().embedFields.is_empty());
63
54
  let n = fnNamed(&s, "Ship__new").unwrap();
64
55
  let names: Vec<_> = n.params.iter().map(|p| p.name.as_str()).collect();
65
56
  assert_eq!(names, vec!["__p_x", "__p_y", "__p_tex"]);
@@ -76,12 +67,6 @@ fn unknown_constructor_parameter() {
76
67
  assert!(e.contains(&"Ship has no constructor parameter width".to_string()), "{e:?}");
77
68
  }
78
69
 
79
- #[test]
80
- fn never_set_field() {
81
- let (_, e) = lower("struct Aa(x: Int, color: Int)\n\nstruct Ship(Aa(x), tex: Int)\n");
82
- assert_eq!(e, vec!["Ship field color is never set: pass it or set it in init"]);
83
- }
84
-
85
70
  #[test]
86
71
  fn method_call_on_self_before_all_set() {
87
72
  let src = "struct Rec(a: Int) =
@@ -145,26 +130,6 @@ fn idempotent() {
145
130
  assert_eq!(s, before);
146
131
  }
147
132
 
148
- #[test]
149
- fn embedded_inits_run_down_the_chain_before_the_struct_init() {
150
- let src = "struct Aa(k0: Int) =
151
- fun init(self) =
152
- self.p: Int = 1
153
-
154
- struct Bb(Aa(k0)) =
155
- fun init(self) =
156
- self.q: Int = self.p + 1
157
-
158
- struct Rec(Bb(k0), k: Int) =
159
- fun init(self) =
160
- self.r: Int = self.q + 1
161
- ";
162
- let (s, e) = lower(src);
163
- assert!(e.is_empty(), "{e:?}");
164
- assert_eq!(enumOf(&s, "Rec").variants[0].field_names, vec!["k0", "p", "q", "k", "r"]);
165
- assert!(s.items.iter().all(|i| !matches!(i, Item::Fn(f) if f.name == "init")));
166
- }
167
-
168
133
  #[test]
169
134
  fn nested_construction_rewritten() {
170
135
  let src = format!(
@@ -203,11 +168,3 @@ fn named_args_map_by_name_in_any_order() {
203
168
  assert!(matches!(&c.args[0], Arg::Positional(Expr { kind: ExprKind::Int(2), .. })));
204
169
  assert!(matches!(&c.args[1], Arg::Positional(Expr { kind: ExprKind::Float(_), .. })));
205
170
  }
206
-
207
- #[test]
208
- fn embeddedInitRunsBetweenAndExposedFieldsWork() {
209
- let src = "struct Actor(width: Int, height: Int) =\n fun init(self) =\n self.area: Int = self.width * self.height\n\nstruct Ship(Actor(width, height), tex: Int) =\n fun init(self) =\n self.id = 7\n\nfun make() -> Ship = Ship(width: 1, height: 2, tex: 3)\n";
210
- let (s, errs) = lower(src);
211
- assert!(errs.is_empty(), "{errs:?}");
212
- assert!(fnNamed(&s, "Ship__new").is_some());
213
- }
compiler/core/tests/core/subtyping_test.rs DELETED
@@ -1,43 +0,0 @@
1
- #![allow(non_snake_case)]
2
- use plum_core::subtyping::*;
3
-
4
- const SRC: &str = "struct Actor(x: Int) =\n fun draw(self) -> Int = 1\nstruct Ship(Actor(x), s: Int) =\n override fun draw(self) -> Int = 2\nstruct Boss(Ship(x, s), b: Int)\n";
5
-
6
- #[test]
7
- fn subtypeChain() {
8
- let s = plum_test_support::parseRawSource(SRC);
9
- assert!(isSubtype(&s, "Ship", "Actor"));
10
- assert!(isSubtype(&s, "Boss", "Actor"));
11
- assert!(isSubtype(&s, "Boss", "Ship"));
12
- assert!(isSubtype(&s, "Ship", "Ship"));
13
- assert!(!isSubtype(&s, "Actor", "Ship"));
14
- }
15
-
16
- #[test]
17
- fn overridersCountOwnOverrides() {
18
- let s = plum_test_support::parseRawSource(SRC);
19
- assert_eq!(overriders(&s, "Actor", "draw"), vec!["Ship"]);
20
- let v = virtualMethods(&s);
21
- assert_eq!(v.len(), 1);
22
- assert!(v.contains(&("Actor".to_string(), "draw".to_string())));
23
- }
24
-
25
- #[test]
26
- fn deepestFirst() {
27
- let src = format!("{}struct Deep(Boss(x, s, b)) =\n override fun draw(self) -> Int = 3\n", SRC);
28
- let s = plum_test_support::parseRawSource(&src);
29
- assert_eq!(overriders(&s, "Actor", "draw"), vec!["Deep", "Ship"]);
30
- }
31
-
32
- #[test]
33
- fn secondEmbedIsNotABase() {
34
- let s = plum_test_support::parseRawSource("struct Aa(x: Int)\nstruct Bb(y: Int)\nstruct Cc(Aa(x), Bb(y))\n");
35
- assert!(isSubtype(&s, "Cc", "Aa"));
36
- assert!(!isSubtype(&s, "Cc", "Bb"));
37
- }
38
-
39
- #[test]
40
- fn genericBaseIsNoBase() {
41
- let s = plum_test_support::parseRawSource("struct Gen[T](x: T)\nstruct Hh(Gen(x))\n");
42
- assert!(!isSubtype(&s, "Hh", "Gen"));
43
- }
compiler/core/tests/core/uniqueness_test.rs CHANGED
@@ -341,23 +341,11 @@ fn mixed_unique_and_shared_calls_say_so() {
341
341
  }
342
342
 
343
343
  #[test]
344
- fn override_storing_self_makes_the_method_name_not_unique() {
344
+ fn a_method_storing_self_is_not_proved_in_place() {
345
- let s = "struct Base(label: Str, n: Int) =\n fun step(self) -> Base = Base(..self, n: self.n + 1)\n\
346
- struct Sub(Base(label, n), log: Slice[Base]) =\n override fun step(self) -> Base =\n self.log.push(self)\n Base(label: self.label, n: self.n + 1)\n\
345
+ let s = "struct Stored(label: Str, n: Int, log: Slice[Stored]) =\n fun step(self) -> Stored =\n self.log.push(self)\n Stored(..self, n: self.n + 1)\n\
347
- fun go(b: Base) -> Int =\n c := b.step()\n b.n + c.n\n";
346
+ fun go(b: Stored) -> Int =\n c := b.step()\n b.n + c.n\n";
348
347
  let (_, r) = run(s);
349
- assert!(!provedFn(&r, "Base.step"), "{r:?}");
348
+ assert!(!provedFn(&r, "Stored.step"), "{r:?}");
350
- }
351
-
352
- #[test]
353
- fn override_storing_self_with_a_unique_receiver_stays_a_copy_of_the_stored_value() {
354
- // The receiver is dead after the call, so Base.step may write in place; Sub.step keeps
355
- // `self` in its log, and the log is the only holder left.
356
- let s = "struct Base(label: Str, n: Int) =\n fun step(self) -> Base = Base(..self, n: self.n + 1)\n\
357
- struct Sub(Base(label, n), log: Slice[Base]) =\n override fun step(self) -> Base =\n self.log.push(self)\n Base(label: self.label, n: self.n + 1)\n\
358
- fun go() -> Base =\n b := Base(label: \"a\", n: 0)\n b.step()\n";
359
- let (_, r) = run(s);
360
- assert!(!provedFn(&r, "Sub.step"), "{r:?}");
361
349
  }
362
350
 
363
351
  #[test]
compiler/lsp/src/completion.rs CHANGED
@@ -26,7 +26,6 @@ use tower_lsp::lsp_types::*;
26
26
 
27
27
  const KEYWORDS: &[&str] = &[
28
28
  "fun",
29
- "override",
30
29
  "fn",
31
30
  "crash",
32
31
  "struct",
@@ -36,7 +35,6 @@ const KEYWORDS: &[&str] = &[
36
35
  "module",
37
36
  "extern",
38
37
  "export",
39
- "abstract",
40
38
  "fip",
41
39
  "if",
42
40
  "then",
@@ -162,8 +160,7 @@ pub fn staticItems(source: &ast::Source, owner: &str) -> Vec<CompletionItem> {
162
160
  items
163
161
  }
164
162
 
165
- /// Constructor parameters (name, type text) of struct `owner`: the
163
+ /// Constructor parameters (name, type text) of struct `owner`: its own fields.
166
- /// embedded-struct fields its header selects (`Actor(x, y)`) followed by its own fields.
167
164
  /// Fields set by `init` are not constructor parameters. `own` is the unlowered
168
165
  /// parse of the document (header intact); `merged` is the fallback.
169
166
  pub fn ctorParams(own: &ast::Source, merged: &ast::Source, owner: &str) -> Vec<(String, String)> {
@@ -176,23 +173,6 @@ pub fn ctorParams(own: &ast::Source, merged: &ast::Source, owner: &str) -> Vec<(
176
173
  let Some(en) = find(own).or_else(|| find(merged)) else { return Vec::new() };
177
174
  let v = &en.variants[0];
178
175
  let mut out = Vec::new();
179
- for (tname, exposed) in en.embedFields() {
180
- for f in exposed {
181
- let ty = [own, merged]
182
- .into_iter()
183
- .find_map(|src| {
184
- src.items.iter().find_map(|i| match i {
185
- ast::Item::Enum(e) if &e.name == tname => e.variants.first().and_then(|v| {
186
- let k = v.field_names.iter().position(|n| n == f)?;
187
- v.fields.get(k).map(typeText)
188
- }),
189
- _ => None,
190
- })
191
- })
192
- .unwrap_or_default();
193
- out.push((f.clone(), ty));
194
- }
195
- }
196
176
  for (i, name) in v.field_names.iter().enumerate() {
197
177
  out.push((name.clone(), v.fields.get(i).map(typeText).unwrap_or_default()));
198
178
  }
@@ -646,8 +626,6 @@ mod tests {
646
626
  is_extern: false,
647
627
  is_export: false,
648
628
  is_test: false,
649
- is_override: false,
650
- is_abstract: false,
651
629
  is_fip: false,
652
630
  has_self: false,
653
631
  params: vec![],
compiler/lsp/src/main.rs CHANGED
@@ -207,7 +207,6 @@ impl LanguageServer for Backend {
207
207
  ..Default::default()
208
208
  }),
209
209
  hover_provider: Some(HoverProviderCapability::Simple(true)),
210
- definition_provider: Some(OneOf::Left(true)),
211
210
  ..Default::default()
212
211
  },
213
212
  server_info: Some(ServerInfo {
@@ -255,43 +254,16 @@ impl LanguageServer for Backend {
255
254
 
256
255
  async fn hover(&self, params: HoverParams) -> RpcResult<Option<Hover>> {
257
256
  let p = params.text_document_position_params;
258
- let Ok(path) = p.text_document.uri.to_file_path() else {
259
- return Ok(None);
260
- };
261
257
  let Some(text) = self.docs.lock().await.get(&p.text_document.uri).cloned() else {
262
258
  return Ok(None);
263
259
  };
264
- Ok(hoverAt(&path, &self.libPath, &text, p.position.line as usize, p.position.character as usize)
260
+ Ok(hoverAt(&text, p.position.line as usize, p.position.character as usize)
265
261
  .map(|s| Hover {
266
262
  contents: HoverContents::Scalar(MarkedString::String(s)),
267
263
  range: None,
268
264
  }))
269
265
  }
270
266
 
271
- async fn goto_definition(
272
- &self,
273
- params: GotoDefinitionParams,
274
- ) -> RpcResult<Option<GotoDefinitionResponse>> {
275
- let p = params.text_document_position_params;
276
- let Ok(path) = p.text_document.uri.to_file_path() else {
277
- return Ok(None);
278
- };
279
- let Some(text) = self.docs.lock().await.get(&p.text_document.uri).cloned() else {
280
- return Ok(None);
281
- };
282
- let Some((file, l, c)) = definitionAt(&path, &self.libPath, &text, p.position.line as usize, p.position.character as usize) else {
283
- return Ok(None);
284
- };
285
- let Ok(uri) = Url::from_file_path(&file) else {
286
- return Ok(None);
287
- };
288
- let pos = Position::new(l, c);
289
- Ok(Some(GotoDefinitionResponse::Scalar(Location {
290
- uri,
291
- range: Range { start: pos, end: pos },
292
- })))
293
- }
294
-
295
267
  async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
296
268
  let uri = params.text_document_position.text_document.uri;
297
269
  let pos = params.text_document_position.position;
@@ -389,95 +361,6 @@ fn memberCompletionFor(
389
361
  items
390
362
  }
391
363
 
392
- /// What the cursor is on, for hover / go-to-definition.
393
- enum Target {
394
- /// `recv.name` copied into the receiver's record from an embedded record `from`
395
- /// (`struct World(Stage(camScale), ...)`). `loc` is the original's file, 1-based
396
- /// line and column, for methods; fields point at the embedded record's name.
397
- Inherited { from: String, name: String, loc: Option<(String, usize, usize)> },
398
- }
399
-
400
- /// The embedded record (searched through `owner`'s embed chain) that `owner`'s
401
- /// field or method `word` was copied from, if it was inherited.
402
- fn inheritedFrom(
403
- merged: &ast::Source,
404
- path: &Path,
405
- canonText: &(PathBuf, String),
406
- owner: &str,
407
- word: &str,
408
- ) -> Option<Target> {
409
- // The merged program has its embed lists lowered away; read the headers from
410
- // the unlowered sources (the entry buffer, then every file that declares a fn).
411
- let mut files: Vec<String> = vec![path.to_string_lossy().into_owned()];
412
- for i in &merged.items {
413
- if let ast::Item::Fn(f) = i {
414
- if !files.contains(&f.file) {
415
- files.push(f.file.clone());
416
- }
417
- }
418
- }
419
- let mut headers: HashMap<String, Vec<String>> = HashMap::new();
420
- for file in files {
421
- let p = PathBuf::from(&file);
422
- let content = if std::fs::canonicalize(&p).unwrap_or(p.clone()) == canonText.0 {
423
- canonText.1.clone()
424
- } else {
425
- match std::fs::read_to_string(&p) {
426
- Ok(c) => c,
427
- Err(_) => continue,
428
- }
429
- };
430
- let tree = Backend::parseTree(&content);
431
- let src = plum_core::AstParser::new(&content).parseSourceRaw(tree.root_node());
432
- for i in &src.items {
433
- if let ast::Item::Enum(e) = i {
434
- if e.isStruct() {
435
- headers.insert(e.name.clone(), e.structInfo().map(|i| i.embedFields.iter().map(|(n, _)| n.clone()).collect()).unwrap_or_default());
436
- }
437
- }
438
- }
439
- }
440
- let mut chain: Vec<String> = Vec::new();
441
- let mut todo: Vec<String> = headers.get(owner)?.clone();
442
- while !todo.is_empty() {
443
- let n = todo.remove(0);
444
- if headers.contains_key(&n) && !chain.contains(&n) {
445
- todo.extend(headers[&n].clone());
446
- chain.push(n);
447
- }
448
- }
449
- let method = |t: &str| {
450
- merged.items.iter().find_map(|i| match i {
451
- ast::Item::Fn(f) if f.type_param.as_deref() == Some(t) && f.name == word => Some(f),
452
- _ => None,
453
- })
454
- };
455
- let mine = method(owner);
456
- for name in &chain {
457
- let Some(e) = merged.items.iter().find_map(|i| match i {
458
- ast::Item::Enum(e) if e.name == *name && e.isStruct() => Some(e),
459
- _ => None,
460
- }) else {
461
- continue;
462
- };
463
- if let (Some(m), Some(orig)) = (mine, method(name)) {
464
- if m.file == orig.file && m.line == orig.line && m.column == orig.column {
465
- return Some(Target::Inherited {
466
- from: name.clone(),
467
- name: word.to_string(),
468
- loc: Some((orig.file.clone(), orig.line, orig.column)),
469
- });
470
- }
471
- }
472
- if mine.is_none()
473
- && e.variants.first().is_some_and(|v| v.field_names.iter().any(|n| n == word))
474
- {
475
- return Some(Target::Inherited { from: name.clone(), name: word.to_string(), loc: None });
476
- }
477
- }
478
- None
479
- }
480
-
481
364
  fn identAt(line: &str, col: usize) -> Option<(usize, usize)> {
482
365
  let b = line.as_bytes();
483
366
  let isId = |c: u8| c.is_ascii_alphanumeric() || c == b'_';
@@ -523,64 +406,8 @@ fn mergedFn<'a>(merged: &'a ast::Source, own: Option<&'a ast::Fn>) -> Option<&'a
523
406
  .or(Some(o))
524
407
  }
525
408
 
526
- fn text_override(path: &Path, text: &str) -> (PathBuf, String) {
527
- (std::fs::canonicalize(path).unwrap_or(path.to_path_buf()), text.to_string())
528
- }
529
-
530
- fn resolveTarget(
531
- path: &Path,
532
- libPath: &Path,
533
- text: &str,
534
- line0: usize,
535
- col: usize,
536
- ) -> Option<Target> {
537
- let lineText = text.lines().nth(line0)?;
538
- let (s, e) = identAt(lineText, col)?;
539
- let word = &lineText[s..e];
540
- let canon = std::fs::canonicalize(path).unwrap_or(path.to_path_buf());
541
- let mut overrides = HashMap::new();
542
- overrides.insert(canon, text.to_string());
543
- let merged = plum_core::loadAndMergeWithOverridesDiag(path, libPath, &overrides).ok()?;
544
- if s > 0 && lineText.as_bytes()[s - 1] == b'.' {
545
- let before = &lineText[..s - 1];
546
- let rs = before
547
- .rfind(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
548
- .map(|i| i + 1)
549
- .unwrap_or(0);
550
- let receiver = &before[rs..];
551
- let tree = Backend::parseTree(text);
552
- let own = plum_core::AstParser::new(text).parseSourceRaw(tree.root_node());
553
- let (global_env, methods, enum_variants) = plum_checker::buildGlobalTables(&merged);
554
- let enum_params = plum_checker::EnumParams::new();
555
- let min_required = plum_checker::buildMinRequiredArgs(&merged);
556
- let primitive_generics = plum_checker::buildPrimitiveGenerics(&merged);
557
- let statics = plum_checker::buildStaticMethods(&merged);
558
- let ctx = plum_checker::CheckCtx {
559
- methods: &methods,
560
- enum_variants: &enum_variants,
561
- enum_params: &enum_params,
562
- min_required: &min_required,
563
- primitive_generics: &primitive_generics,
564
- statics: Some(&statics),
565
- };
566
- let f = mergedFn(&merged, completion::enclosingFn(&own, line0 + 1));
567
- let ty = completion::receiverType(f, receiver, &global_env, &ctx)?;
568
- let owner = plum_checker::methodReceiverName(&ty)?;
569
- if let Some(t) = inheritedFrom(&merged, path, &text_override(path, text), &owner, word) {
570
- return Some(t);
571
- }
572
- return None;
573
- }
574
- None
575
- }
576
-
577
- fn hoverAt(path: &Path, libPath: &Path, text: &str, line0: usize, col: usize) -> Option<String> {
409
+ fn hoverAt(text: &str, line0: usize, col: usize) -> Option<String> {
578
- if let Some(h) = recordHover(text, line0, col) {
410
+ recordHover(text, line0, col)
579
- return Some(h);
580
- }
581
- match resolveTarget(path, libPath, text, line0, col)? {
582
- Target::Inherited { from, name, .. } => Some(format!("{name} (from {from})")),
583
- }
584
411
  }
585
412
 
586
413
  /// Hover on a struct/record name in this document: its constructor parameters.
@@ -601,98 +428,6 @@ fn recordHover(text: &str, line0: usize, col: usize) -> Option<String> {
601
428
  Some(format!("{word}({})", list.join(", ")))
602
429
  }
603
430
 
604
- /// Where an inherited method (or, for a field, the embedded struct) is declared.
605
- fn definitionAt(
606
- path: &Path,
607
- libPath: &Path,
608
- text: &str,
609
- line0: usize,
610
- col: usize,
611
- ) -> Option<(PathBuf, u32, u32)> {
612
- match resolveTarget(path, libPath, text, line0, col)? {
613
- Target::Inherited { from, name, loc } => {
614
- inheritedDefinition(path, libPath, text, &from, &name, loc.as_ref())
615
- }
616
- }
617
- }
618
-
619
- /// Where an inherited method (or, for a field, the embedded record) is declared.
620
- fn inheritedDefinition(
621
- path: &Path,
622
- libPath: &Path,
623
- text: &str,
624
- from: &str,
625
- name: &str,
626
- loc: Option<&(String, usize, usize)>,
627
- ) -> Option<(PathBuf, u32, u32)> {
628
- let canon = std::fs::canonicalize(path).unwrap_or(path.to_path_buf());
629
- let mut overrides = HashMap::new();
630
- overrides.insert(canon.clone(), text.to_string());
631
- let merged = plum_core::loadAndMergeWithOverridesDiag(path, libPath, &overrides).ok()?;
632
- let (file, line, needle) = match loc {
633
- Some((f, l, _)) => (f.clone(), *l, format!("fun {name}")),
634
- None => {
635
- let l = merged.items.iter().find_map(|i| match i {
636
- ast::Item::Enum(e) if e.name == from && e.isStruct() => Some(e.line),
637
- _ => None,
638
- })?;
639
- return recordNameAt(&merged, path, &canon, text, from, l);
640
- }
641
- };
642
- let mut p = PathBuf::from(&file);
643
- let content = if std::fs::canonicalize(&p).unwrap_or(p.clone()) == canon {
644
- p = path.to_path_buf();
645
- text.to_string()
646
- } else {
647
- std::fs::read_to_string(&p).ok()?
648
- };
649
- let ln = content.lines().nth(line.checked_sub(1)?)?;
650
- let at = ln.find(&needle)? + "fun ".len();
651
- Some((p, line as u32 - 1, at as u32))
652
- }
653
-
654
- /// The `struct <from>` declaration line (searched in the entry file, then imports).
655
- fn recordNameAt(
656
- merged: &ast::Source,
657
- path: &Path,
658
- canon: &Path,
659
- text: &str,
660
- from: &str,
661
- _line: usize,
662
- ) -> Option<(PathBuf, u32, u32)> {
663
- let mut files: Vec<String> = vec![path.to_string_lossy().into_owned()];
664
- for i in &merged.items {
665
- if let ast::Item::Fn(f) = i {
666
- if !files.contains(&f.file) {
667
- files.push(f.file.clone());
668
- }
669
- }
670
- }
671
- let headers = [format!("struct {from}")];
672
- for file in files {
673
- let p = PathBuf::from(&file);
674
- let content = if std::fs::canonicalize(&p).unwrap_or(p.clone()) == canon {
675
- text.to_string()
676
- } else {
677
- match std::fs::read_to_string(&p) {
678
- Ok(c) => c,
679
- Err(_) => continue,
680
- }
681
- };
682
- for (n, l) in content.lines().enumerate() {
683
- for header in &headers {
684
- if let Some(c) = l.find(header.as_str()) {
685
- let end = c + header.len();
686
- if l[end..].chars().next().map_or(true, |ch| !(ch.is_ascii_alphanumeric() || ch == '_')) {
687
- return Some((p, n as u32, (c + "struct ".len()) as u32));
688
- }
689
- }
690
- }
691
- }
692
- }
693
- None
694
- }
695
-
696
431
  #[tokio::main]
697
432
  async fn main() {
698
433
  // `--lib-path` mirrors `plum-cli`'s own required flag (see
@@ -728,36 +463,16 @@ mod tests {
728
463
  PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
729
464
  }
730
465
 
731
- // `Entity` is a base struct; `House` embeds it and is its subtype.
732
- const TRAIT_SRC: &str = "module t\nimport std/Int\nimport std/Bool\n\nstruct Vec2(x: Int, y: Int)\n\nstruct Entity(pos: Vec2, hp: Int) =\n fun alive(self) -> Bool = self.hp > 0\n\nstruct House(Entity(pos, hp), rooms: Int)\n\nfun main() =\n h := House(pos: Vec2(x: 1, y: 2), hp: 5, rooms: 3)\n h.pos\n e: Entity = h\n e.zzzzzz\n";
733
-
734
- fn traitFile(tag: &str) -> PathBuf {
735
- let dir = std::env::temp_dir().join(format!("plum-lsp-trait-{}-{}", tag, std::process::id()));
736
- std::fs::create_dir_all(&dir).unwrap();
737
- let path = dir.join("t.plum");
738
- std::fs::write(&path, TRAIT_SRC).unwrap();
739
- path
740
- }
741
-
742
- #[test]
743
- fn hover_embedded_field() {
744
- let path = traitFile("hover");
745
- // line 12 (0-based 11): ` h.pos`, cursor on `pos`.
746
- let h = hoverAt(&path, &repoRoot(), TRAIT_SRC, 13, 5).expect("hover");
747
- assert!(h.contains("pos (from Entity)"), "{h}");
748
- }
749
-
750
466
  #[test]
751
467
  fn hover_record_shows_ctor_params() {
752
- let src = "module t\n\nstruct Actor(x: Int, y: Int, hp: Int)\n\nstruct Ship(Actor(x, y), tex: Int) =\n fun init(self) =\n self.z: Int = 1\n";
468
+ let src = "module t\n\nstruct Ship(x: Int, y: Int, tex: Int) =\n fun init(self) =\n self.z: Int = 1\n";
753
- let path = traitFile("hoverrec");
754
- let h = hoverAt(&path, &repoRoot(), src, 4, 8).expect("hover");
469
+ let h = hoverAt(src, 2, 8).expect("hover");
755
470
  assert_eq!(h, "Ship(x: Int, y: Int, tex: Int)");
756
471
  }
757
472
 
758
473
  #[test]
759
474
  fn complete_ctor_offers_only_ctor_params() {
760
- let src = "module t\n\nstruct Actor(x: Int, y: Int, hp: Int)\n\nstruct Ship(Actor(x, y), tex: Int)\n";
475
+ let src = "module t\n\nstruct Ship(x: Int, y: Int, tex: Int)\n";
761
476
  let tree = Backend::parseTree(src);
762
477
  let ast = plum_core::AstParser::new(src).parseSourceRaw(tree.root_node());
763
478
  let labels: Vec<String> = completion::ctorArgItems(&ast, &ast, "Ship").into_iter().map(|i| i.label).collect();
@@ -766,51 +481,6 @@ mod tests {
766
481
  assert_eq!(completion::enclosingCtor(" f(Vec(x: 1), "), None);
767
482
  }
768
483
 
769
- #[test]
770
- fn complete_base_value() {
771
- let path = traitFile("complete");
772
- let items = memberCompletionFor(&path, &repoRoot(), TRAIT_SRC, 16, "e");
773
- let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect();
774
- for want in ["pos", "hp", "alive"] {
775
- assert!(labels.contains(&want), "missing {want} in {labels:?}");
776
- }
777
- assert!(!labels.iter().any(|l| l.starts_with("__")), "{labels:?}");
778
- let rec = memberCompletionFor(&path, &repoRoot(), TRAIT_SRC, 14, "h");
779
- let rl: Vec<&str> = rec.iter().map(|i| i.label.as_str()).collect();
780
- for want in ["pos", "hp", "rooms", "alive"] {
781
- assert!(rl.contains(&want), "missing {want} in {rl:?}");
782
- }
783
- assert!(!rl.iter().any(|l| l.starts_with("__")), "{rl:?}");
784
- }
785
-
786
- #[test]
787
- fn goto_embedded_field() {
788
- let path = traitFile("goto");
789
- let (f, l, c) = definitionAt(&path, &repoRoot(), TRAIT_SRC, 13, 5).expect("def");
790
- assert_eq!(f, path);
791
- assert_eq!((l, c), (6, 7), "`Entity` in `struct Entity(pos: ...` (fields point at the embedded struct)");
792
- }
793
-
794
- const EMBED_SRC: &str = "module t\nimport std/Int\n\nstruct Stage(camScale: Int) =\n fun init(self) =\n self.camX: Int = 0\n\n fun act(self, dt: Int) =\n self.camX = self.camX + dt\n\nstruct World(Stage(camScale), score: Int)\n\nfun main() =\n w := World(camScale: 2, score: 1)\n w.act(1)\n x := w.camX\n";
795
-
796
- #[test]
797
- fn hover_inherited_method() {
798
- let path = traitFile("hoverinh");
799
- // line 14 (0-based): ` w.act(1)`, cursor on `act`.
800
- let h = hoverAt(&path, &repoRoot(), EMBED_SRC, 14, 5).expect("hover");
801
- assert!(h.contains("from Stage"), "{h}");
802
- let f = hoverAt(&path, &repoRoot(), EMBED_SRC, 15, 11).expect("hover field");
803
- assert!(f.contains("from Stage"), "{f}");
804
- }
805
-
806
- #[test]
807
- fn goto_inherited_method() {
808
- let path = traitFile("gotoinh");
809
- let (f, l, c) = definitionAt(&path, &repoRoot(), EMBED_SRC, 14, 5).expect("def");
810
- assert_eq!(f, path);
811
- assert_eq!((l, c), (7, 6), "act in ` fun act(self, ...` of Stage");
812
- }
813
-
814
484
  /// The whole diagnostic path (`publishAll`: syntax diagnostics, then the
815
485
  /// two-pass checker) must find nothing wrong in the sequences example.
816
486
  #[test]
@@ -1033,7 +703,6 @@ mod tests {
1033
703
  "then",
1034
704
  "true",
1035
705
  "false",
1036
- "override",
1037
706
  ] {
1038
707
  assert!(labels.iter().any(|l| l == want), "missing {want}");
1039
708
  }
@@ -1048,6 +717,8 @@ mod tests {
1048
717
  "indexed",
1049
718
  "case",
1050
719
  "trait",
720
+ "override",
721
+ "abstract",
1051
722
  "implements",
1052
723
  "error",
1053
724
  "catch",
compiler/tooling/tree-sitter-plum/grammar.js CHANGED
@@ -162,29 +162,13 @@ module.exports = grammar({
162
162
  ),
163
163
 
164
164
  // `struct Vec2(x: Float, y: Float)` / `struct Pair[K, V](key: K, val: V)`:
165
- // the one product type (generics, embedding, `mut` fields, no/empty
165
+ // the one product type (generics, `mut` fields, no/empty
166
166
  // parens); the compiler picks the layout.
167
167
  // Optional `=` body holds consts and methods.
168
168
  struct: ($) => productBody($, "struct"),
169
169
 
170
- // Constructor fields, optionally led by embedded structs: `(E, C, k: Int)`,
170
+ // Constructor fields: `(x: Int, k: Int)`.
171
- // `(E)`, `(k: Int)`. Embeds always come first; `(k: Int, E)` is a syntax error.
172
- _ctor_fields: ($) =>
173
- choice(
174
- seq(
175
- commaSep1(field("embed", $.embed_spec)),
176
- optional(seq(",", commaSep1(field("field", alias($.ctor_field, $.enum_named_field))))),
171
+ _ctor_fields: ($) => commaSep1(field("field", alias($.ctor_field, $.enum_named_field))),
177
- ),
178
- commaSep1(field("field", alias($.ctor_field, $.enum_named_field))),
179
- ),
180
-
181
- // A struct embedded in a struct header, optionally selecting its exposed fields:
182
- // `Actor` or `Actor(x, y)`.
183
- embed_spec: ($) =>
184
- seq(
185
- field("name", $.type_identifier),
186
- optional(seq("(", commaSep1(field("exposed", $.var_identifier)), ")")),
187
- ),
188
172
 
189
173
  // `primitive Name =` + consts, statics and (for a compiler built-in such as
190
174
  // `Int`, only in std) methods: declares a built-in's API, else a stateless
@@ -248,7 +232,7 @@ module.exports = grammar({
248
232
  // error either way), not the grammar, to keep this rule simple.
249
233
  // `export fun foo(...) = ...` is a normal function that is also exported from
250
234
  // the wasm module under its own name, with the `extern` boundary ABI.
251
- optional(choice(field("externKw", "extern"), field("exportKw", "export"), field("overrideKw", "override"), field("abstractKw", "abstract"))),
235
+ optional(choice(field("externKw", "extern"), field("exportKw", "export"))),
252
236
  // `fip fun`: a compile-time guarantee that the body's updates run in place.
253
237
  optional(field("fipKw", "fip")),
254
238
  field("kw", "fun"),
@@ -806,7 +790,7 @@ function sep1(rule, separator) {
806
790
  return seq(rule, repeat(seq(separator, rule)));
807
791
  }
808
792
  // Shape of `struct`: name, generics, optional constructor
809
- // fields (embeds first), optional `=` body of methods and consts.
793
+ // fields, optional `=` body of methods and consts.
810
794
  function productBody($, keyword) {
811
795
  return seq(
812
796
  keyword,
compiler/tooling/tree-sitter-plum/queries/plum/format.scm CHANGED
@@ -128,9 +128,7 @@
128
128
 
129
129
  (fn "extern" @append_space)
130
130
 
131
- (fn "override" @append_space)
132
131
 
133
- (fn "abstract" @append_space)
134
132
 
135
133
  (fn "fip" @append_space)
136
134
 
compiler/tooling/tree-sitter-plum/queries/plum/highlights.scm CHANGED
@@ -93,8 +93,6 @@
93
93
  "fun"
94
94
  "extern"
95
95
  "export"
96
- "override"
97
- "abstract"
98
96
  "fip"
99
97
  "fn"
100
98
  "test"
compiler/tooling/tree-sitter-plum/src/grammar.json CHANGED
Binary file
compiler/tooling/tree-sitter-plum/src/node-types.json CHANGED
Binary file
compiler/tooling/tree-sitter-plum/src/parser.c CHANGED
Binary file
compiler/tooling/tree-sitter-plum/test/corpus/fip.txt CHANGED
@@ -20,11 +20,11 @@ fip fun step(n: Int) -> Int = n
20
20
  (var_identifier)))))
21
21
 
22
22
  ================================================================================
23
- override fip fun in a struct body
23
+ fip fun in a struct body
24
24
  ================================================================================
25
25
 
26
26
  struct P(x: Int, fip: Int) =
27
- override fip fun size(self) -> Int = self.fip
27
+ fip fun size(self) -> Int = self.fip
28
28
 
29
29
  --------------------------------------------------------------------------------
30
30
 
compiler/tooling/tree-sitter-plum/test/corpus/function.txt CHANGED
@@ -465,7 +465,8 @@ fun main() -> Int =
465
465
  (var_identifier)))))))))))))
466
466
 
467
467
  ================================================================================
468
- override fun
468
+ override is removed
469
+ :error
469
470
  ================================================================================
470
471
 
471
472
  struct Rr(a: Int) =
@@ -473,22 +474,11 @@ struct Rr(a: Int) =
473
474
 
474
475
  --------------------------------------------------------------------------------
475
476
 
476
- (source
477
- (struct
478
- (type_identifier)
479
- (enum_named_field
480
- (var_identifier)
481
- (type
482
- (type_identifier)))
483
- (fn
484
- (fn_identifier)
485
- (self)
477
+ (ERROR)
486
- (expression
487
- (primary_expression
488
- (integer))))))
489
478
 
490
479
  ================================================================================
491
- abstract fun
480
+ abstract is removed
481
+ :error
492
482
  ================================================================================
493
483
 
494
484
  struct Aa(a: Int) =
@@ -496,18 +486,7 @@ struct Aa(a: Int) =
496
486
 
497
487
  --------------------------------------------------------------------------------
498
488
 
499
- (source
500
- (struct
501
- (type_identifier)
502
- (enum_named_field
503
- (var_identifier)
504
- (type
505
- (type_identifier)))
506
- (fn
507
- (fn_identifier)
508
- (self)
489
+ (ERROR)
509
- (type
510
- (type_identifier)))))
511
490
 
512
491
  ================================================================================
513
492
  function - match body
compiler/tooling/tree-sitter-plum/test/corpus/struct_unified.txt CHANGED
@@ -17,22 +17,15 @@ struct Box[T](value: T)
17
17
  (type_identifier)))))
18
18
 
19
19
  ================================================================================
20
- struct with embedding
20
+ embedding is removed
21
+ :error
21
22
  ================================================================================
22
23
 
23
24
  struct House(Actor, k: Int)
24
25
 
25
26
  --------------------------------------------------------------------------------
26
27
 
27
- (source
28
+ (ERROR)
28
- (struct
29
- (type_identifier)
30
- (embed_spec
31
- (type_identifier))
32
- (enum_named_field
33
- (var_identifier)
34
- (type
35
- (type_identifier)))))
36
29
 
37
30
  ================================================================================
38
31
  struct with mut field and methods
compiler/tooling/tree-sitter-plum/test/corpus/trait.txt CHANGED
@@ -11,46 +11,26 @@ trait X(a: Int) =
11
11
  (ERROR)
12
12
 
13
13
  ================================================================================
14
- struct with embeds
14
+ embeds are removed
15
+ :error
15
16
  ================================================================================
16
17
 
17
18
  struct House(Ent, Cl, k: Int)
18
19
 
19
20
  --------------------------------------------------------------------------------
20
21
 
21
- (source
22
+ (ERROR)
22
- (struct
23
- (type_identifier)
24
- (embed_spec
25
- (type_identifier))
26
- (embed_spec
27
- (type_identifier))
28
- (enum_named_field
29
- (var_identifier)
30
- (type
31
- (type_identifier)))))
32
23
 
33
24
  ================================================================================
34
- struct header selects embedded fields
25
+ embed with exposed fields is removed
26
+ :error
35
27
  ================================================================================
36
28
 
37
29
  struct Ship(Actor(x, y), Body, tex: Int)
38
30
 
39
31
  --------------------------------------------------------------------------------
40
32
 
41
- (source
33
+ (ERROR)
42
- (struct
43
- (type_identifier)
44
- (embed_spec
45
- (type_identifier)
46
- (var_identifier)
47
- (var_identifier))
48
- (embed_spec
49
- (type_identifier))
50
- (enum_named_field
51
- (var_identifier)
52
- (type
53
- (type_identifier)))))
54
34
 
55
35
  ================================================================================
56
36
  typed self field assignment
compiler/tooling/vscode-plum/syntaxes/plum.tmLanguage.json CHANGED
@@ -39,7 +39,7 @@
39
39
  "keywords": {
40
40
  "patterns": [
41
41
  {
42
- "match": "\\b(module|struct|enum|trait|primitive|fun|fn|extern|export|override|abstract|fip|self|test)(?![A-Za-z0-9_])",
42
+ "match": "\\b(module|struct|enum|trait|primitive|fun|fn|extern|export|fip|self|test)(?![A-Za-z0-9_])",
43
43
  "name": "keyword.const.plum"
44
44
  },
45
45
  {
compiler/wasm-codegen/src/calls.rs CHANGED
@@ -316,16 +316,6 @@ pub(crate) fn compileMethodCallAs(
316
316
  .func_ids
317
317
  .get(&key)
318
318
  .ok_or_else(|| format!("codegen: unknown method '{}.{}'", class_name, method))?;
319
- if !matches!(object, ast::Expr { kind: ast::ExprKind::TypeName(_), .. }) {
320
- if let (Some(overs), Some(&recv)) = (
321
- virtualOverriders(obj_ty, method),
322
- ctx.locals.get(&virtRecvLocal(result_expr)),
323
- ) {
324
- return compileVirtualCall(
325
- object, method, call, &class_name, &overs, recv, result_expr, body, ctx, state,
326
- );
327
- }
328
- }
329
319
  // A "static"-style call (`Bool.parse("true")`, `Float.fromStr("3.14")`) on a
330
320
  // self-less method declared inside a `type`/`enum` body — its receiver is a
331
321
  // bare reference to the type's own name, not a real value. Every method still
@@ -354,73 +344,3 @@ pub(crate) fn compileMethodCallAs(
354
344
  emitCall(func_idx, sig, call.tail, result_expr, body, ctx);
355
345
  Ok(())
356
346
  }
357
-
358
- /// A call whose receiver's static type has overriders: the receiver is evaluated once
359
- /// into `recv`, then for each overrider (deepest first) `ref.test S` selects
360
- /// `ref.cast S` + `S.m`, else `T.m`. The arguments are compiled once per branch (only
361
- /// one branch runs, so each is still evaluated exactly once, after the receiver).
362
- #[allow(clippy::too_many_arguments)]
363
- pub(crate) fn compileVirtualCall(
364
- object: &ast::Expr,
365
- method: &str,
366
- call: &ast::FnCall,
367
- class_name: &str,
368
- overs: &[String],
369
- recv: u32,
370
- result_expr: &ast::Expr,
371
- body: &mut Vec<u8>,
372
- ctx: &LocalCtx,
373
- state: &mut ModuleState,
374
- ) -> Result<(), String> {
375
- let baseKey = format!("{}::{}", class_name, method);
376
- let ret = ctx
377
- .func_sigs
378
- .get(&baseKey)
379
- .map(|s| s.ret.clone())
380
- .ok_or_else(|| format!("codegen: unknown method '{}.{}'", class_name, method))?;
381
- let bt = match ret.len() {
382
- 0 => BlockType::Empty,
383
- 1 => BlockType::Result(ret[0]),
384
- _ => {
385
- return Err(format!(
386
- "codegen: virtual method '{}.{}' returning a multi-value (flat) struct is not supported",
387
- class_name, method
388
- ))
389
- }
390
- };
391
- compileExpr(object, body, ctx, state)?;
392
- Instruction::LocalSet(recv).encode(body);
393
- let mut guards = Vec::new();
394
- for s in overs {
395
- let key = format!("{}::{}", s, method);
396
- let (Some(&idx), Some(sig), Some(&sidx)) = (
397
- ctx.func_ids.get(&key),
398
- ctx.func_sigs.get(&key),
399
- ctx.gc_types.class_type_idx.get(s),
400
- ) else {
401
- return Err(format!("codegen: unknown overrider '{}.{}'", s, method));
402
- };
403
- Instruction::LocalGet(recv).encode(body);
404
- Instruction::RefTestNonNull(HeapType::Concrete(sidx)).encode(body);
405
- Instruction::If(bt).encode(body);
406
- guards.push(TailDepth::enter(&ctx.tail_depth));
407
- Instruction::LocalGet(recv).encode(body);
408
- Instruction::RefCastNonNull(HeapType::Concrete(sidx)).encode(body);
409
- compileCallArgs(&key, &call.args, sig, body, ctx, state)?;
410
- emitCall(idx, sig, call.tail, result_expr, body, ctx);
411
- Instruction::Else.encode(body);
412
- }
413
- let idx = *ctx
414
- .func_ids
415
- .get(&baseKey)
416
- .ok_or_else(|| format!("codegen: unknown method '{}.{}'", class_name, method))?;
417
- let sig = &ctx.func_sigs[&baseKey];
418
- Instruction::LocalGet(recv).encode(body);
419
- compileCallArgs(&baseKey, &call.args, sig, body, ctx, state)?;
420
- emitCall(idx, sig, call.tail, result_expr, body, ctx);
421
- for _ in overs {
422
- Instruction::End.encode(body);
423
- }
424
- drop(guards);
425
- Ok(())
426
- }
compiler/wasm-codegen/src/context.rs CHANGED
@@ -288,9 +288,6 @@ pub(crate) struct GenState {
288
288
  pub(crate) value_helpers: HashMap<String, u32>,
289
289
  /// Per-compile facts (the source file `x!` crash messages name).
290
290
  pub(crate) err_info: ErrInfo,
291
- /// `(static receiver type, method)` -> the strict subtypes overriding it, deepest
292
- /// first (`plum_core::subtyping::overriders`); non-empty entries only.
293
- pub(crate) overriders: HashMap<(String, String), Vec<String>>,
294
291
  }
295
292
 
296
293
  impl GenState {
compiler/wasm-codegen/src/driver.rs CHANGED
@@ -65,23 +65,13 @@ pub(crate) fn resetGenState(source: &ast::Source) {
65
65
  });
66
66
  }
67
67
 
68
- /// The lowered program the types are keyed by: every node id is unique, and the
68
+ /// The lowered program the types are keyed by: every node id is unique.
69
- /// overriders of each virtual method are recorded.
70
- pub(crate) fn registerOverriders(source: &ast::Source) {
69
+ pub(crate) fn checkNodeIds(source: &ast::Source) {
71
70
  debug_assert!(
72
71
  ast::duplicateId(source).is_none(),
73
72
  "codegen: two nodes of the lowered program share {:?}",
74
73
  ast::duplicateId(source)
75
74
  );
76
- withGen(|s| {
77
- s.overriders = plum_core::subtyping::virtualMethods(source)
78
- .into_iter()
79
- .filter_map(|(t, m)| {
80
- let o = plum_core::subtyping::overriders(source, &t, &m);
81
- (!o.is_empty()).then_some(((t, m), o))
82
- })
83
- .collect();
84
- });
85
75
  }
86
76
 
87
77
  impl<'a> Setup<'a> {
compiler/wasm-codegen/src/emit.rs CHANGED
@@ -62,8 +62,6 @@ impl<'a> Emit<'a> {
62
62
  is_extern: false,
63
63
  is_export: false,
64
64
  is_test: false,
65
- is_override: false,
66
- is_abstract: false,
67
65
  is_fip: false,
68
66
  has_self: true,
69
67
  params: Vec::new(),
compiler/wasm-codegen/src/fnbody.rs CHANGED
@@ -227,14 +227,6 @@ impl<'a> Collector<'a> {
227
227
  ast::Expr { kind: ast::ExprKind::Attribute(a), .. } => {
228
228
  self.walkExpr(&a.object);
229
229
  if let ast::AttrKind::Method(call) = &a.attr {
230
- // A virtual call (the receiver's static type has overriders) parks
231
- // the receiver while its dynamic type is tested.
232
- if !matches!(a.object, ast::Expr { kind: ast::ExprKind::TypeName(_), .. }) {
233
- let oty = self.ty(&a.object);
234
- if virtualOverriders(&oty, &call.name).is_some() {
235
- self.bind(&virtRecvLocal(expr), oty);
236
- }
237
- }
238
230
  // A call through an fn-typed field needs a local to hold the
239
231
  // closure (read twice: env, then table index). Reserved even if
240
232
  // a same-named method wins at compile time (then left unused).
@@ -502,8 +494,7 @@ pub(crate) fn compileFnBodyImpl(
502
494
  ast::FnBody::Block(block) => {
503
495
  compileBlockAsFnBody(block, &mut body, &local_ctx, state, result_vt)?;
504
496
  }
505
- // `fns` excludes every `extern fun`; a bodiless non-extern method is abstract
497
+ // `fns` excludes every `extern fun`; the checker rejects any other bodiless fn.
506
- // and never runs (a subtype overrides it).
507
498
  ast::FnBody::Extern => Instruction::Unreachable.encode(&mut body),
508
499
  }
509
500
 
compiler/wasm-codegen/src/gctypes.rs CHANGED
@@ -204,46 +204,6 @@ pub(crate) fn arrayDepth(t: &PlumType) -> usize {
204
204
  }
205
205
  }
206
206
 
207
- /// Struct subtyping: base name -> the structs whose FIRST embed it is, from the lowered
208
- /// program (`Enum::base`). A base is a non-final heap struct of its fields; each subtype's
209
- /// struct extends it, so a subtype value already IS a base value and base fields are plain
210
- /// struct fields.
211
- pub(crate) fn subtypeBases(source: &ast::Source) -> HashMap<String, Vec<String>> {
212
- let named: HashSet<&str> = source
213
- .items
214
- .iter()
215
- .filter_map(|i| match i {
216
- ast::Item::Enum(e) if e.generics.is_empty() && e.isStruct() => Some(e.name.as_str()),
217
- _ => None,
218
- })
219
- .collect();
220
- let mut out: HashMap<String, Vec<String>> = HashMap::new();
221
- for item in &source.items {
222
- let ast::Item::Enum(e) = item else { continue };
223
- if !e.isStruct() {
224
- continue;
225
- }
226
- if let Some(b) = e.base().filter(|b| named.contains(b)) {
227
- out.entry(b.to_string()).or_default().push(e.name.clone());
228
- }
229
- }
230
- out
231
- }
232
-
233
- /// Ancestors of struct `n` (nearest first) along `parent` links.
234
- pub(crate) fn subtypeDepth(parent: &HashMap<String, String>, n: &str) -> usize {
235
- let mut d = 0;
236
- let mut cur = n;
237
- while let Some(p) = parent.get(cur) {
238
- d += 1;
239
- cur = p;
240
- if d > parent.len() {
241
- break;
242
- }
243
- }
244
- d
245
- }
246
-
247
207
  /// Builds the wasm-gc type registry: one struct family per non-generic enum and per
248
208
  /// distinct concrete instance (`apps`, e.g. `List[Int]`) of a generic enum, keyed by
249
209
  /// the structural `PlumType`; `Array[T]` instances become native wasm arrays of `T`'s
@@ -278,13 +238,6 @@ pub(crate) fn buildGcTypeRegistry(
278
238
  leaf_array_idx: HashMap::new(),
279
239
  mk_str_fn: None,
280
240
  };
281
- let subtyped = subtypeBases(source);
282
- let mut parent_of: HashMap<String, String> = HashMap::new();
283
- for (t, members) in &subtyped {
284
- for m in members {
285
- parent_of.insert(m.clone(), t.clone());
286
- }
287
- }
288
241
  let mut enum_decls: HashMap<String, &ast::Enum> = HashMap::new();
289
242
  let mut instances: Vec<PlumType> = Vec::new();
290
243
  for item in &source.items {
@@ -305,14 +258,13 @@ pub(crate) fn buildGcTypeRegistry(
305
258
  }
306
259
  }
307
260
  }
308
- // Enum supers, then structs by subtype depth:
261
+ // Enum supers first, then structs.
309
- // a supertype must precede (have a lower index than) every type extending it.
310
262
  instances.sort_by_key(|t| {
311
263
  let n = plum_checker::nominalName(t).unwrap_or("");
312
264
  if enum_decls.get(n).is_some_and(|e| !e.isStruct()) {
313
- (1, 0)
265
+ 1
314
266
  } else {
315
- (2, subtypeDepth(&parent_of, n))
267
+ 2
316
268
  }
317
269
  });
318
270
  for ty in &instances {
@@ -459,16 +411,7 @@ pub(crate) fn buildGcTypeRegistry(
459
411
  vname
460
412
  )
461
413
  });
462
- let super_idx = match ty {
463
- // A subtype extends its base's class struct.
464
- PlumType::TNamed(n) if *vname == *n && parent_of.contains_key(n) => {
465
- let b = &parent_of[n];
466
- *reg.class_type_idx.get(b).unwrap_or_else(|| {
467
- panic!("internal codegen error: base struct {b} of {n} not registered before it")
468
- })
469
- }
470
- _ => reg.superIdx(ty).expect("enum supertype slot"),
414
+ let super_idx = reg.superIdx(ty).expect("enum supertype slot");
471
- };
472
415
  let mut field_types: Vec<FieldType> = Vec::new();
473
416
  if let Some(leaves) = valueFieldsOf(ty).map(|_| flatValtypes(ty, &reg)) {
474
417
  // A value enum's box holds its flattened leaves directly.
@@ -484,10 +427,8 @@ pub(crate) fn buildGcTypeRegistry(
484
427
  }
485
428
  }));
486
429
  }
487
- // A struct some other struct extends is a non-final wasm-gc type.
488
- let isBase = matches!(ty, PlumType::TNamed(n) if *vname == *n && subtyped.contains_key(n));
489
430
  SubType {
490
- is_final: !isBase,
431
+ is_final: true,
491
432
  supertype_idx: Some(super_idx),
492
433
  composite_type: CompositeType {
493
434
  inner: CompositeInnerType::Struct(StructType {
@@ -527,20 +468,6 @@ pub(crate) fn buildGcTypeRegistry(
527
468
  },
528
469
  })
529
470
  .collect();
530
- // A subtype's fields start with its base's (same names, same types): the wasm-gc
531
- // subtype rule, and what makes a plain `struct.get` on the base type valid.
532
- for (b, subs) in &subtyped {
533
- let Some(bf) = enum_params.get(b) else { continue };
534
- for sname in subs {
535
- let Some(sv) = enum_variants.get(sname) else { continue };
536
- let ok = bf.len() <= sv.field_names.len()
537
- && bf.iter().zip(sv.field_names.iter().zip(sv.field_types.iter())).all(|((bn, bt), (sn, st))| bn == sn && bt == st);
538
- assert!(
539
- ok,
540
- "internal codegen error: fields of {sname} do not start with the fields of its base {b}"
541
- );
542
- }
543
- }
544
471
  module.addGcTypes(subtypes);
545
472
  reg
546
473
  }
compiler/wasm-codegen/src/inference.rs CHANGED
@@ -30,13 +30,6 @@ pub(crate) fn fnFieldType(obj_ty: &PlumType, field: &str, enum_variants: &EnumVa
30
30
  .filter(|t| matches!(t, PlumType::TFun(_, _)))
31
31
  }
32
32
 
33
- /// The strict subtypes of receiver type `obj_ty` overriding `method`, deepest first;
34
- /// `None` when the call cannot dispatch dynamically.
35
- pub(crate) fn virtualOverriders(obj_ty: &PlumType, method: &str) -> Option<Vec<String>> {
36
- let recv = plum_checker::methodReceiverName(obj_ty)?;
37
- withGen(|s| s.overriders.get(&(recv, method.to_string())).cloned())
38
- }
39
-
40
33
  /// The annotation of `name: Type := value`, which the checker records on `value`.
41
34
  pub(crate) fn declaredType(value: &ast::Expr, types: &UnitTypes) -> Option<PlumType> {
42
35
  if value.id == ast::NodeId(0) {
compiler/wasm-codegen/src/keys.rs CHANGED
@@ -38,12 +38,6 @@ pub(crate) fn fnFieldLocal(call_expr: &ast::Expr) -> String {
38
38
  format!("fnfield#{}", nodeKey(call_expr))
39
39
  }
40
40
 
41
- /// The synthetic local (reserved by `Collector`) parking the receiver of the virtual
42
- /// call `call_expr` while `ref.test` picks the overrider.
43
- pub(crate) fn virtRecvLocal(call_expr: &ast::Expr) -> String {
44
- format!("virt#{}", nodeKey(call_expr))
45
- }
46
-
47
41
  /// Import key of the `plum::crash(Str)` host function in `func_ids`.
48
42
  pub(crate) const CRASH_FN_KEY: &str = "__crash";
49
43
 
compiler/wasm-codegen/src/lib.rs CHANGED
@@ -208,7 +208,7 @@ fn compileSourceUnits(
208
208
  resetGenState(source);
209
209
  // The types are keyed by the node ids of the checker's lowered program.
210
210
  let source: &ast::Source = lowered.as_deref().unwrap_or(source);
211
- registerOverriders(source);
211
+ checkNodeIds(source);
212
212
  let mut setup = Setup::new(source, extra_exports, typed);
213
213
  setup.planUnits();
214
214
  setup.collectGenericTypes();
compiler/wasm-codegen/tests/wasm_codegen/e2e_mem/unique_inplace.rs CHANGED
@@ -38,21 +38,6 @@ fn unique_spread_reuses_object() {
38
38
  assert_eq!(runInt("reuse_run", &unique), 207);
39
39
  }
40
40
 
41
- #[test]
42
- fn unique_base_record_spread_in_place() {
43
- let unique = prog("struct Actor() =\n fun go(self) -> Int = 7\n\nstruct 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");
44
- assert_eq!(runInt("unique_base_record_spread_in_place", &unique), 700 + 90 + 6);
45
- assert!(spreadIsUnique("unique_base_record_flag", "hit", &unique));
46
- }
47
-
48
- /// A unique spread of a struct with an inherited base field are not checked as user code (F-T6-2).
49
- #[test]
50
- fn unique_base_field_record_spread_in_place() {
51
- let unique = prog("struct Actor(x: Int) =\n fun go(self) -> Int = 7\n\nstruct 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");
52
- assert_eq!(runInt("unique_base_field_record_spread_in_place", &unique), 700 + 90 + 6);
53
- assert!(spreadIsUnique("unique_base_field_flag", "hit", &unique));
54
- }
55
-
56
41
  /// F-T6-3: `s = Cell(..s, ...)` on a param must reassign the param's own slot, not
57
42
  /// a fresh shadow local that every read of `s` (the spread source included) sees unset.
58
43
  #[test]
compiler/wasm-codegen/tests/wasm_codegen/e2e_records/record_init.rs CHANGED
@@ -5,25 +5,16 @@ fn prog(body: &str) -> String {
5
5
  format!("{PRE}{body}")
6
6
  }
7
7
 
8
- const ACTOR: &str = "struct Actor(px: Int, py: Int) =
9
- fun init(self) =
10
- self.width: Int = 10
11
- self.height: Int = self.width * 2
12
- self.kids: Slice[Int] = Slice[Int]()
13
- fun area(self) -> Int = self.width * self.height
14
-
15
- ";
16
-
17
8
  #[test]
18
9
  fn errors_surface_through_the_loader() {
19
- let src = prog(&format!("{ACTOR}struct Ship(Actor(px, py)) =
10
+ let src = prog("struct Ship(px: Int, py: Int) =
20
11
  fun init(self) =
21
12
  self.vx = 0
22
13
 
23
14
  fun main() -> Int =
24
15
  s := Ship(px: 1, py: 2, width: 3)
25
16
  0
26
- "));
17
+ ");
27
18
  let e = load("err", &src).unwrap_err();
28
19
  assert!(e.contains("Ship has no constructor parameter width"), "{e}");
29
20
  }
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/mod.rs CHANGED
@@ -2,6 +2,5 @@
2
2
  mod io_entry;
3
3
  mod io_primitive;
4
4
  mod question;
5
- mod subtyping;
6
5
  mod synthetic_sites;
7
6
  mod type_param_static;
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/subtyping.rs DELETED
@@ -1,169 +0,0 @@
1
- //! Struct subtyping end to end: a struct embedding another FIRST is its box-free
2
- //! wasm-gc subtype; `override` dispatches on the concrete type at the call site.
3
- use plum_test_support::*;
4
-
5
- fn prog(body: &str) -> String {
6
- format!("{PRE}{body}")
7
- }
8
-
9
- /// Compiles with fusion on or off and runs `main() -> Int`.
10
- fn runWith(name: &str, src: &str, fusion: bool) -> i64 {
11
- let source = load(name, src).unwrap_or_else(|e| panic!("load failed: {e}"));
12
- let opts = plum_wasm_codegen::CompileOptions {
13
- passes: plum_driver::PassOptions { fusion: fusion, explain: false, ..plum_driver::PassOptions::default() },
14
- ..plum_wasm_codegen::CompileOptions::default()
15
- };
16
- let bytes = plum_wasm_codegen::compile(&source, &opts)
17
- .unwrap_or_else(|e| panic!("compile failed: {}", e.iter().map(|d| d.to_string()).collect::<Vec<_>>().join("\n")))
18
- .wasm;
19
- wasmparser::validate(&bytes).expect("valid wasm");
20
- let engine = engine();
21
- let module = wasmtime::Module::new(&engine, &bytes).unwrap();
22
- let mut store = wasmtime::Store::new(&engine, ());
23
- let mut linker = wasmtime::Linker::new(&engine);
24
- linker.define_unknown_imports_as_default_values(&mut store, &module).unwrap();
25
- let instance = linker.instantiate(&mut store, &module).unwrap();
26
- let main = instance.get_typed_func::<(), i64>(&mut store, "main").unwrap();
27
- main.call(&mut store, ()).unwrap()
28
- }
29
-
30
- fn both(name: &str, src: &str) -> i64 {
31
- let a = runWith(name, src, true);
32
- let b = runWith(name, src, false);
33
- assert_eq!(a, b, "fusion on/off disagree");
34
- a
35
- }
36
-
37
- const ACTORS: &str = "\
38
- struct Actor(x: Int) =
39
- fun draw(self) -> Int = 1
40
-
41
- struct Ship(Actor(x), s: Int) =
42
- override fun draw(self) -> Int = 2 + self.s
43
-
44
- struct Rock(Actor(x), r: Int)
45
-
46
- fun total(xs: Slice[Actor]) -> Int =
47
- t := 0
48
- for a := range xs
49
- t = t + a.draw()
50
- t
51
- ";
52
-
53
- #[test]
54
- fn overrideDispatchOverSlice() {
55
- let src = prog(&format!(
56
- "{ACTORS}
57
- fun main() -> Int =
58
- xs: Slice[Actor] := Slice[Actor]()
59
- xs.push(Ship(x: 0, s: 0))
60
- xs.push(Rock(x: 0, r: 5))
61
- total(xs)
62
- "
63
- ));
64
- // Ship.draw = 2, Rock inherits Actor.draw = 1.
65
- assert_eq!(both("sub_total", &src), 3);
66
- }
67
-
68
- #[test]
69
- fn deepestOverrideWins() {
70
- let src = prog("\
71
- struct Actor(x: Int) =
72
- fun draw(self) -> Int = 1
73
-
74
- struct Ship(Actor(x), s: Int) =
75
- override fun draw(self) -> Int = 2
76
-
77
- struct Boss(Ship(x, s), hp: Int)
78
-
79
- struct Champ(Ship(x, s), w: Int) =
80
- override fun draw(self) -> Int = 30
81
-
82
- fun pick(a: Actor) -> Int = a.draw()
83
-
84
- fun main() -> Int =
85
- pick(Actor(x: 0)) * 1000 + pick(Ship(x: 0, s: 0)) * 100 + pick(Boss(x: 0, s: 0, hp: 1)) * 10 + pick(Champ(x: 0, s: 0, w: 0)) / 30
86
- ");
87
- // Actor 1, Ship 2, Boss inherits Ship's 2, Champ 30 -> 1000 + 200 + 20 + 1
88
- assert_eq!(both("sub_deep", &src), 1221);
89
- }
90
-
91
- #[test]
92
- fn baseFieldWriteAndReadThroughBase() {
93
- let src = prog("\
94
- struct Actor(mut x: Int) =
95
- fun draw(self) -> Int = self.x
96
-
97
- struct Ship(Actor(x), s: Int) =
98
- override fun draw(self) -> Int = self.x * 10 + self.s
99
-
100
- fun move(a: Actor) =
101
- a.x = 5
102
-
103
- fun main() -> Int =
104
- s := Ship(x: 1, s: 3)
105
- move(s)
106
- a: Actor := s
107
- s.draw() + a.x * 1000
108
- ");
109
- // s.x became 5 through the Actor view: draw = 53; a.x = 5.
110
- assert_eq!(both("sub_fields", &src), 53 + 5000);
111
- }
112
-
113
- #[test]
114
- fn subtypePatternReadsSubtypeField() {
115
- let src = prog("\
116
- struct Actor(x: Int)
117
-
118
- struct Ship(Actor(x), s: Int)
119
-
120
- struct Rock(Actor(x), r: Int)
121
-
122
- fun weight(a: Actor) -> Int =
123
- match a
124
- Ship(x, s) => s + x
125
- Rock(x, r) => r * 100
126
- _ => 0
127
-
128
- fun main() -> Int =
129
- weight(Ship(x: 1, s: 4)) + weight(Rock(x: 0, r: 2)) + weight(Actor(x: 9))
130
- ");
131
- assert_eq!(both("sub_pattern", &src), 5 + 200 + 0);
132
- }
133
-
134
- #[test]
135
- fn baseEqualityIsIdentity() {
136
- let src = prog("\
137
- struct Actor(x: Int)
138
-
139
- struct Ship(Actor(x), s: Int)
140
-
141
- fun main() -> Int =
142
- a := Ship(x: 1, s: 2)
143
- b := Ship(x: 1, s: 2)
144
- c := a
145
- r := 0
146
- if a == c
147
- r = r + 1
148
- if a == b
149
- r = r + 10
150
- r
151
- ");
152
- assert_eq!(both("sub_eq", &src), 1);
153
- }
154
-
155
- #[test]
156
- fn abstractMethodOverriddenByEverySubtype() {
157
- let src = prog("\
158
- struct Actor(x: Int) =
159
- abstract fun draw(self) -> Int
160
-
161
- struct Ship(Actor(x), s: Int) =
162
- override fun draw(self) -> Int = 7
163
-
164
- fun main() -> Int =
165
- a: Actor := Ship(x: 0, s: 0)
166
- a.draw()
167
- ");
168
- assert_eq!(both("sub_abstract", &src), 7);
169
- }
compiler/wasm-codegen/tests/wasm_codegen/fusion_alloc_test.rs CHANGED
@@ -422,8 +422,7 @@ fun main(io: Io) -> Int =
422
422
  }
423
423
 
424
424
  /// Typing a field receiver must not hide a write: a pure producer reading `c.scale` stays
425
- /// before a call that writes it, whether the write is the field type's own method or an
426
- /// override reached through the field's base type.
425
+ /// before a call that writes it through the field's type.
427
426
  #[test]
428
427
  fn fusion_keeps_producer_before_write_through_field() {
429
428
  let own = format!(
@@ -458,51 +457,13 @@ fun main(io: Io) -> Int =
458
457
  let (fused, plain) = (compile(&src, true), compile(&src, false));
459
458
  assert_eq!(markTrace(&plain), vec![4, 2]);
460
459
  assert_eq!(markTrace(&fused), vec![4, 2]);
461
-
462
- let overridden = format!(
463
- "{IO_MARK}struct Base(mut scale: Int) =
464
- fun poke(self, n: Int) -> Int =
465
- n
466
-
467
- struct Sub(Base(scale)) =
468
- override fun poke(self, n: Int) -> Int =
469
- self.scale = n
470
- n
471
-
472
- struct Holder(base: Base) =
473
- fun poke(self, n: Int) -> Int =
474
- self.base.poke(n)
475
-
476
- fun scaled(c: Base, xs: Lst) -> Lst = match xs
477
- Nil => Nil
478
- Cons(h, t) => Cons(head: h * c.scale, tail: scaled(c, t))
479
-
480
- fun drawL(io: Io, xs: Lst) -> Int = match xs
481
- Nil => 0
482
- Cons(h, t) =>
483
- io.mark(h)
484
- drawL(io, t)
485
-
486
- fun go(io: Io, b: Holder, n: Int) -> Int =
487
- ys := scaled(b.base, build(n))
488
- _ := b.poke(100)
489
- drawL(io, ys)
490
-
491
- fun main(io: Io) -> Int =
492
- go(io, Holder(base: Sub(scale: 2)), 2)
493
- "
494
- );
495
- let src = loadRaw(&overridden);
496
- let (fused, plain) = (compile(&src, true), compile(&src, false));
497
- assert_eq!(markTrace(&plain), vec![4, 2]);
498
- assert_eq!(markTrace(&fused), vec![4, 2]);
499
460
  }
500
461
 
501
462
  // ---------- override dispatch: fusion only changes speed ----------
502
463
 
503
464
  /// `scaled` reads `c.scale`, `drawL` marks the result: a producer moved past a write marks
504
465
  /// the new scale.
505
- const OVERRIDE_PRELUDE: &str = "fun scaled(c: Base, xs: Lst) -> Lst = match xs
466
+ const FUSION_PRELUDE: &str = "fun scaled(c: Base, xs: Lst) -> Lst = match xs
506
467
  Nil => Nil
507
468
  Cons(h, t) => Cons(head: h * c.scale, tail: scaled(c, t))
508
469
 
@@ -517,322 +478,16 @@ fun sumL(xs: Lst) -> Int = match xs
517
478
  Cons(h, t) => h + sumL(t)
518
479
  ";
519
480
 
520
- /// Base's `poke` only reads, the override writes.
521
- const SUB_WRITES: &str = "struct Base(mut scale: Int) =
522
- fun poke(self, n: Int) -> Int =
523
- n
524
-
525
- struct Sub(Base(scale)) =
526
- override fun poke(self, n: Int) -> Int =
527
- self.scale = n
528
- n
529
- ";
530
-
531
- /// Base's `poke` writes, the override only reads.
532
- const BASE_WRITES: &str = "struct Base(mut scale: Int) =
533
- fun poke(self, n: Int) -> Int =
534
- self.scale = n
535
- n
536
-
537
- struct Sub(Base(scale)) =
538
- override fun poke(self, n: Int) -> Int =
539
- n
540
- ";
541
-
542
- /// Base's `poke` reads, `Mid`'s reads, `Deep`'s writes.
543
- const DEEP_WRITES: &str = "struct Base(mut scale: Int) =
544
- fun poke(self, n: Int) -> Int =
545
- n
546
-
547
- struct Mid(Base(scale)) =
548
- override fun poke(self, n: Int) -> Int =
549
- n + 1
550
-
551
- struct Deep(Mid(scale)) =
552
- override fun poke(self, n: Int) -> Int =
553
- self.scale = n
554
- n
555
- ";
556
-
557
- /// Base's `poke` reads, `Mid`'s writes, the leaf `Deep`'s reads.
558
- const MID_WRITES: &str = "struct Base(mut scale: Int) =
559
- fun poke(self, n: Int) -> Int =
560
- n
561
-
562
- struct Mid(Base(scale)) =
563
- override fun poke(self, n: Int) -> Int =
564
- self.scale = n
565
- n
566
-
567
- struct Deep(Mid(scale)) =
568
- override fun poke(self, n: Int) -> Int =
569
- n
570
- ";
571
-
572
481
  /// The marks of `main` fused and unfused, checked against `want`.
573
482
  #[track_caller]
574
483
  fn sameTrace(types: &str, rest: &str, want: &[i32]) {
575
- let text = format!("{IO_MARK}{types}{OVERRIDE_PRELUDE}{rest}");
484
+ let text = format!("{IO_MARK}{types}{FUSION_PRELUDE}{rest}");
576
485
  let src = loadRaw(&text);
577
486
  let (fused, plain) = (compile(&src, true), compile(&src, false));
578
487
  assert_eq!(markTrace(&plain), want, "--no-fusion:\n{rest}");
579
488
  assert_eq!(markTrace(&fused), want, "fused:\n{rest}");
580
489
  }
581
490
 
582
- /// `go` moves a producer past `{call}` only if the call cannot write `b.scale`.
583
- fn goWith(call: &str, param: &str, value: &str) -> String {
584
- format!(
585
- "fun go(io: Io, b: {param}, n: Int) -> Int =
586
- ys := scaled(b, build(n))
587
- {call}
588
- drawL(io, ys)
589
-
590
- fun main(io: Io) -> Int =
591
- go(io, {value}, 2)
592
- "
593
- )
594
- }
595
-
596
- /// A helper calling an overridden method through a typed parameter is as read-only as the
597
- /// union of the method and every override: the override here writes.
598
- #[test]
599
- fn fusion_override_through_typed_param() {
600
- let helper = "fun pk(b: Base) -> Int = b.poke(100)\n";
601
- let rest = format!("{helper}{}", goWith("_ := pk(b)", "Base", "Sub(scale: 2)"));
602
- sameTrace(SUB_WRITES, &rest, &[4, 2]);
603
- // called more than once, the result used
604
- let rest = format!(
605
- "{helper}fun go(io: Io, b: Base, n: Int) -> Int =
606
- ys := scaled(b, build(n))
607
- a := pk(b) + pk(b)
608
- io.mark(a)
609
- drawL(io, ys)
610
-
611
- fun main(io: Io) -> Int =
612
- go(io, Sub(scale: 2), 2)
613
- "
614
- );
615
- sameTrace(SUB_WRITES, &rest, &[200, 4, 2]);
616
- // the base writes, the override does not; the base runs
617
- let rest = format!("{helper}{}", goWith("_ := pk(b)", "Base", "Base(scale: 2)"));
618
- sameTrace(BASE_WRITES, &rest, &[4, 2]);
619
- // called directly: `b`'s method is the base one, not only the overrides
620
- sameTrace(SUB_WRITES, &goWith("_ := b.poke(100)", "Base", "Sub(scale: 2)"), &[4, 2]);
621
- sameTrace(BASE_WRITES, &goWith("_ := b.poke(100)", "Base", "Base(scale: 2)"), &[4, 2]);
622
- }
623
-
624
- /// The override writes through a field, directly or through that field's method.
625
- #[test]
626
- fn fusion_override_writes_through_field() {
627
- let types = |write: &str| {
628
- format!(
629
- "struct Cell(mut v: Int) =
630
- fun set(self, n: Int) =
631
- self.v = n
632
-
633
- struct Base(cell: Cell, mut scale: Int) =
634
- fun poke(self, n: Int) -> Int =
635
- n
636
-
637
- struct Sub(Base(cell, scale)) =
638
- override fun poke(self, n: Int) -> Int =
639
- {write}
640
- n
641
-
642
- fun cells(c: Cell, xs: Lst) -> Lst = match xs
643
- Nil => Nil
644
- Cons(h, t) => Cons(head: h * c.v, tail: cells(c, t))
645
- "
646
- )
647
- };
648
- let rest = "fun pk(b: Base) -> Int = b.poke(100)
649
-
650
- fun go(io: Io, b: Base, n: Int) -> Int =
651
- ys := cells(b.cell, build(n))
652
- _ := pk(b)
653
- drawL(io, ys)
654
-
655
- fun main(io: Io) -> Int =
656
- go(io, Sub(cell: Cell(v: 2), scale: 1), 2)
657
- ";
658
- sameTrace(&types("self.cell.v = n"), rest, &[4, 2]);
659
- sameTrace(&types("self.cell.set(n)"), rest, &[4, 2]);
660
- }
661
-
662
- /// Two levels of override: the call on `Base` or `Mid` is the union of all of them below,
663
- /// whichever level writes; an inherited method (`Mid` declares none) counts the same.
664
- #[test]
665
- fn fusion_override_two_levels() {
666
- let helper = |t: &str| format!("fun pk(b: {t}) -> Int = b.poke(100)\n");
667
- for (types, t, value) in [
668
- (DEEP_WRITES, "Base", "Deep(scale: 2)"),
669
- (DEEP_WRITES, "Mid", "Deep(scale: 2)"),
670
- (MID_WRITES, "Base", "Mid(scale: 2)"),
671
- (MID_WRITES, "Mid", "Mid(scale: 2)"),
672
- ] {
673
- let rest = format!("{}{}", helper(t), goWith("_ := pk(b)", t, value));
674
- sameTrace(types, &rest, &[4, 2]);
675
- sameTrace(types, &goWith("_ := b.poke(100)", t, value), &[4, 2]);
676
- }
677
- let inherited = "struct Base(mut scale: Int) =
678
- fun poke(self, n: Int) -> Int =
679
- n
680
-
681
- struct Mid(Base(scale))
682
-
683
- struct Deep(Mid(scale)) =
684
- override fun poke(self, n: Int) -> Int =
685
- self.scale = n
686
- n
687
- ";
688
- let rest = format!("{}{}", helper("Mid"), goWith("_ := pk(b)", "Mid", "Deep(scale: 2)"));
689
- sameTrace(inherited, &rest, &[4, 2]);
690
- sameTrace(inherited, &goWith("_ := b.poke(100)", "Mid", "Deep(scale: 2)"), &[4, 2]);
691
- }
692
-
693
- /// Through a field of the base type, in a helper or at the site; through a local holding it.
694
- #[test]
695
- fn fusion_override_through_field_and_local() {
696
- for body in ["h.base.poke(100)", "pk(h)", "viaLocal(h)", "viaTyped(h)"] {
697
- let rest = format!(
698
- "struct Holder(base: Base)
699
-
700
- fun pk(h: Holder) -> Int = h.base.poke(100)
701
-
702
- fun viaLocal(h: Holder) -> Int =
703
- l := h.base
704
- l.poke(100)
705
-
706
- fun viaTyped(h: Holder) -> Int =
707
- l: Base := h.base
708
- l.poke(100)
709
-
710
- fun go(io: Io, h: Holder, n: Int) -> Int =
711
- ys := scaled(h.base, build(n))
712
- _ := {body}
713
- drawL(io, ys)
714
-
715
- fun main(io: Io) -> Int =
716
- go(io, Holder(base: VALUE), 2)
717
- "
718
- );
719
- sameTrace(SUB_WRITES, &rest.replace("VALUE", "Sub(scale: 2)"), &[4, 2]);
720
- sameTrace(BASE_WRITES, &rest.replace("VALUE", "Base(scale: 2)"), &[4, 2]);
721
- sameTrace(MID_WRITES, &rest.replace("VALUE", "Mid(scale: 2)"), &[4, 2]);
722
- }
723
- // a local at the site
724
- let rest = "struct Holder(base: Base)
725
-
726
- fun go(io: Io, h: Holder, n: Int) -> Int =
727
- ys := scaled(h.base, build(n))
728
- l := h.base
729
- _ := l.poke(100)
730
- drawL(io, ys)
731
-
732
- fun main(io: Io) -> Int =
733
- go(io, Holder(base: VALUE), 2)
734
- ";
735
- sameTrace(SUB_WRITES, &rest.replace("VALUE", "Sub(scale: 2)"), &[4, 2]);
736
- sameTrace(BASE_WRITES, &rest.replace("VALUE", "Base(scale: 2)"), &[4, 2]);
737
- sameTrace(MID_WRITES, &rest.replace("VALUE", "Mid(scale: 2)"), &[4, 2]);
738
- }
739
-
740
- /// A method calling `self.poke`: `self` may be a subtype, so a base method that only calls
741
- /// an overridden one is as read-only as the override.
742
- #[test]
743
- fn fusion_override_through_self() {
744
- let types = "struct Base(mut scale: Int) =
745
- fun poke(self, n: Int) -> Int =
746
- n
747
- fun run(self, n: Int) -> Int =
748
- self.poke(n)
749
-
750
- struct Sub(Base(scale)) =
751
- override fun poke(self, n: Int) -> Int =
752
- self.scale = n
753
- n
754
- ";
755
- sameTrace(types, &goWith("_ := b.run(100)", "Base", "Sub(scale: 2)"), &[4, 2]);
756
- let rest = format!("fun pk(b: Base) -> Int = b.run(100)\n{}", goWith("_ := pk(b)", "Base", "Sub(scale: 2)"));
757
- sameTrace(types, &rest, &[4, 2]);
758
- }
759
-
760
- /// A value read through an overridden method is pure only if the method and all its
761
- /// overrides are: the base one writes, so `b.next()` must not move past `io.mark(b.scale)`.
762
- #[test]
763
- fn fusion_override_keeps_impure_argument_in_place() {
764
- let types = "struct Base(mut scale: Int) =
765
- fun next(self) -> Int =
766
- self.scale = self.scale + 1
767
- self.scale
768
-
769
- struct Sub(Base(scale)) =
770
- override fun next(self) -> Int =
771
- self.scale
772
-
773
- fun times(k: Int, xs: Lst) -> Lst = match xs
774
- Nil => Nil
775
- Cons(h, t) => Cons(head: h * k, tail: times(k, t))
776
- ";
777
- let rest = "fun go(io: Io, b: Base, n: Int) -> Int =
778
- ys := times(b.next(), build(n))
779
- io.mark(b.scale)
780
- drawL(io, ys)
781
-
782
- fun main(io: Io) -> Int =
783
- go(io, VALUE, 2)
784
- ";
785
- sameTrace(types, &rest.replace("VALUE", "Base(scale: 2)"), &[3, 6, 3]);
786
- sameTrace(types, &rest.replace("VALUE", "Sub(scale: 2)"), &[2, 4, 2]);
787
- }
788
-
789
- /// A wrapper method is unfolded at its call only when it is the one method the call can
790
- /// run: never past an override, whichever side of it the receiver is.
791
- #[test]
792
- fn fusion_never_unfolds_past_override() {
793
- let types = "struct Base(mut scale: Int) =
794
- fun total(self, xs: Lst) -> Int = sumL(xs)
795
-
796
- struct Sub(Base(scale)) =
797
- override fun total(self, xs: Lst) -> Int = sumL(xs) + 1000
798
-
799
- struct Mid2(Base(scale)) =
800
- override fun total(self, xs: Lst) -> Int = sumL(xs) + 2000
801
-
802
- struct Deep(Mid2(scale)) =
803
- override fun total(self, xs: Lst) -> Int = sumL(xs) + 3000
804
-
805
- struct Holder(base: Base)
806
- ";
807
- for (call, param, arg) in [("b.total(mapL(build(2)))", "Base", "VALUE"), ("b.base.total(mapL(build(2)))", "Holder", "Holder(base: VALUE)")] {
808
- for (value, want) in [("Base(scale: 1)", 30), ("Sub(scale: 1)", 1030), ("Mid2(scale: 1)", 2030), ("Deep(scale: 1)", 3030)] {
809
- let rest = format!(
810
- "fun go(io: Io, b: {param}) -> Int =
811
- io.mark({call})
812
- 0
813
-
814
- fun main(io: Io) -> Int =
815
- go(io, {})
816
- ",
817
- arg.replace("VALUE", value)
818
- );
819
- sameTrace(types, &rest, &[want]);
820
- }
821
- }
822
- // through a local, whose type the analyses do not know
823
- let rest = "fun go(io: Io, h: Holder) -> Int =
824
- l := h.base
825
- io.mark(l.total(mapL(build(2))))
826
- 0
827
-
828
- fun main(io: Io) -> Int =
829
- go(io, Holder(base: VALUE))
830
- ";
831
- for (value, want) in [("Base(scale: 1)", 30), ("Sub(scale: 1)", 1030), ("Deep(scale: 1)", 3030)] {
832
- sameTrace(types, &rest.replace("VALUE", value), &[want]);
833
- }
834
- }
835
-
836
491
  /// Unknown receiver, unrelated types: the first method of the name is not the one to unfold.
837
492
  #[test]
838
493
  fn fusion_never_unfolds_a_guessed_method() {
@@ -895,90 +550,6 @@ fun main(io: Io) -> Int =
895
550
  sameTrace(types, rest, &[4, 2]);
896
551
  }
897
552
 
898
- /// A method matching on a parameter, handed a just-built value: its arm is picked at the
899
- /// call only if it is the method the call runs. By name, the leaf override is a guess.
900
- #[test]
901
- fn fusion_never_specialises_a_guessed_method() {
902
- let types = "struct Base(mut scale: Int) =
903
- fun first(self, xs: Lst) -> Int = match xs
904
- Nil => 0
905
- Cons(h, t) => h
906
-
907
- struct Sub(Base(scale)) =
908
- override fun first(self, xs: Lst) -> Int = match xs
909
- Nil => 1000
910
- Cons(h, t) => h + 1000
911
-
912
- struct Holder(base: Base)
913
- ";
914
- let rest = "fun go(io: Io, h: Holder) -> Int =
915
- l := h.base
916
- io.mark(l.first(Nil))
917
- io.mark(l.first(Cons(head: 7, tail: Nil)))
918
- 0
919
-
920
- fun main(io: Io) -> Int =
921
- go(io, Holder(base: VALUE))
922
- ";
923
- sameTrace(types, &rest.replace("VALUE", "Base(scale: 0)"), &[0, 7]);
924
- sameTrace(types, &rest.replace("VALUE", "Sub(scale: 0)"), &[1000, 1007]);
925
- // a Unit method whose picked arm has several statements, spliced at the call
926
- let types = "struct Base(mut scale: Int) =
927
- fun show(self, io: Io, xs: Lst) = match xs
928
- Nil =>
929
- io.mark(1)
930
- io.mark(2)
931
- Cons(h, t) =>
932
- io.mark(h)
933
-
934
- struct Sub(Base(scale)) =
935
- override fun show(self, io: Io, xs: Lst) = match xs
936
- Nil =>
937
- io.mark(1001)
938
- io.mark(1002)
939
- Cons(h, t) =>
940
- io.mark(h)
941
-
942
- struct Holder(base: Base)
943
- ";
944
- let rest = "fun go(io: Io, h: Holder) -> Int =
945
- l := h.base
946
- l.show(io, Nil)
947
- 0
948
-
949
- fun main(io: Io) -> Int =
950
- go(io, Holder(base: VALUE))
951
- ";
952
- sameTrace(types, &rest.replace("VALUE", "Base(scale: 0)"), &[1, 2]);
953
- sameTrace(types, &rest.replace("VALUE", "Sub(scale: 0)"), &[1001, 1002]);
954
- }
955
-
956
- /// A producer behind an overridden method is not fused by guessing the leaf override.
957
- #[test]
958
- fn fusion_never_fuses_a_guessed_producer() {
959
- let types = "struct Base(mut scale: Int) =
960
- fun items(self) -> Lst = Cons(head: 1, tail: Nil)
961
-
962
- struct Sub(Base(scale)) =
963
- override fun items(self) -> Lst = Cons(head: 1000, tail: Nil)
964
-
965
- struct Holder(base: Base)
966
- ";
967
- let rest = "fun go(io: Io, h: Holder) -> Int =
968
- l := h.base
969
- io.mark(sumL(l.items()))
970
- io.mark(sumL(h.base.items()))
971
- b := h.base
972
- io.mark(drawL(io, b.items()))
973
- 0
974
-
975
- fun main(io: Io) -> Int =
976
- go(io, Holder(base: VALUE))
977
- ";
978
- sameTrace(types, &rest.replace("VALUE", "Base(scale: 0)"), &[1, 1, 1, 0]);
979
- sameTrace(types, &rest.replace("VALUE", "Sub(scale: 0)"), &[1000, 1000, 1000, 0]);
980
- }
981
-
982
553
  /// A method call on a parameter shadowed at the site, by a pattern name on a value of unknown
983
554
  /// type or a closure parameter: the shadowing name has its own type, whose `poke` writes.
984
555
  #[test]
docs/language.md CHANGED
@@ -16,7 +16,6 @@ A tour of the language, one idea at a time. Every feature also has a runnable fi
16
16
  - [Generics](#generics)
17
17
  - [Methods, statics and constants](#methods-statics-and-constants)
18
18
  - [Operators on your own types](#operators-on-your-own-types)
19
- - [Inheritance](#inheritance)
20
19
  - [Modules](#modules)
21
20
  - [Tests](#tests)
22
21
  - [Talking to the outside world](#talking-to-the-outside-world)
@@ -441,37 +440,6 @@ struct Vec2(x: Float, y: Float) =
441
440
  Vec2(x: self.x * s, y: self.y * s)
442
441
  ```
443
442
 
444
- ## Inheritance
445
-
446
- A struct can embed one other struct by listing it first. It gets the base's fields and methods, and can be used anywhere the base is expected.
447
-
448
- ```plum
449
- import std/Io
450
- import std/Int
451
- import std/Str
452
-
453
- struct Animal(name: Str) =
454
- fun speak(self, io: Io) =
455
- io.printLn("...")
456
-
457
- struct Dog(Animal(name), tricks: Int) =
458
- override fun speak(self, io: Io) =
459
- io.printLn("Woof")
460
- ```
461
-
462
- The header names which base fields callers pass. `override fun` replaces a base method, and the right version is picked at run time. `self.Animal.speak(io)` calls the base's version.
463
-
464
- `abstract fun` declares a method with no body. Every subtype must override it, and the base can't be constructed:
465
-
466
- ```plum
467
- struct Shape() =
468
- abstract fun area(self) -> Float
469
-
470
- struct Square(Shape, side: Float) =
471
- override fun area(self) -> Float =
472
- self.side * self.side
473
- ```
474
-
475
443
  ## Modules
476
444
 
477
445
  A file starts with its imports. Nothing is implicit, not even `Int` or `Str` methods, and each file imports what it uses.
examples/structs_records.plum CHANGED
@@ -128,7 +128,7 @@ struct Bag(n: Int) =
128
128
  self.items: Slice[Int] = Slice[Int]()
129
129
  self.cap: Int = self.n * 2
130
130
 
131
- # ---- embedding records and override ----
131
+ # ---- a struct holding another struct ----
132
132
 
133
133
  struct Counter(mut count: Int) =
134
134
 
@@ -141,13 +141,9 @@ struct Counter(mut count: Int) =
141
141
  fun show(self) -> Str =
142
142
  "counter {self.count}"
143
143
 
144
- struct Scoreboard(Counter(count), name: Str) =
144
+ struct Scoreboard(counter: Counter, name: Str) =
145
-
146
- fun init(self) =
147
- self.best: Int = 0
148
-
149
- override fun show(self) -> Str =
145
+ fun show(self) -> Str =
150
- "{self.name}: {self.Counter.show()}"
146
+ "{self.name}: {self.counter.show()}"
151
147
 
152
148
  # ==== digit names ====
153
149
 
@@ -368,16 +364,12 @@ test "init lists are fresh per construction"
368
364
  assert a.items.length() == 2
369
365
  assert b.items.length() == 0
370
366
 
371
- test "a record embeds another: fields, init and methods are copied"
367
+ test "a struct holds another and calls its methods"
372
- s := Scoreboard(count: 5, name: "hi")
368
+ s := Scoreboard(counter: Counter(count: 5), name: "hi")
373
- assert s.count == 5
369
+ assert s.counter.count == 5
374
- s.bump()
370
+ s.counter.bump()
375
- assert s.count == 6
371
+ assert s.counter.count == 6
376
- assert s.best == 0
377
-
378
- test "override replaces an embedded method and self.X.m() calls the original"
379
- s := Scoreboard(count: 1, name: "hi")
380
- assert s.show() == "hi: counter 1"
372
+ assert s.show() == "hi: counter 6"
381
373
 
382
374
  # ==== digit names tests ====
383
375
 
examples/subtyping.plum DELETED
@@ -1,97 +0,0 @@
1
- # Subtyping: a struct that embeds another struct FIRST is its subtype. It gains
2
- # the base's fields and methods, is accepted wherever the base is expected, and
3
- # `override` picks the subtype's method at runtime. A base method without a body
4
- # is abstract: every subtype must override it, and the base itself can't be built.
5
- import std/Str
6
- import std/Int
7
- import std/Float
8
- import std/Slice
9
-
10
- struct Vec2(x: Int, y: Int) =
11
- fun add(self, o: Vec2) -> Vec2 = Vec2(x: self.x + o.x, y: self.y + o.y)
12
-
13
- # ==== a base with defaults ====
14
-
15
- struct Entity(mut pos: Vec2) =
16
- fun draw(self) -> Int = 1
17
- fun moveBy(self, d: Vec2) =
18
- self.pos = self.pos + d
19
- fun drawTwice(self) -> Int = self.draw() * 2
20
-
21
- # Ship overrides `draw`; Rock keeps the default.
22
- struct Ship(Entity(pos), speed: Int) =
23
- override fun draw(self) -> Int = 100 + self.speed
24
-
25
- struct Rock(Entity(pos), weight: Int)
26
-
27
- # Takes the base; the call dispatches on the concrete type.
28
- fun drawIt(e: Entity) -> Int = e.draw()
29
-
30
- fun total(es: Slice[Entity]) -> Int =
31
- t := 0
32
- for e := range es
33
- t = t + e.draw()
34
- t
35
-
36
- # ==== an abstract method ====
37
-
38
- # `area` has no body: Shape can't be constructed and each subtype overrides it.
39
- struct Shape(name: Int) =
40
- abstract fun area(self) -> Int
41
- fun doubled(self) -> Int = self.area() * 2
42
-
43
- struct Square(Shape(name), side: Int) =
44
- override fun area(self) -> Int = self.side * self.side
45
-
46
- struct Rect(Shape(name), w: Int, h: Int) =
47
- override fun area(self) -> Int = self.w * self.h
48
-
49
- fun areaOf(s: Shape) -> Int = s.area()
50
-
51
- fun speedOf(e: Entity) -> Int =
52
- match e
53
- Ship(_, speed) => speed
54
- _ => -1
55
-
56
- # ==== tests ====
57
-
58
- test "ship passed as the base calls its override"
59
- ship := Ship(pos: Vec2(x: 1, y: 2), speed: 5)
60
- assert drawIt(ship) == 105
61
-
62
- test "a subtype without an override uses the base method"
63
- assert drawIt(Rock(pos: Vec2(x: 0, y: 0), weight: 3)) == 1
64
-
65
- test "a slice of the base dispatches per element"
66
- es := Slice[Entity]()
67
- es.push(Ship(pos: Vec2(x: 0, y: 0), speed: 1))
68
- es.push(Rock(pos: Vec2(x: 0, y: 0), weight: 9))
69
- assert total(es) == 102
70
-
71
- test "a base default calls the subtype override"
72
- e: Entity := Ship(pos: Vec2(x: 0, y: 0), speed: 0)
73
- assert e.drawTwice() == 200
74
-
75
- test "base fields are shared with the subtype"
76
- ship := Ship(pos: Vec2(x: 1, y: 2), speed: 5)
77
- e: Entity := ship
78
- e.moveBy(Vec2(x: 10, y: 20))
79
- assert ship.pos.x == 11
80
- assert e.pos.y == 22
81
-
82
- test "match on a base value with a subtype pattern"
83
- assert speedOf(Ship(pos: Vec2(x: 0, y: 0), speed: 4)) == 4
84
- assert speedOf(Rock(pos: Vec2(x: 0, y: 0), weight: 1)) == -1
85
-
86
- test "abstract methods dispatch to each subtype"
87
- assert areaOf(Square(name: 1, side: 3)) == 9
88
- assert areaOf(Rect(name: 2, w: 2, h: 5)) == 10
89
- s: Shape := Rect(name: 2, w: 2, h: 5)
90
- assert s.doubled() == 20
91
-
92
- test "spread copies a subtype"
93
- a := Ship(pos: Vec2(x: 1, y: 1), speed: 5)
94
- b := Ship(..a, speed: 6)
95
- assert b.speed == 6
96
- assert b.pos.x == 1
97
- assert a.speed == 5
examples/unions.plum CHANGED
@@ -1,8 +1,7 @@
1
1
  # Inline-variant enums: each variant declares its own fields, `enum Shape = Circle(r: Float) | Rect(w: Float, h: Float)`.
2
2
  # A variant is not a type: build it by name, read its fields by pattern. Wrap a
3
3
  # primitive in a one-field variant (`JInt(v: Int)`). An enum can define methods
4
- # (a `match self` body), and a struct embedding a base struct is its subtype
4
+ # (a `match self` body), which is how one call runs different code per variant.
5
- # with `override` dispatch (the replacement for auto-dispatch over members).
6
5
  module unions
7
6
  import std/Str
8
7
  import std/Int
@@ -93,16 +92,15 @@ fun stepIndex(s: Step) -> Int =
93
92
  ReadMin => 1
94
93
  ReadMax => 2
95
94
 
96
- # Subtyping: a struct embedding a base struct is accepted where the base is
95
+ # One call, different code per variant: a method over the variants.
97
- # expected; the bodiless base method is abstract and each subtype overrides it.
96
+ enum Book =
98
- struct Book(title: Str) =
97
+ | Novel(title: Str, pages: Int)
99
- abstract fun describe(self) -> Str
98
+ | Atlas(title: Str, regions: Int)
100
99
 
101
- struct Novel(Book(title), pages: Int) =
100
+ fun describe(self) -> Str =
101
+ match self
102
- override fun describe(self) -> Str = "novel {self.title}"
102
+ Novel(title, _) => "novel {title}"
103
-
104
- struct Atlas(Book(title), regions: Int) =
105
- override fun describe(self) -> Str = "atlas {self.title}"
103
+ Atlas(title, _) => "atlas {title}"
106
104
 
107
105
  fun describeAuto(b: Book) -> Str =
108
106
  b.describe()
@@ -111,7 +109,6 @@ fun describeMatch(b: Book) -> Str =
111
109
  match b
112
110
  Novel(title, _) => "novel {title}"
113
111
  Atlas(title, _) => "atlas {title}"
114
- _ => "book"
115
112
 
116
113
  enum Jb = BNull | BBool(v: Bool) | BFloat(v: Float) | BStr(v: Str) | BList(v: Slice[Jb])
117
114
 
@@ -210,7 +207,7 @@ test "enum defines its own method over its variants"
210
207
  b: Shape := Rectangle(w: 1.0, h: 2.0)
211
208
  assert b.area() == 2.0
212
209
 
213
- test "Book both ways: override dispatch and match agree"
210
+ test "Book both ways: method and match agree"
214
211
  books: Slice[Book] := Slice[Book]()
215
212
  books.push(Novel(title: "Dune", pages: 600))
216
213
  books.push(Atlas(title: "World", regions: 7))
tests/fbip/aliasing.plum CHANGED
@@ -39,20 +39,12 @@ struct Zero(label: Str, n: Int) =
39
39
  Origin = Zero(label: "z", n: 0)
40
40
  fun inc(self) -> Zero = Zero(..self, n: self.n + 1)
41
41
 
42
- # ---- a base with an override that stores self ----
42
+ # ---- a method that stores self, then updates a copy ----
43
43
 
44
- struct Base(label: Str, n: Int) =
45
- fun step(self) -> Base = Base(..self, n: self.n + 1)
46
-
47
- struct Sub(Base(label, n), log: Slice[Base]) =
44
+ struct Stored(label: Str, n: Int, log: Slice[Stored]) =
48
- override fun step(self) -> Base =
45
+ fun step(self) -> Stored =
49
46
  self.log.push(self)
50
- Base(label: self.label, n: self.n + 1)
47
+ Stored(..self, n: self.n + 1)
51
-
52
- struct Wide(label: Str, n: Int, extra: Int) =
53
- fun step(self) -> Wide = Wide(..self, n: self.n + 1)
54
-
55
- struct Narrow(Wide(label, n, extra), flag: Int)
56
48
 
57
49
  # ---- helpers ----
58
50
 
@@ -244,21 +236,13 @@ test "a shared value passed through a method then handed over in a loop"
244
236
  assert a.n == 0
245
237
  assert v.n == 3
246
238
 
247
- test "an override that stores self"
239
+ test "a method that stores self then spreads it"
248
- s := Sub(label: "s", n: 0, log: Slice[Base]())
240
+ s := Stored(label: "s", n: 0, log: Slice[Stored]())
249
- b: Base := s
250
- c := b.step()
241
+ c := s.step()
251
242
  assert s.log.at(0).n == 0
243
+ assert s.n == 0
252
244
  assert c.n == 1
253
245
 
254
- test "a base spread of a subtype stays a copy of the base"
255
- w := Narrow(label: "n", n: 0, extra: 5, flag: 1)
256
- x: Wide := w
257
- y := x.step()
258
- assert w.n == 0
259
- assert y.n == 1
260
- assert y.extra == 5
261
-
262
246
  test "an update in only one branch with the old value read after"
263
247
  a := Cell(label: "a", n: 0)
264
248
  assert branchy(a, true) == 1
tests/wasm_codegen/e2e_records/base_through_embedded_struct.plum DELETED
@@ -1,22 +0,0 @@
1
- import std/Str
2
- import std/Int
3
- import std/Float
4
- import std/Slice
5
-
6
- struct Actor(x: Int) =
7
- fun go(self) -> Int = 1
8
-
9
- struct Mid(Actor(x), m: Int) =
10
- override fun go(self) -> Int = 2 + self.Actor.go()
11
-
12
- struct Top(Mid(x, m), t: Int)
13
-
14
- fun run(a: Actor) -> Int = a.go()
15
-
16
- fun runMain() -> Int =
17
- t := Top(x: 1, m: 2, t: 3)
18
- run(t) * 10 + t.x
19
-
20
- # moved from e2e_records::record_embed::base_through_embedded_struct
21
- test "base through embedded struct"
22
- assert runMain() == 31
tests/wasm_codegen/e2e_records/embedded_record_standalone_and_unselected_set_by_init.plum DELETED
@@ -1,21 +0,0 @@
1
- import std/Str
2
- import std/Int
3
- import std/Float
4
- import std/Slice
5
-
6
- struct Stage(camScale: Int, tag: Int) =
7
- fun init(self) =
8
- self.camX: Int = 6
9
-
10
- struct World(Stage(tag), score: Int) =
11
- fun init(self) =
12
- self.camScale = 5
13
-
14
- fun runMain() -> Int =
15
- s := Stage(camScale: 2, tag: 1)
16
- w := World(tag: 3, score: 1)
17
- s.camScale * 100 + w.camScale * 10 + w.camX + w.tag + s.camX
18
-
19
- # moved from e2e_records::record_embed::embedded_record_standalone_and_unselected_set_by_init
20
- test "embedded record standalone and unselected set by init"
21
- assert runMain() == 265
tests/wasm_codegen/e2e_records/field_less_struct_embedding_field_less_base_dispatches_in_a_slice.plum DELETED
@@ -1,29 +0,0 @@
1
- import std/Str
2
- import std/Int
3
- import std/Float
4
- import std/Slice
5
-
6
- struct Actor() =
7
- fun act(self, n: Int) -> Int = n
8
- fun draw(self) -> Int = 1
9
-
10
- struct Panel(Actor) =
11
- override fun draw(self) -> Int = 10
12
-
13
- struct Ship(Actor, speed: Int) =
14
- override fun act(self, n: Int) -> Int = n + self.speed
15
- override fun draw(self) -> Int = 100
16
-
17
- fun runMain() -> Int =
18
- xs: Slice[Actor] := Slice[Actor]()
19
- xs.push(Panel())
20
- xs.push(Ship(speed: 5))
21
- total := 0
22
- for i := range 2
23
- total = total + xs.at(i).draw() + xs.at(i).act(1000)
24
- total
25
-
26
- # moved from e2e_records::empty_record_embed::field_less_struct_embedding_field_less_base_dispatches_in_a_slice
27
- # Panel: draw 10 + act 1000; Ship: draw 100 + act 1005.
28
- test "field less struct embedding field less base dispatches in a slice"
29
- assert runMain() == 2115
tests/wasm_codegen/e2e_records/fresh_list_per_construction.plum CHANGED
@@ -10,13 +10,9 @@ struct Actor(px: Int, py: Int) =
10
10
  self.kids: Slice[Int] = Slice[Int]()
11
11
  fun area(self) -> Int = self.width * self.height
12
12
 
13
- struct Ship(Actor(px, py)) =
14
- fun init(self) =
15
- self.vx = 0
16
-
17
13
  fun runMain() -> Int =
18
- a := Ship(px: 0, py: 0)
14
+ a := Actor(px: 0, py: 0)
19
- b := Ship(px: 0, py: 0)
15
+ b := Actor(px: 0, py: 0)
20
16
  a.kids.push(1)
21
17
  a.kids.push(2)
22
18
  b.kids.push(9)
tests/wasm_codegen/e2e_records/{inherited_fields_methods_and_init_order.plum → init_fields_and_order.plum} RENAMED
@@ -3,24 +3,18 @@ import std/Int
3
3
  import std/Float
4
4
  import std/Slice
5
5
 
6
- struct Stage(camScale: Int) =
6
+ struct Stage(camScale: Int, score: Int) =
7
7
  fun init(self) =
8
8
  self.root: Int = 10
9
9
  self.camX: Int = self.camScale * 2
10
+ self.best: Int = self.root + self.camX + 1
10
11
 
11
12
  fun act(self, dt: Int) -> Int = self.root + dt
12
13
 
13
- fun draw(self) -> Int = 100
14
-
15
- struct World(Stage(camScale), score: Int) =
16
- fun init(self) =
17
- self.best: Int = self.root + self.camX + 1
18
-
19
14
  fun runMain() -> Int =
20
- w := World(camScale: 3, score: 4)
15
+ w := Stage(camScale: 3, score: 4)
21
16
  w.camScale + w.root * 10 + w.camX * 100 + w.score * 1000 + w.best * 10000 + w.act(5) * 100000
22
17
 
23
- # moved from e2e_records::record_embed::inherited_fields_methods_and_init_order
24
18
  # camScale 3, root 10, camX 6, score 4, best 17, act(5) = 15
25
- test "inherited fields methods and init order"
19
+ test "init fields and init order"
26
20
  assert runMain() == 1674703
tests/wasm_codegen/e2e_records/override_calls_original.plum DELETED
@@ -1,24 +0,0 @@
1
- import std/Str
2
- import std/Int
3
- import std/Float
4
- import std/Slice
5
-
6
- struct Stage(camScale: Int) =
7
- fun init(self) =
8
- self.root: Int = 10
9
- self.camX: Int = self.camScale * 2
10
-
11
- fun act(self, dt: Int) -> Int = self.root + dt
12
-
13
- fun draw(self) -> Int = 100
14
-
15
- struct World(Stage(camScale), score: Int) =
16
- override fun draw(self) -> Int = self.Stage.draw() + self.score
17
-
18
- fun runMain() -> Int =
19
- w := World(camScale: 1, score: 7)
20
- w.draw() * 10 + w.act(1)
21
-
22
- # moved from e2e_records::record_embed::override_calls_original
23
- test "override calls original"
24
- assert runMain() == 1081
tests/wasm_codegen/e2e_records/ship_example_values.plum DELETED
@@ -1,26 +0,0 @@
1
- import std/Str
2
- import std/Int
3
- import std/Float
4
- import std/Slice
5
-
6
- struct Actor(px: Int, py: Int) =
7
- fun init(self) =
8
- self.width: Int = 10
9
- self.height: Int = self.width * 2
10
- self.kids: Slice[Int] = Slice[Int]()
11
- fun area(self) -> Int = self.width * self.height
12
-
13
- struct Ship(Actor(px, py), tex: Int) =
14
- fun init(self) =
15
- self.height = self.width + 1
16
- self.vx = 7
17
- override fun area(self) -> Int = self.width * self.height + self.vx
18
-
19
- fun runMain() -> Int =
20
- s := Ship(px: 1, py: 2, tex: 3)
21
- s.px + s.py * 10 + s.tex * 100 + s.area() * 1000
22
-
23
- # moved from e2e_records::record_init::ship_example_values
24
- # px 1 + py 20 + tex 300 + (10*11+7)*1000
25
- test "ship example values"
26
- assert runMain() == 117321
tests/wasm_codegen/e2e_records/through_a_base_value.plum DELETED
@@ -1,24 +0,0 @@
1
- import std/Str
2
- import std/Int
3
- import std/Float
4
- import std/Slice
5
-
6
- struct Actor(px: Int, width: Int) =
7
- fun init(self) =
8
- self.width = 10
9
- fun area(self) -> Int = self.width * 2
10
-
11
- struct Ship(Actor(px), tex: Int) =
12
- override fun area(self) -> Int = self.width * 20
13
-
14
- struct Rock(Actor(px))
15
-
16
- fun total(a: Actor) -> Int = a.area() + a.px
17
-
18
- fun runMain() -> Int =
19
- total(Ship(px: 1, tex: 0)) * 1000 + total(Rock(px: 2))
20
-
21
- # moved from e2e_records::record_init::through_a_base_value
22
- # Ship: 10*20 + 1 = 201 ; Rock: 20 + 2 = 22
23
- test "through a base value"
24
- assert runMain() == 201022