plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
78eed27
— Peter John
2026-11-01T21:38:06+05:30
refactor: real CheckCtx field for primitive generics
- plum-checker/src/lib.rs +17 -17
- plum-checker/tests/checker_tests.rs +2 -0
- plum-checker/tests/primitive_generics_test.rs +43 -0
- plum-lsp/src/main.rs +2 -0
- plum-wasm-codegen/src/lib.rs +10 -1
plum-checker/src/lib.rs
CHANGED
|
@@ -429,6 +429,7 @@ fn enumGenericsOf<'a>(name: &str, ctx: &'a CheckCtx) -> &'a [String] {
|
|
|
429
429
|
.values()
|
|
430
430
|
.find(|i| i.enum_name == name)
|
|
431
431
|
.map(|i| i.generics.as_slice())
|
|
432
|
+
.or_else(|| ctx.primitive_generics.get(name).map(|g| g.as_slice()))
|
|
432
433
|
.unwrap_or(&[])
|
|
433
434
|
}
|
|
434
435
|
|
|
@@ -944,6 +945,19 @@ pub type EnumParams = BTreeMap<String, Vec<(String, PlumType)>>;
|
|
|
944
945
|
/// and the matching codegen logic in `plum-wasm-codegen`).
|
|
945
946
|
pub type MinRequiredArgs = BTreeMap<(Option<String>, String), usize>;
|
|
946
947
|
|
|
948
|
+
/// Generic parameter names of each generic builtin `primitive` (`Array[T]`).
|
|
949
|
+
/// Primitives have no variants, so they don't live in `EnumVariants`.
|
|
950
|
+
pub type PrimitiveGenerics = BTreeMap<String, Vec<String>>;
|
|
951
|
+
|
|
952
|
+
pub fn buildPrimitiveGenerics(source: &ast::Source) -> PrimitiveGenerics {
|
|
953
|
+
source
|
|
954
|
+
.primitives
|
|
955
|
+
.iter()
|
|
956
|
+
.filter(|p| !p.generics.is_empty())
|
|
957
|
+
.map(|p| (p.name.clone(), p.generics.clone()))
|
|
958
|
+
.collect()
|
|
959
|
+
}
|
|
960
|
+
|
|
947
961
|
pub fn buildMinRequiredArgs(source: &ast::Source) -> MinRequiredArgs {
|
|
948
962
|
let mut result = MinRequiredArgs::new();
|
|
949
963
|
for item in &source.items {
|
|
@@ -969,6 +983,7 @@ pub struct CheckCtx<'a> {
|
|
|
969
983
|
pub enum_variants: &'a EnumVariants,
|
|
970
984
|
pub enum_params: &'a EnumParams,
|
|
971
985
|
pub min_required: &'a MinRequiredArgs,
|
|
986
|
+
pub primitive_generics: &'a PrimitiveGenerics,
|
|
972
987
|
}
|
|
973
988
|
|
|
974
989
|
/// Builds the global lookup tables (function/const signatures, class fields, method
|
|
@@ -1025,23 +1040,6 @@ pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, MethodEnv, EnumVaria
|
|
|
1025
1040
|
}
|
|
1026
1041
|
}
|
|
1027
1042
|
|
|
1028
|
-
// A generic builtin `primitive` (`Array[T]`) has no variants; its generic
|
|
1029
|
-
// parameters are recorded under an unnameable key so `enumGenericsOf` finds them.
|
|
1030
|
-
for p in source.primitives.iter().filter(|p| !p.generics.is_empty()) {
|
|
1031
|
-
enum_variants.insert(
|
|
1032
|
-
format!("<primitive {}>", p.name),
|
|
1033
|
-
EnumVariantInfo {
|
|
1034
|
-
enum_name: p.name.clone(),
|
|
1035
|
-
tag: 0,
|
|
1036
|
-
field_types: Vec::new(),
|
|
1037
|
-
field_names: Vec::new(),
|
|
1038
|
-
values: Vec::new(),
|
|
1039
|
-
generics: p.generics.clone(),
|
|
1040
|
-
is_value: false,
|
|
1041
|
-
},
|
|
1042
|
-
);
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
1043
|
// Second pass: register top-level function/method signatures and consts.
|
|
1046
1044
|
// Methods (`name<Receiver>(...)`) live in `methods`, keyed by receiver type,
|
|
1047
1045
|
// so a bare call can't accidentally resolve to some other type's method.
|
|
@@ -1562,11 +1560,13 @@ fn checkSourceInner(source: &ast::Source) -> (CheckResult<()>, TypedInfo) {
|
|
|
1562
1560
|
);
|
|
1563
1561
|
let (mut global_env, methods, enum_variants, enum_params) = buildGlobalTables(source);
|
|
1564
1562
|
let min_required = buildMinRequiredArgs(source);
|
|
1563
|
+
let primitive_generics = buildPrimitiveGenerics(source);
|
|
1565
1564
|
let ctx = CheckCtx {
|
|
1566
1565
|
methods: &methods,
|
|
1567
1566
|
enum_variants: &enum_variants,
|
|
1568
1567
|
enum_params: &enum_params,
|
|
1569
1568
|
min_required: &min_required,
|
|
1569
|
+
primitive_generics: &primitive_generics,
|
|
1570
1570
|
};
|
|
1571
1571
|
// Constants are inlined at their use sites, so their type is whatever their
|
|
1572
1572
|
// initializer infers to (`FLAG = true`, `N = LIMIT * 2 + 1`, `T = List(1, 2)`).
|
plum-checker/tests/checker_tests.rs
CHANGED
|
@@ -13,11 +13,13 @@ fn emptyCtx() -> CheckCtx<'static> {
|
|
|
13
13
|
static ENUM_VARIANTS: OnceLock<plum_checker::EnumVariants> = OnceLock::new();
|
|
14
14
|
static ENUM_PARAMS: OnceLock<plum_checker::EnumParams> = OnceLock::new();
|
|
15
15
|
static MIN_REQUIRED: OnceLock<plum_checker::MinRequiredArgs> = OnceLock::new();
|
|
16
|
+
static PRIM_GENERICS: OnceLock<plum_checker::PrimitiveGenerics> = OnceLock::new();
|
|
16
17
|
CheckCtx {
|
|
17
18
|
methods: METHODS.get_or_init(Default::default),
|
|
18
19
|
enum_variants: ENUM_VARIANTS.get_or_init(Default::default),
|
|
19
20
|
enum_params: ENUM_PARAMS.get_or_init(Default::default),
|
|
20
21
|
min_required: MIN_REQUIRED.get_or_init(Default::default),
|
|
22
|
+
primitive_generics: PRIM_GENERICS.get_or_init(Default::default),
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
|
plum-checker/tests/primitive_generics_test.rs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#![allow(non_snake_case)]
|
|
2
|
+
use plum_checker::{buildGlobalTables, checkSource};
|
|
3
|
+
|
|
4
|
+
fn repoRoot() -> std::path::PathBuf {
|
|
5
|
+
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("..")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
fn load(name: &str, body: &str) -> plum_core::ast::Source {
|
|
9
|
+
let dir = std::env::temp_dir().join(format!("plum_prim_generics_{}", std::process::id()));
|
|
10
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
11
|
+
let path = dir.join(format!("{name}.plum"));
|
|
12
|
+
std::fs::write(&path, body).unwrap();
|
|
13
|
+
plum_core::loadAndMerge(&path, &repoRoot()).unwrap()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[test]
|
|
17
|
+
fn primitiveGenericsAreNotPseudoVariants() {
|
|
18
|
+
let source = load("a", "import std/Array\nimport std/Int\n\nfun main() -> Int =\n 1\n");
|
|
19
|
+
assert!(source.primitives.iter().any(|p| p.name == "Array"));
|
|
20
|
+
let (_, _, enum_variants, _) = buildGlobalTables(&source);
|
|
21
|
+
let bad: Vec<_> = enum_variants.keys().filter(|k| k.contains("<primitive")).collect();
|
|
22
|
+
assert!(bad.is_empty(), "pseudo-entries in enum_variants: {bad:?}");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[test]
|
|
26
|
+
fn arrayIntMethodsStillTypeCheck() {
|
|
27
|
+
let source = load(
|
|
28
|
+
"b",
|
|
29
|
+
"import std/Array\nimport std/Int\n\nfun main() -> Int =\n a := Array[Int]().push(3).push(4)\n return a.get(1) + a.length()\n",
|
|
30
|
+
);
|
|
31
|
+
if let Err(errs) = checkSource(&source) {
|
|
32
|
+
panic!("{:?}", errs.iter().map(|e| e.message.clone()).collect::<Vec<_>>());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#[test]
|
|
37
|
+
fn arrayElementTypeIsEnforced() {
|
|
38
|
+
let source = load(
|
|
39
|
+
"c",
|
|
40
|
+
"import std/Array\nimport std/Int\nimport std/Str\n\nfun main() -> Str =\n a := Array[Int]().push(3)\n return a.get(0)\n",
|
|
41
|
+
);
|
|
42
|
+
assert!(checkSource(&source).is_err());
|
|
43
|
+
}
|
plum-lsp/src/main.rs
CHANGED
|
@@ -329,11 +329,13 @@ fn memberCompletionFor(
|
|
|
329
329
|
let (global_env, methods, enum_variants, enum_params) =
|
|
330
330
|
plum_checker::buildGlobalTables(&merged);
|
|
331
331
|
let min_required = plum_checker::buildMinRequiredArgs(&merged);
|
|
332
|
+
let primitive_generics = plum_checker::buildPrimitiveGenerics(&merged);
|
|
332
333
|
let ctx = plum_checker::CheckCtx {
|
|
333
334
|
methods: &methods,
|
|
334
335
|
enum_variants: &enum_variants,
|
|
335
336
|
enum_params: &enum_params,
|
|
336
337
|
min_required: &min_required,
|
|
338
|
+
primitive_generics: &primitive_generics,
|
|
337
339
|
};
|
|
338
340
|
let cursor_fn = completion::enclosingFn(&own_source, cursor_line);
|
|
339
341
|
completion::memberItems(cursor_fn, receiver, &global_env, &ctx, &merged.stars)
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -388,6 +388,7 @@ pub struct CompileCtx<'a> {
|
|
|
388
388
|
pub enum_variants: EnumVariants,
|
|
389
389
|
pub enum_params: EnumParams,
|
|
390
390
|
pub min_required: plum_checker::MinRequiredArgs,
|
|
391
|
+
pub primitive_generics: plum_checker::PrimitiveGenerics,
|
|
391
392
|
pub global_env: TypeEnv,
|
|
392
393
|
/// Closure literal (keyed by `&Expr::Closure` pointer identity) -> its `ClosureInfo`.
|
|
393
394
|
pub closures: HashMap<(usize, usize), ClosureInfo>,
|
|
@@ -466,6 +467,7 @@ struct LocalCtx<'a> {
|
|
|
466
467
|
enum_variants: &'a EnumVariants,
|
|
467
468
|
enum_params: &'a EnumParams,
|
|
468
469
|
min_required: &'a plum_checker::MinRequiredArgs,
|
|
470
|
+
primitive_generics: &'a plum_checker::PrimitiveGenerics,
|
|
469
471
|
gc_types: &'a GcTypeRegistry,
|
|
470
472
|
singleton_globals: &'a HashMap<(PlumType, String), u32>,
|
|
471
473
|
/// Tracks each binding's inferred type as compilation proceeds through
|
|
@@ -1318,12 +1320,14 @@ fn checkCtxOf<'a>(
|
|
|
1318
1320
|
ctx_enum_variants: &'a EnumVariants,
|
|
1319
1321
|
ctx_enum_params: &'a EnumParams,
|
|
1320
1322
|
ctx_min_required: &'a plum_checker::MinRequiredArgs,
|
|
1323
|
+
ctx_primitive_generics: &'a plum_checker::PrimitiveGenerics,
|
|
1321
1324
|
) -> plum_checker::CheckCtx<'a> {
|
|
1322
1325
|
plum_checker::CheckCtx {
|
|
1323
1326
|
methods: ctx_methods,
|
|
1324
1327
|
enum_variants: ctx_enum_variants,
|
|
1325
1328
|
enum_params: ctx_enum_params,
|
|
1326
1329
|
min_required: ctx_min_required,
|
|
1330
|
+
primitive_generics: ctx_primitive_generics,
|
|
1327
1331
|
}
|
|
1328
1332
|
}
|
|
1329
1333
|
|
|
@@ -1348,6 +1352,7 @@ fn inferFallbackType(expr: &ast::Expr, ctx: &LocalCtx) -> PlumType {
|
|
|
1348
1352
|
ctx.enum_variants,
|
|
1349
1353
|
ctx.enum_params,
|
|
1350
1354
|
ctx.min_required,
|
|
1355
|
+
ctx.primitive_generics,
|
|
1351
1356
|
);
|
|
1352
1357
|
plum_checker::inferExpr(expr, &env, &cctx).unwrap_or(PlumType::TInt)
|
|
1353
1358
|
}
|
|
@@ -1858,6 +1863,7 @@ fn compileSourceInner(
|
|
|
1858
1863
|
let bool_to_str_fn = plum_checker::needsBuiltinBoolToStr(source).then(plum_checker::builtinBoolToStr);
|
|
1859
1864
|
let (global_env, methods, enum_variants, enum_params) = plum_checker::buildGlobalTables(source);
|
|
1860
1865
|
let min_required = plum_checker::buildMinRequiredArgs(source);
|
|
1866
|
+
let primitive_generics = plum_checker::buildPrimitiveGenerics(source);
|
|
1861
1867
|
let mut eg: EnumGenerics = HashMap::new();
|
|
1862
1868
|
for item in &source.items {
|
|
1863
1869
|
if let ast::Item::Enum(e) = item {
|
|
@@ -1883,7 +1889,7 @@ fn compileSourceInner(
|
|
|
1883
1889
|
let mut units: Vec<UnitDef> = Vec::new();
|
|
1884
1890
|
let mut known_fns: HashSet<String> = HashSet::new();
|
|
1885
1891
|
{
|
|
1886
|
-
let cctx = checkCtxOf(&methods, &enum_variants, &enum_params, &min_required);
|
|
1892
|
+
let cctx = checkCtxOf(&methods, &enum_variants, &enum_params, &min_required, &primitive_generics);
|
|
1887
1893
|
for f in &all_fns {
|
|
1888
1894
|
if f.type_param.is_none() {
|
|
1889
1895
|
known_fns.insert(f.name.clone());
|
|
@@ -2512,6 +2518,7 @@ fn compileSourceInner(
|
|
|
2512
2518
|
enum_variants,
|
|
2513
2519
|
enum_params,
|
|
2514
2520
|
min_required,
|
|
2521
|
+
primitive_generics,
|
|
2515
2522
|
global_env,
|
|
2516
2523
|
closures,
|
|
2517
2524
|
closure_asts,
|
|
@@ -4310,6 +4317,7 @@ fn compileFnBodyImpl(
|
|
|
4310
4317
|
enum_variants: &ctx.enum_variants,
|
|
4311
4318
|
enum_params: &ctx.enum_params,
|
|
4312
4319
|
min_required: &ctx.min_required,
|
|
4320
|
+
primitive_generics: &ctx.primitive_generics,
|
|
4313
4321
|
gc_types: &ctx.gc_types,
|
|
4314
4322
|
singleton_globals: &ctx.singleton_globals,
|
|
4315
4323
|
type_env: RefCell::new(base_env),
|
|
@@ -7818,6 +7826,7 @@ fn compileClosureBody(
|
|
|
7818
7826
|
enum_variants: &ctx.enum_variants,
|
|
7819
7827
|
enum_params: &ctx.enum_params,
|
|
7820
7828
|
min_required: &ctx.min_required,
|
|
7829
|
+
primitive_generics: &ctx.primitive_generics,
|
|
7821
7830
|
gc_types: &ctx.gc_types,
|
|
7822
7831
|
singleton_globals: &ctx.singleton_globals,
|
|
7823
7832
|
type_env: RefCell::new(base_env),
|