plum

#treesitter#compiler#wasm

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

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


afc0334 — Peter John 2026-11-01T23:26:02+05:30
fix: bare generic type name inside its own methods compiles (applyOwnGenerics pre-pass)
plum-checker/src/lib.rs CHANGED
@@ -1103,6 +1103,15 @@ pub fn typedSource(source: &ast::Source) -> CheckResult<TypedInfo> {
1103
1103
  }
1104
1104
 
1105
1105
  fn checkSourceFull(source: &ast::Source) -> (CheckResult<()>, TypedInfo) {
1106
+ if let Some(applied) = applyOwnGenerics(source) {
1107
+ // Check (and let codegen compile) the copy with bare own-generic names applied.
1108
+ let applied = std::rc::Rc::new(applied);
1109
+ let (result, mut info) = checkSourceFull(&applied);
1110
+ if info.lowered.is_none() {
1111
+ info.lowered = Some(applied);
1112
+ }
1113
+ return (result, info);
1114
+ }
1106
1115
  if !source.formLoops {
1107
1116
  return checkUnions(source, |s| checkSourceOnce(s));
1108
1117
  }
@@ -1221,6 +1230,121 @@ fn fillErrorTables(source: &ast::Source, info: &mut TypedInfo) {
1221
1230
  info.error_codes = all.into_iter().zip(1..).collect();
1222
1231
  }
1223
1232
 
1233
+ /// Inside a generic type's own methods and field types, a bare `Crate` (for
1234
+ /// `record Crate[T]`) means `Crate[T]`, the declaration's own type arguments.
1235
+ /// Returns a copy of `source` with every such type annotation (params, returns,
1236
+ /// yields, annotated locals, explicit construction generics, fields) applied,
1237
+ /// so the checker and codegen both see the same `TApp`; `None` if there is none.
1238
+ fn applyOwnGenerics(source: &ast::Source) -> Option<ast::Source> {
1239
+ let own: HashMap<&str, Vec<ast::Type>> = source
1240
+ .items
1241
+ .iter()
1242
+ .filter_map(|i| match i {
1243
+ ast::Item::Enum(e) if !e.generics.is_empty() => Some((
1244
+ e.name.as_str(),
1245
+ e.generics.iter().map(|g| ast::Type { name: g.name.clone(), generics: vec![] }).collect(),
1246
+ )),
1247
+ _ => None,
1248
+ })
1249
+ .collect();
1250
+ if own.is_empty() {
1251
+ return None;
1252
+ }
1253
+ fn apply(t: &mut ast::Type, name: &str, args: &[ast::Type], changed: &mut bool) {
1254
+ if t.name == name && t.generics.is_empty() {
1255
+ t.generics = args.to_vec();
1256
+ *changed = true;
1257
+ }
1258
+ for g in &mut t.generics {
1259
+ apply(g, name, args, changed);
1260
+ }
1261
+ }
1262
+ // Statement-level annotations (`c: Crate := ...`) in `stmts` and their nested blocks.
1263
+ fn stmtTypes(stmts: &mut [ast::Stmt], fix: &mut dyn FnMut(&mut ast::Type)) {
1264
+ for s in stmts {
1265
+ match s {
1266
+ ast::Stmt::Assign(a) => a.ty.iter_mut().for_each(&mut *fix),
1267
+ ast::Stmt::While(w) => stmtTypes(&mut w.body.stmts, fix),
1268
+ ast::Stmt::ForLoop(l) => stmtTypes(&mut l.body.stmts, fix),
1269
+ ast::Stmt::If(i) => {
1270
+ stmtTypes(&mut i.body.stmts, fix);
1271
+ for e in &mut i.else_ifs {
1272
+ stmtTypes(&mut e.body.stmts, fix);
1273
+ }
1274
+ if let Some(b) = &mut i.else_ {
1275
+ stmtTypes(&mut b.stmts, fix);
1276
+ }
1277
+ }
1278
+ ast::Stmt::Match(m) => m.cases.iter_mut().for_each(|c| stmtTypes(&mut c.body.stmts, fix)),
1279
+ _ => {}
1280
+ }
1281
+ }
1282
+ }
1283
+ let mut replaced: Vec<(usize, ast::Item)> = Vec::new();
1284
+ for (idx, item) in source.items.iter().enumerate() {
1285
+ let (name, args) = match item {
1286
+ ast::Item::Enum(e) => match own.get_key_value(e.name.as_str()) {
1287
+ Some((n, a)) => (*n, a),
1288
+ None => continue,
1289
+ },
1290
+ ast::Item::Fn(f) => match f.type_param.as_deref().and_then(|r| own.get_key_value(r)) {
1291
+ Some((n, a)) => (*n, a),
1292
+ None => continue,
1293
+ },
1294
+ _ => continue,
1295
+ };
1296
+ let mut changed = false;
1297
+ let mut fix = |t: &mut ast::Type| apply(t, name, args, &mut changed);
1298
+ let mut item = item.clone();
1299
+ match &mut item {
1300
+ ast::Item::Enum(e) => {
1301
+ e.variants.iter_mut().flat_map(|v| v.fields.iter_mut()).for_each(&mut fix);
1302
+ e.params.iter_mut().for_each(|p| fix(&mut p.ty));
1303
+ }
1304
+ ast::Item::Fn(f) => {
1305
+ for p in &mut f.params {
1306
+ match &mut p.ty {
1307
+ ast::ParamType::Type(t) | ast::ParamType::Variadic(t) | ast::ParamType::Seq(t) => fix(t),
1308
+ ast::ParamType::Fn(ps, ret) => {
1309
+ ps.iter_mut().for_each(&mut fix);
1310
+ if let Some(r) = ret {
1311
+ fix(r);
1312
+ }
1313
+ }
1314
+ }
1315
+ }
1316
+ f.returns.iter_mut().for_each(&mut fix);
1317
+ f.yields.iter_mut().flatten().for_each(&mut fix);
1318
+ if let ast::FnBody::Block(b) = &mut f.body {
1319
+ stmtTypes(&mut b.stmts, &mut fix);
1320
+ }
1321
+ plum_core::forms::mapExprs(&mut f.body, &mut |e| match e {
1322
+ ast::Expr::ClassCall(c) => c.generics.iter_mut().for_each(&mut fix),
1323
+ ast::Expr::Closure(c) => stmtTypes(&mut c.body.stmts, &mut fix),
1324
+ ast::Expr::Catch(c) => {
1325
+ if let ast::CatchHandler::Block { body, .. } = &mut c.handler {
1326
+ stmtTypes(&mut body.stmts, &mut fix);
1327
+ }
1328
+ }
1329
+ _ => {}
1330
+ });
1331
+ }
1332
+ _ => unreachable!(),
1333
+ }
1334
+ if changed {
1335
+ replaced.push((idx, item));
1336
+ }
1337
+ }
1338
+ if replaced.is_empty() {
1339
+ return None;
1340
+ }
1341
+ let mut copy = source.clone();
1342
+ for (idx, item) in replaced {
1343
+ copy.items[idx] = item;
1344
+ }
1345
+ Some(copy)
1346
+ }
1347
+
1224
1348
  /// A generic type named without type arguments (`p: Parcel` for `enum
1225
1349
  /// Parcel[T]`) has no known payload representation at runtime, so it is
1226
1350
  /// rejected in signatures and field types. Inside the generic's own methods the
plum-examples/generics.plum CHANGED
@@ -102,6 +102,44 @@ fun strContentsOr(p: Parcel[Str], default: Int) -> Int =
102
102
  Delivered(v) => v.length()
103
103
  Lost => default
104
104
 
105
+ # Inside a generic type's own methods and fields, the bare name means the
106
+ # type applied to its own parameters: `Crate` is `Crate[T]` there.
107
+ record Crate[T](value: T) =
108
+
109
+ fun same(self) -> Crate =
110
+ self
111
+
112
+ fun pick(self, other: Crate, useOther: Bool) -> Crate =
113
+ if useOther then other else self
114
+
115
+ fun viaLocal(self) -> T =
116
+ c: Crate := self
117
+ kept := List[Crate]()
118
+ kept.push(c)
119
+ kept.at(0).value
120
+
121
+ record Chain[T](v: T, next: Chain?) =
122
+
123
+ fun last(self) -> T =
124
+ cur: Chain := self
125
+ while n := cur.next
126
+ cur = n
127
+ cur.v
128
+
129
+ record Stashed[T](v: T)
130
+
131
+ enum Stash[T] =
132
+ | Stashed[T]
133
+ | Unstashed
134
+
135
+ fun same(self) -> Stash =
136
+ self
137
+
138
+ fun stashedOr(s: Stash[Int], default: Int) -> Int =
139
+ match s.same()
140
+ Stashed(v) => v
141
+ Unstashed => default
142
+
105
143
  test "Int extremes survive a generic fn"
106
144
  big := 9223372036854775807
107
145
  small := 0 - 9223372036854775807 - 1
@@ -279,3 +317,15 @@ test "a generic enum is specialized per type and matched"
279
317
  assert intContentsOr(Lost, 5) == 5
280
318
  assert strContentsOr(Delivered("abcd"), 0) == 4
281
319
  assert intContentsOr(Delivered(13), 0) + strContentsOr(Delivered("abcd"), 0) == 17
320
+
321
+ test "a bare generic type name inside its own methods means its applied form"
322
+ assert Crate(value: 3).same().value == 3
323
+ assert Crate(value: "s").same().value == "s"
324
+ assert Crate(value: 1).pick(Crate(value: 2), true).value == 2
325
+ assert Crate(value: "a").pick(Crate(value: "b"), false).value == "a"
326
+ assert Crate(value: 7).viaLocal() == 7
327
+ assert Crate(value: "x").viaLocal() == "x"
328
+ assert Chain(v: 1, next: Chain(v: 2, next: Chain(v: 3, next: null))).last() == 3
329
+ assert Chain(v: "p", next: Chain(v: "q", next: null)).last() == "q"
330
+ assert stashedOr(Stashed(5), 0) == 5
331
+ assert stashedOr(Unstashed, 9) == 9