plum
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 +17 -0
- plum-core/src/ast.rs +2 -0
- plum-core/src/loader.rs +2 -25
- plum-core/src/parser.rs +1 -0
- plum-std/Array.plum +9 -3
- plum-wasm-codegen/src/lib.rs +6 -1
- plum-wasm-codegen/tests/codegen_tests.rs +2 -4
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
|
-
|
|
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
|
-
|
|
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);
|