plum

#treesitter#compiler#wasm

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

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


8ecbf56Peter John 2026-07-20T11:47:23+05:30
feat(plum-wasm-codegen): compile general enum match patterns (tags + constructors)
plum-wasm-codegen/src/lib.rs CHANGED
@@ -431,12 +431,25 @@ impl<'a> Collector<'a> {
431
431
  for case in &m.cases {
432
432
  let saved = self.env.clone();
433
433
  if m.subjects.len() == 1 {
434
+ match case.patterns.first() {
434
- if let Some(ast::CasePattern::Name(n)) = case.patterns.first() {
435
+ Some(ast::CasePattern::Name(n)) => {
435
- let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
436
+ let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
436
- && self.cctx.enum_variants.contains_key(n);
437
+ && self.cctx.enum_variants.contains_key(n);
437
- if !is_variant {
438
+ if !is_variant {
438
- self.bind(n, subject_ty.clone());
439
+ self.bind(n, subject_ty.clone());
440
+ }
439
441
  }
442
+ Some(ast::CasePattern::Class { name, fields }) => {
443
+ if let Some(info) = self.cctx.enum_variants.get(name) {
444
+ let field_types = info.field_types.clone();
445
+ for (f, fty) in fields.iter().zip(field_types.iter()) {
446
+ if let ast::CasePattern::Name(n) = f {
447
+ self.bind(n, fty.clone());
448
+ }
449
+ }
450
+ }
451
+ }
452
+ _ => {}
440
453
  }
441
454
  }
442
455
  self.walk_block(&case.body);
@@ -901,7 +914,9 @@ fn compile_match_arms(
901
914
  }
902
915
  ast::CasePattern::String(_) => Err("codegen: string match patterns are not yet supported".to_string()),
903
916
  ast::CasePattern::Float(_) => Err("codegen: float match patterns are not yet supported".to_string()),
917
+ ast::CasePattern::Class { name, fields } => {
904
- ast::CasePattern::Class { .. } => Err("codegen: constructor match patterns are not yet supported".to_string()),
918
+ compile_variant_constructor_arm(name, fields, subject_vt, scratch_local, case, rest, body, ctx, state)
919
+ }
905
920
  }
906
921
  }
907
922
 
@@ -916,15 +931,14 @@ fn compile_variant_eq_arm(
916
931
  ctx: &LocalCtx,
917
932
  state: &mut ModuleState,
918
933
  ) -> Result<(), String> {
919
- // Only Bool's own variants have a concrete runtime representation in v1.5.
920
- let tag = match name {
934
+ let info = ctx
921
- "True" => 1i32,
935
+ .enum_variants
922
- "False" => 0i32,
936
+ .get(name)
923
- other => return Err(format!("codegen: enum variant pattern '{}' is not yet supported (only True/False)", other)),
937
+ .ok_or_else(|| format!("codegen: unknown enum variant '{}'", name))?;
924
- };
925
938
  if subject_vt != ValType::I32 {
926
- return Err("codegen: Bool match pattern against a non-Bool subject".to_string());
939
+ return Err(format!("codegen: enum tag pattern '{}' against a non-enum subject", name));
927
940
  }
941
+ let tag = info.tag;
928
942
  Instruction::LocalGet(scratch_local).encode(body);
929
943
  Instruction::I32Const(tag).encode(body);
930
944
  Instruction::I32Eq.encode(body);
@@ -936,6 +950,69 @@ fn compile_variant_eq_arm(
936
950
  Ok(())
937
951
  }
938
952
 
953
+ #[allow(clippy::too_many_arguments)]
954
+ fn compile_variant_constructor_arm(
955
+ name: &str,
956
+ fields: &[ast::CasePattern],
957
+ subject_vt: ValType,
958
+ scratch_local: u32,
959
+ case: &ast::Case,
960
+ rest: &[ast::Case],
961
+ body: &mut Vec<u8>,
962
+ ctx: &LocalCtx,
963
+ state: &mut ModuleState,
964
+ ) -> Result<(), String> {
965
+ let info = ctx
966
+ .enum_variants
967
+ .get(name)
968
+ .ok_or_else(|| format!("codegen: unknown enum variant '{}'", name))?;
969
+ if subject_vt != ValType::I32 {
970
+ return Err(format!("codegen: constructor pattern '{}' against a non-enum subject", name));
971
+ }
972
+ if fields.len() != info.field_types.len() {
973
+ return Err(format!(
974
+ "codegen: constructor pattern '{}' expects {} field(s), got {}",
975
+ name, info.field_types.len(), fields.len()
976
+ ));
977
+ }
978
+ let tag = info.tag;
979
+ let field_types = info.field_types.clone();
980
+
981
+ Instruction::LocalGet(scratch_local).encode(body);
982
+ Instruction::I32Load(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(body);
983
+ Instruction::I32Const(tag).encode(body);
984
+ Instruction::I32Eq.encode(body);
985
+ Instruction::If(BlockType::Empty).encode(body);
986
+ for (i, (pat, field_ty)) in fields.iter().zip(field_types.iter()).enumerate() {
987
+ let bind_name = match pat {
988
+ ast::CasePattern::Name(n) => Some(n.as_str()),
989
+ ast::CasePattern::Wildcard => None,
990
+ _ => return Err("codegen: only bare bindings or '_' are supported inside a constructor pattern".to_string()),
991
+ };
992
+ if let Some(n) = bind_name {
993
+ let idx = ctx
994
+ .locals
995
+ .get(n)
996
+ .copied()
997
+ .ok_or_else(|| format!("internal codegen error: missing binding local '{}'", n))?;
998
+ Instruction::LocalGet(scratch_local).encode(body);
999
+ let offset = ((i + 1) as u64) * 8;
1000
+ match plum_type_to_valtype(field_ty) {
1001
+ ValType::I64 => Instruction::I64Load(MemArg { offset, align: 3, memory_index: 0 }),
1002
+ ValType::F64 => Instruction::F64Load(MemArg { offset, align: 3, memory_index: 0 }),
1003
+ _ => Instruction::I32Load(MemArg { offset, align: 2, memory_index: 0 }),
1004
+ }.encode(body);
1005
+ Instruction::LocalSet(idx).encode(body);
1006
+ ctx.type_env.borrow_mut().insert(n.to_string(), TypeScheme::mono(field_ty.clone()));
1007
+ }
1008
+ }
1009
+ compile_block(&case.body, body, ctx, state)?;
1010
+ Instruction::Else.encode(body);
1011
+ compile_match_arms(rest, subject_vt, scratch_local, body, ctx, state)?;
1012
+ Instruction::End.encode(body);
1013
+ Ok(())
1014
+ }
1015
+
939
1016
  fn plum_type_from_valtype_hint(vt: ValType) -> PlumType {
940
1017
  match vt {
941
1018
  ValType::I64 => PlumType::TInt,
plum-wasm-codegen/tests/codegen_tests.rs CHANGED
@@ -422,3 +422,50 @@ main() -> Int =
422
422
  let bytes = compile_source(&source).expect("compile failed");
423
423
  assert_eq!(run_main(&bytes), 12);
424
424
  }
425
+
426
+ #[test]
427
+ fn non_bool_bare_tag_pattern_runs_correctly() {
428
+ let src = "\
429
+ enum Color =
430
+ | Red
431
+ | Green
432
+ | Blue
433
+
434
+ code(c: Color) -> Int =
435
+ match c
436
+ Red =>
437
+ return 1
438
+ Green =>
439
+ return 2
440
+ Blue =>
441
+ return 3
442
+
443
+ main() -> Int =
444
+ code(Green)
445
+ ";
446
+ let source = parse(src);
447
+ let bytes = compile_source(&source).expect("compile failed");
448
+ assert_eq!(run_main(&bytes), 2);
449
+ }
450
+
451
+ #[test]
452
+ fn constructor_pattern_wildcard_field_runs_correctly() {
453
+ let src = "\
454
+ enum Option =
455
+ | Some(Int)
456
+ | None
457
+
458
+ isSome(o: Option) -> Int =
459
+ match o
460
+ Some(_) =>
461
+ return 1
462
+ None =>
463
+ return 0
464
+
465
+ main() -> Int =
466
+ isSome(Some(99))
467
+ ";
468
+ let source = parse(src);
469
+ let bytes = compile_source(&source).expect("compile failed");
470
+ assert_eq!(run_main(&bytes), 1);
471
+ }