plum

#treesitter#compiler#wasm

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 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),