plum
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 +124 -0
- plum-examples/generics.plum +50 -0
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
|