plum

#treesitter#compiler#wasm

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

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


3c73b0b — Peter John 2026-11-01T21:26:50+05:30
feat: Array[T] is a generic primitive, not a fake single-variant enum; legacy exemption removed
plum-checker/src/lib.rs CHANGED
@@ -1013,6 +1013,23 @@ pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, MethodEnv, EnumVaria
1013
1013
  }
1014
1014
  }
1015
1015
 
1016
+ // A generic builtin `primitive` (`Array[T]`) has no variants; its generic
1017
+ // parameters are recorded under an unnameable key so `enumGenericsOf` finds them.
1018
+ for p in source.primitives.iter().filter(|p| !p.generics.is_empty()) {
1019
+ enum_variants.insert(
1020
+ format!("<primitive {}>", p.name),
1021
+ EnumVariantInfo {
1022
+ enum_name: p.name.clone(),
1023
+ tag: 0,
1024
+ field_types: Vec::new(),
1025
+ field_names: Vec::new(),
1026
+ values: Vec::new(),
1027
+ generics: p.generics.clone(),
1028
+ is_value: false,
1029
+ },
1030
+ );
1031
+ }
1032
+
1016
1033
  // Second pass: register top-level function/method signatures and consts.
1017
1034
  // Methods (`name<Receiver>(...)`) live in `methods`, keyed by receiver type,
1018
1035
  // so a bare call can't accidentally resolve to some other type's method.
plum-core/src/ast.rs CHANGED
@@ -33,6 +33,8 @@ pub struct PrimitiveDecl {
33
33
  pub name: String,
34
34
  pub line: usize,
35
35
  pub column: usize,
36
+ /// The type's own generic parameter names (`Array[T]`).
37
+ pub generics: Vec<String>,
36
38
  /// The first `fun` in the block that declares `self` (name, line, column).
37
39
  pub selfFn: Option<(String, usize, usize)>,
38
40
  }
plum-core/src/loader.rs CHANGED
@@ -254,30 +254,7 @@ fn mergeItems(
254
254
  prims: &mut Vec<PrimitiveDecl>,
255
255
  ) -> Result<(), String> {
256
256
  // Removed forms (inline variant payloads, lone self-named variant).
257
- // A payload-less self-named single variant (`enum Array[T] = | Array`)
258
- // is an opaque builtin handle and stays legal.
259
- let opaque: Vec<String> = source
260
- .items
261
- .iter()
262
- .filter_map(|i| match i {
263
- Item::Enum(e)
264
- if e.variants.len() == 1
265
- && e.variants[0].name == e.name
266
- && e.variants[0].fields.is_empty()
267
- && e.variants[0].values.is_empty() =>
268
- {
269
- Some(format!(
270
- "enum {0} has a single variant named {0} — write record {0}(...)",
271
- e.name
272
- ))
273
- }
274
- _ => None,
275
- })
276
- .collect();
277
- let legacy: Vec<String> = crate::unions::legacyErrors(source)
257
+ let legacy: Vec<String> = crate::unions::legacyErrors(source);
278
- .into_iter()
279
- .filter(|m| !opaque.contains(m))
280
- .collect();
281
258
  if !legacy.is_empty() {
282
259
  return Err(legacy.join("\n"));
283
260
  }
@@ -289,7 +266,7 @@ fn mergeItems(
289
266
  source
290
267
  .primitives
291
268
  .iter()
292
- .filter(|p| !BUILTIN_PRIMITIVES.contains(&p.name.as_str()))
269
+ .filter(|p| !BUILTIN_PRIMITIVES.contains(&p.name.as_str()) || !p.generics.is_empty())
293
270
  .cloned(),
294
271
  );
295
272
  for item in &source.items {
plum-core/src/parser.rs CHANGED
@@ -112,6 +112,7 @@ impl<'a> AstParser<'a> {
112
112
  name: name.clone(),
113
113
  line: pos.row + 1,
114
114
  column: pos.column + 1,
115
+ generics: self.parseGenericsField(child).into_iter().map(|g| g.name).collect(),
115
116
  selfFn,
116
117
  });
117
118
  for c in consts {
plum-std/Array.plum CHANGED
@@ -1,6 +1,7 @@
1
1
  module std
2
2
  import std/Int
3
3
  import std/Float
4
+ import std/Str
4
5
 
5
6
  # A fixed-length, O(1)-indexable array of `T`, backed directly by a wasm-gc
6
7
  # `array<anyref>` — `Array[T]` is erased, so every `T` shares the SAME
@@ -29,9 +30,7 @@ import std/Float
29
30
  # hood — so `get` on an index that was never `set` traps rather than
30
31
  # returning some default `T` value (there is no way to conjure a default `T`
31
32
  # generically).
32
- enum Array[T] =
33
+ primitive Array[T] =
33
- | Array
34
-
35
34
  # A new, empty array.
36
35
  fun init() -> Array[T] =
37
36
  todo
@@ -76,3 +75,10 @@ enum Array[T] =
76
75
  a.set(i, v)
77
76
  i = i + 1
78
77
  return a
78
+
79
+ test "Array is a primitive with working generic methods"
80
+ a := Array[Str]().push("x").push("y")
81
+ assert a.length() == 2
82
+ assert a.get(1) == "y"
83
+ b := Array.filled(3, 7)
84
+ assert b.at(2) == 7
plum-wasm-codegen/src/lib.rs CHANGED
@@ -1156,13 +1156,13 @@ fn buildGcTypeRegistry(
1156
1156
  });
1157
1157
  reg.app_super.insert(a.clone(), idx);
1158
1158
  reg.app_class.insert(a.clone(), idx);
1159
- reg.app_variant.insert((a.clone(), "Array".to_string()), idx);
1160
1159
  }
1161
1160
 
1162
1161
  let enum_generics = |n: &str| -> Vec<String> {
1163
1162
  enum_decls
1164
1163
  .get(n)
1165
1164
  .map(|e| e.generics.iter().map(|g| g.name.clone()).collect())
1165
+ .or_else(|| source.primitives.iter().find(|p| p.name == n).map(|p| p.generics.clone()))
1166
1166
  .unwrap_or_default()
1167
1167
  };
1168
1168
  let subst_for = |ty: &PlumType| -> std::collections::BTreeMap<String, PlumType> {
@@ -1856,6 +1856,11 @@ fn compileSourceInner(
1856
1856
  eg.insert(e.name.clone(), e.generics.iter().map(|g| g.name.clone()).collect());
1857
1857
  }
1858
1858
  }
1859
+ for p in &source.primitives {
1860
+ if !p.generics.is_empty() {
1861
+ eg.insert(p.name.clone(), p.generics.clone());
1862
+ }
1863
+ }
1859
1864
 
1860
1865
  // ---- units: every non-generic fn, plus one per reached instantiation ----
1861
1866
  let all_fns: Vec<&ast::Fn> = source
plum-wasm-codegen/tests/codegen_tests.rs CHANGED
@@ -3072,16 +3072,14 @@ primitive Alpha =
3072
3072
  primitive Beta =
3073
3073
  X = 20
3074
3074
 
3075
- enum Gamma =
3075
+ record Gamma(n: Int) =
3076
- | Gamma
3077
-
3078
3076
  X = 300
3079
3077
 
3080
3078
  fun sum(self) -> Int =
3081
3079
  X + Alpha.X + Beta.X
3082
3080
 
3083
3081
  fun main() -> Int =
3084
- g := Gamma
3082
+ g := Gamma(n: 0)
3085
3083
  Alpha.X + Beta.X + Gamma.X + g.sum()
3086
3084
  ", STD_HEAD);
3087
3085
  assert_eq!(runMerged::<i64>("const_names", &src), 1 + 20 + 300 + 321);