plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
5f2f962
— Peter John
2026-09-08T12:01:22+05:30
feat(lang): remove type/class entirely, enum is the only declaration form
- README.md +18 -12
- plum-checker/src/lib.rs +128 -184
- plum-checker/src/monomorphize.rs +158 -367
- plum-checker/tests/checker_tests.rs +49 -82
- plum-checker/tests/monomorphize_tests.rs +2 -27
- plum-core/src/ast.rs +6 -16
- plum-core/src/builtin_usage.rs +0 -5
- plum-core/src/loader.rs +11 -11
- plum-core/src/parser.rs +15 -50
- plum-core/tests/formatter_test.rs +3 -4
- plum-core/tests/loader_test.rs +4 -4
- plum-core/tests/parser_test.rs +8 -8
- plum-examples/closures.plum +2 -2
- plum-examples/functions.plum +6 -6
- plum-examples/match.plum +2 -3
- plum-examples/methods.plum +4 -6
- plum-examples/oop_visitor.plum +8 -17
- plum-examples/types.plum +19 -24
- plum-std/Array.plum +3 -1
- plum-std/Buffer.plum +2 -3
- plum-std/Byte.plum +3 -1
- plum-std/ByteSlice.plum +3 -1
- plum-std/Http.plum +2 -4
- plum-std/Json.plum +4 -6
- plum-std/List.plum +4 -8
- plum-std/Map.plum +4 -6
- plum-std/Str.plum +2 -2
- plum-std/Time.plum +6 -8
- plum-std/Uuid.plum +2 -2
- plum-tooling/tree-sitter-plum/grammar.js +11 -16
- plum-tooling/tree-sitter-plum/queries/plum/format.scm +7 -32
- plum-tooling/tree-sitter-plum/queries/plum/highlights.scm +1 -1
- plum-tooling/tree-sitter-plum/queries/plum/indents.scm +0 -2
- plum-tooling/tree-sitter-plum/queries/plum/tags.scm +0 -3
- plum-tooling/tree-sitter-plum/queries/plum/textobjects.scm +0 -3
- plum-tooling/tree-sitter-plum/src/grammar.json +0 -0
- plum-tooling/tree-sitter-plum/src/node-types.json +0 -0
- plum-tooling/tree-sitter-plum/src/parser.c +0 -0
- plum-tooling/tree-sitter-plum/test/corpus/type.txt +64 -54
- plum-tooling/tree-sitter-plum/test/highlight/sample.plum +2 -3
- plum-wasm-codegen/src/lib.rs +158 -277
- plum-wasm-codegen/tests/codegen_tests.rs +6 -9
README.md
CHANGED
|
@@ -170,13 +170,14 @@ $ plum test plum-examples/testing.plum
|
|
|
170
170
|
|
|
171
171
|
## Types: records, traits, enums
|
|
172
172
|
|
|
173
|
+
There's a single declaration form, `enum` — a record type is just a single-variant enum whose one variant shares the enum's own name:
|
|
174
|
+
|
|
173
175
|
```plum
|
|
174
|
-
|
|
176
|
+
enum Point =
|
|
175
|
-
x: Int
|
|
177
|
+
| Point(x: Int, y: Int)
|
|
176
|
-
y: Int
|
|
177
178
|
|
|
178
|
-
|
|
179
|
+
enum Named(ToStr) = # implements ToStr
|
|
179
|
-
name: Str
|
|
180
|
+
| Named(name: Str)
|
|
180
181
|
|
|
181
182
|
trait Shape =
|
|
182
183
|
area() -> Float
|
|
@@ -203,8 +204,8 @@ enum Step(n: Int) = # a shared field on every variant ...
|
|
|
203
204
|
## Generics
|
|
204
205
|
|
|
205
206
|
```plum
|
|
206
|
-
|
|
207
|
+
enum Box[T] =
|
|
207
|
-
value: T
|
|
208
|
+
| Box(value: T)
|
|
208
209
|
|
|
209
210
|
trait Comparable[T: Ord] = # bounded generic param
|
|
210
211
|
compareTo(other: T) -> Int
|
|
@@ -235,12 +236,11 @@ A closure literal is `|params| body`. Capture is snapshot-by-value: a closure co
|
|
|
235
236
|
|
|
236
237
|
## `self`, field access, and methods
|
|
237
238
|
|
|
238
|
-
A `fun` declared indented directly inside
|
|
239
|
+
A `fun` declared indented directly inside an `enum` body is a method, with an implicit `self`:
|
|
239
240
|
|
|
240
241
|
```plum
|
|
241
|
-
|
|
242
|
+
enum Cat =
|
|
242
|
-
name: Str
|
|
243
|
+
| Cat(name: Str, age: Int)
|
|
243
|
-
age: Int
|
|
244
244
|
fun getAge(self) -> Int =
|
|
245
245
|
self.age
|
|
246
246
|
fun birthday(self) -> Int =
|
|
@@ -259,7 +259,13 @@ fun main() -> Int =
|
|
|
259
259
|
Cat(name: "Whiskers", age: 3)
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
-
Construct a
|
|
262
|
+
Construct a record-shaped enum value by calling its name with `field: value` pairs (any order; every field required).
|
|
263
|
+
|
|
264
|
+
Fields are mutable (`c.age = c.age + 1`), and a Gleam-style spread updates a copy from an existing value, overriding just the fields you name:
|
|
265
|
+
|
|
266
|
+
```plum
|
|
267
|
+
older := Cat(..c, age: c.age + 1)
|
|
268
|
+
```
|
|
263
269
|
|
|
264
270
|
## `match`
|
|
265
271
|
|
plum-checker/src/lib.rs
CHANGED
|
@@ -163,8 +163,6 @@ pub fn lookup(env: &TypeEnv, name: &str) -> Result<PlumType, String> {
|
|
|
163
163
|
.ok_or_else(|| format!("undefined name '{}'", name))
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
/// Field names and types for every `type ClassName = ...` declaration in the source.
|
|
167
|
-
pub type ClassEnv = BTreeMap<String, Vec<(String, PlumType)>>;
|
|
168
166
|
/// `(receiver type, method name) -> TFun` for every `name<Receiver>(...)` method.
|
|
169
167
|
pub type MethodEnv = BTreeMap<(String, String), PlumType>;
|
|
170
168
|
/// Info about one `enum` variant: which enum it belongs to, its 0-based runtime tag
|
|
@@ -221,7 +219,6 @@ pub fn buildMinRequiredArgs(source: &ast::Source) -> MinRequiredArgs {
|
|
|
221
219
|
/// Shared, read-only lookup tables built once from the whole source, threaded through
|
|
222
220
|
/// every check/infer call alongside the (mutable, scope-local) `TypeEnv`.
|
|
223
221
|
pub struct CheckCtx<'a> {
|
|
224
|
-
pub classes: &'a ClassEnv,
|
|
225
222
|
pub methods: &'a MethodEnv,
|
|
226
223
|
pub enum_variants: &'a EnumVariants,
|
|
227
224
|
pub enum_params: &'a EnumParams,
|
|
@@ -232,27 +229,23 @@ pub struct CheckCtx<'a> {
|
|
|
232
229
|
/// signatures, enum variants) from a whole source. Shared by `checkSource` and by
|
|
233
230
|
/// `plum-wasm-codegen`, which needs the same tables to resolve `self`, field access,
|
|
234
231
|
/// and method dispatch during code generation.
|
|
235
|
-
pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv,
|
|
232
|
+
pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, MethodEnv, EnumVariants, EnumParams) {
|
|
236
233
|
let mut global_env: TypeEnv = TypeEnv::new();
|
|
237
|
-
let mut classes: ClassEnv = BTreeMap::new();
|
|
238
234
|
let mut methods: MethodEnv = BTreeMap::new();
|
|
239
235
|
let mut enum_variants: EnumVariants = BTreeMap::new();
|
|
240
236
|
let mut enum_params: EnumParams = BTreeMap::new();
|
|
241
237
|
|
|
242
|
-
// First pass:
|
|
238
|
+
// First pass: find every "record-shaped" enum — exactly one variant, whose
|
|
239
|
+
// name equals the enum's own name (the `type X = ...` replacement shape,
|
|
243
|
-
//
|
|
240
|
+
// e.g. `enum Cat = | Cat(name: Str)`) — so a bare variant elsewhere naming
|
|
244
|
-
//
|
|
241
|
+
// one can wrap it as sugar (see the bare-wrap arm below), regardless of
|
|
245
|
-
// single payload field — regardless of whether the `type` or the `enum`
|
|
246
|
-
// appears first in the file. Resolves `self.field`, `ClassName(...)`, and
|
|
247
|
-
//
|
|
242
|
+
// which declaration appears first in the file.
|
|
248
|
-
|
|
243
|
+
let record_shaped: std::collections::BTreeSet<&str> = source.items.iter()
|
|
249
|
-
|
|
244
|
+
.filter_map(|item| match item {
|
|
250
|
-
let fields = c.fields.iter()
|
|
251
|
-
|
|
245
|
+
ast::Item::Enum(e) if e.variants.len() == 1 && e.variants[0].name == e.name => Some(e.name.as_str()),
|
|
246
|
+
_ => None,
|
|
247
|
+
})
|
|
252
|
-
|
|
248
|
+
.collect();
|
|
253
|
-
classes.insert(c.name.clone(), fields);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
249
|
for item in &source.items {
|
|
257
250
|
match item {
|
|
258
251
|
ast::Item::Enum(e) => {
|
|
@@ -274,31 +267,25 @@ pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, ClassEnv, MethodEnv,
|
|
|
274
267
|
// `plum-std/Number.plum`) — the wrapped payload is the
|
|
275
268
|
// dedicated primitive `PlumType` itself (`TInt`/
|
|
276
269
|
// `TFloat`), not a `TNamed` self-reference. Checked
|
|
277
|
-
// BEFORE the
|
|
270
|
+
// BEFORE the record-shaped bare-wrap case below, since
|
|
278
|
-
// `Int`/`Float` were ALSO registered as zero-field
|
|
279
|
-
// "method-holder" classes (`type Int = fun
|
|
280
|
-
// abs(self)...`, attaching methods to the primitive
|
|
281
|
-
// receiver, before their methods moved into `Number`
|
|
282
|
-
// itself) — this ordering is what made a real
|
|
283
|
-
// primitive win that name collision over the
|
|
284
|
-
// incidentally-same-named class, and stays a
|
|
285
|
-
// defensive safeguard against a future same-named
|
|
286
|
-
// class reintroducing it. This is what lets a bare
|
|
287
|
-
// `Int`/`Float`
|
|
271
|
+
// `Int`/`Float` have no such enum of their own to wrap.
|
|
272
|
+
// This is what lets a bare `Int`/`Float` value be used
|
|
288
|
-
// `Number` is expected with no wrapper
|
|
273
|
+
// directly wherever `Number` is expected with no wrapper
|
|
289
|
-
// `unify`'s matching
|
|
274
|
+
// syntax — see `unify`'s matching
|
|
290
275
|
// primitive-vs-enum arm, and
|
|
291
276
|
// `monomorphize::wrapPrimitiveAgainstExpected`, which
|
|
292
277
|
// does the actual AST rewrite into this variant's
|
|
293
278
|
// constructor at specific expected-type usage sites.
|
|
294
279
|
(vec![plumTypeFromName(&v.name)], Vec::new())
|
|
295
|
-
} else if v.fields.is_empty() &&
|
|
280
|
+
} else if v.fields.is_empty() && e.name != v.name && record_shaped.contains(v.name.as_str()) {
|
|
296
|
-
// Bare-type variant sugar: `|
|
|
281
|
+
// Bare-type variant sugar: `| Cat` (no `[...]`) naming a
|
|
297
|
-
//
|
|
282
|
+
// DIFFERENT, already-declared record-shaped enum means "this
|
|
298
|
-
// wraps one payload field of that
|
|
283
|
+
// variant wraps one payload field of that whole record" —
|
|
299
|
-
// shorthand for the equivalent `|
|
|
284
|
+
// shorthand for the equivalent `| Cat[Cat]`, so `enum Animal
|
|
285
|
+
// = | Cat | Dog` needs no separate wrapper tag distinct from
|
|
286
|
+
// the record it holds. Excludes `e.name == v.name` (a plain
|
|
300
|
-
//
|
|
287
|
+
// payload-free singleton like `enum Foo = | Foo`, which is
|
|
301
|
-
//
|
|
288
|
+
// already exactly what it says — nothing else to wrap).
|
|
302
289
|
(vec![PlumType::TNamed(v.name.clone())], Vec::new())
|
|
303
290
|
} else {
|
|
304
291
|
(v.fields.iter().map(plumTypeFromAst).collect(), v.field_names.clone())
|
|
@@ -359,7 +346,7 @@ pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, ClassEnv, MethodEnv,
|
|
|
359
346
|
}
|
|
360
347
|
}
|
|
361
348
|
|
|
362
|
-
(global_env,
|
|
349
|
+
(global_env, methods, enum_variants, enum_params)
|
|
363
350
|
}
|
|
364
351
|
|
|
365
352
|
/// A `Type`'s name for comparison purposes only — deliberately ignores `.generics`
|
|
@@ -406,21 +393,21 @@ fn checkTraitConformance(source: &ast::Source) -> Vec<CheckError> {
|
|
|
406
393
|
.collect();
|
|
407
394
|
|
|
408
395
|
for item in &source.items {
|
|
409
|
-
let ast::Item::
|
|
396
|
+
let ast::Item::Enum(e) = item else { continue };
|
|
410
|
-
for trait_name in &
|
|
397
|
+
for trait_name in &e.implements {
|
|
411
398
|
let Some(tr) = traits.get(trait_name.as_str()) else { continue };
|
|
412
399
|
for tm in &tr.methods {
|
|
413
|
-
let Some(f) = methods_by_receiver.get(&(
|
|
400
|
+
let Some(f) = methods_by_receiver.get(&(e.name.as_str(), tm.name.as_str())) else {
|
|
414
401
|
errors.push(CheckError {
|
|
415
|
-
message: format!("
|
|
402
|
+
message: format!("enum '{}' claims to implement trait '{}' but is missing method '{}'", e.name, trait_name, tm.name),
|
|
416
403
|
});
|
|
417
404
|
continue;
|
|
418
405
|
};
|
|
419
406
|
if f.params.len() != tm.params.len() {
|
|
420
407
|
errors.push(CheckError {
|
|
421
408
|
message: format!(
|
|
422
|
-
"
|
|
409
|
+
"enum '{}' method '{}' (implementing trait '{}'): expected {} param(s), got {}",
|
|
423
|
-
|
|
410
|
+
e.name, tm.name, trait_name, tm.params.len(), f.params.len()
|
|
424
411
|
),
|
|
425
412
|
});
|
|
426
413
|
continue;
|
|
@@ -430,8 +417,8 @@ fn checkTraitConformance(source: &ast::Source) -> Vec<CheckError> {
|
|
|
430
417
|
if actual != expected {
|
|
431
418
|
errors.push(CheckError {
|
|
432
419
|
message: format!(
|
|
433
|
-
"
|
|
420
|
+
"enum '{}' method '{}' (implementing trait '{}'): param {} ('{}'): expected type '{}', got '{}'",
|
|
434
|
-
|
|
421
|
+
e.name, tm.name, trait_name, i, tp.name,
|
|
435
422
|
expected.unwrap_or("<fn>"), actual.unwrap_or("<fn>")
|
|
436
423
|
),
|
|
437
424
|
});
|
|
@@ -441,16 +428,16 @@ fn checkTraitConformance(source: &ast::Source) -> Vec<CheckError> {
|
|
|
441
428
|
(Some(ft), Some(tt)) if ft.name != tt.name => {
|
|
442
429
|
errors.push(CheckError {
|
|
443
430
|
message: format!(
|
|
444
|
-
"
|
|
431
|
+
"enum '{}' method '{}' (implementing trait '{}'): expected return type '{}', got '{}'",
|
|
445
|
-
|
|
432
|
+
e.name, tm.name, trait_name, tt.name, ft.name
|
|
446
433
|
),
|
|
447
434
|
});
|
|
448
435
|
}
|
|
449
436
|
(None, Some(tt)) => {
|
|
450
437
|
errors.push(CheckError {
|
|
451
438
|
message: format!(
|
|
452
|
-
"
|
|
439
|
+
"enum '{}' method '{}' (implementing trait '{}'): expected return type '{}', got none",
|
|
453
|
-
|
|
440
|
+
e.name, tm.name, trait_name, tt.name
|
|
454
441
|
),
|
|
455
442
|
});
|
|
456
443
|
}
|
|
@@ -467,33 +454,9 @@ pub fn checkSource(source: &ast::Source) -> CheckResult<()> {
|
|
|
467
454
|
// see `checkTraitConformance`'s own doc comment for why.
|
|
468
455
|
let mut errors: Vec<CheckError> = checkTraitConformance(source);
|
|
469
456
|
let source = monomorphize::monomorphizeSource(source).map_err(|e| vec![CheckError { message: e }])?;
|
|
470
|
-
let (global_env,
|
|
457
|
+
let (global_env, methods, enum_variants, enum_params) = buildGlobalTables(&source);
|
|
471
458
|
let min_required = buildMinRequiredArgs(&source);
|
|
472
|
-
let ctx = CheckCtx {
|
|
459
|
+
let ctx = CheckCtx { methods: &methods, enum_variants: &enum_variants, enum_params: &enum_params, min_required: &min_required };
|
|
473
|
-
|
|
474
|
-
// A name that is both a class and an enum variant is ambiguous: `Name(...)`
|
|
475
|
-
// could mean either construction, and downstream code (both the checker's
|
|
476
|
-
// `inferExpr` and codegen) consults `enum_variants` first, so the class
|
|
477
|
-
// constructor would be silently shadowed with no diagnostic. Reject it —
|
|
478
|
-
// UNLESS it's the bare-type variant sugar (`enum Book = | FantasyBook`,
|
|
479
|
-
// see `buildGlobalTables`), which deliberately reuses the class's own name
|
|
480
|
-
// as its variant tag and self-wraps it (`field_types == [TNamed(name)]`).
|
|
481
|
-
for name in classes.keys() {
|
|
482
|
-
if let Some(info) = enum_variants.get(name) {
|
|
483
|
-
// Same exemption, but for a "union" enum's primitive bare-wrap
|
|
484
|
-
// variant (`enum Number = | Int | Float`) — `Int`/`Float` are
|
|
485
|
-
// ALSO registered as zero-field "method-holder" classes (see
|
|
486
|
-
// `buildGlobalTables`'s primitive-bare-wrap comment), so this
|
|
487
|
-
// exact collision is expected and deliberate for them too.
|
|
488
|
-
let is_bare_variant_wrap = info.field_types == [PlumType::TNamed(name.clone())]
|
|
489
|
-
|| info.field_types == [plumTypeFromName(name)];
|
|
490
|
-
if !is_bare_variant_wrap {
|
|
491
|
-
errors.push(CheckError {
|
|
492
|
-
message: format!("'{}' is declared as both a class and an enum variant", name),
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
460
|
|
|
498
461
|
// A discriminant enum (`enum Foo(n: Int) = ...`) requires every variant to supply
|
|
499
462
|
// exactly one value per declared param, unified against that param's type. An
|
|
@@ -933,37 +896,14 @@ fn inferClassCallRaw(call: &ast::ClassCall, env: &TypeEnv, ctx: &CheckCtx) -> Re
|
|
|
933
896
|
}
|
|
934
897
|
}
|
|
935
898
|
}
|
|
936
|
-
match ctx.
|
|
899
|
+
match ctx.enum_variants.get(&call.type_name) {
|
|
937
|
-
Some(fields) => {
|
|
938
|
-
for fa in &call.fields {
|
|
939
|
-
match fields.iter().find(|(n, _)| n == &fa.name) {
|
|
940
|
-
Some((_, expected)) => {
|
|
941
|
-
let actual = inferExpr(&fa.value, env, ctx)?;
|
|
942
|
-
unifyArg(expected, &actual, &fa.value, ctx)
|
|
943
|
-
.map_err(|e| format!("class '{}' field '{}': {}", call.type_name, fa.name, e))?;
|
|
944
|
-
}
|
|
945
|
-
None => return Err(format!("unknown field '{}' on class '{}'", fa.name, call.type_name)),
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
match &call.spread {
|
|
949
|
-
Some(spread) => checkSpreadSource(spread, &call.type_name, &call.type_name, env, ctx)?,
|
|
950
|
-
None => {
|
|
951
|
-
for (field_name, _) in fields {
|
|
952
|
-
if !call.fields.iter().any(|fa| &fa.name == field_name) {
|
|
953
|
-
return Err(format!("class '{}' missing field '{}'", call.type_name, field_name));
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
Ok(PlumType::TNamed(call.type_name.clone()))
|
|
959
|
-
}
|
|
960
|
-
//
|
|
900
|
+
// A NAMED-payload enum variant (`Circle(radius: Int)`, as opposed to the
|
|
961
|
-
//
|
|
901
|
+
// positional/generic-payload form `Some[T]`, which never sets `field_names`
|
|
962
|
-
// the same `Type(field: value, ...)` way a class is; `field_names` is empty
|
|
963
|
-
// for the positional form, so this never fires for it (falls through to the
|
|
964
|
-
// permissive "unmodeled" case below
|
|
902
|
+
// and so falls through to the permissive "unmodeled" case below) — this is
|
|
903
|
+
// ALSO how a record-shaped, single-variant enum (the `type X = ...`
|
|
904
|
+
// replacement, e.g. `enum Cat = | Cat(name: Str)`) constructs, with no
|
|
905
|
+
// separate class-construction path needed.
|
|
965
|
-
|
|
906
|
+
Some(info) if !info.field_names.is_empty() => {
|
|
966
|
-
let info = ctx.enum_variants.get(&call.type_name).expect("just checked is_some");
|
|
967
907
|
for fa in &call.fields {
|
|
968
908
|
match info.field_names.iter().position(|n| n == &fa.name) {
|
|
969
909
|
Some(i) => {
|
|
@@ -987,8 +927,10 @@ fn inferClassCallRaw(call: &ast::ClassCall, env: &TypeEnv, ctx: &CheckCtx) -> Re
|
|
|
987
927
|
}
|
|
988
928
|
Ok(PlumType::TNamed(info.enum_name.clone()))
|
|
989
929
|
}
|
|
930
|
+
// Positional/generic-payload variant (`Some[T]`, constructed via `Expr::FnCall`
|
|
931
|
+
// elsewhere instead), a payload-free variant, or an unmodeled (e.g.
|
|
990
|
-
//
|
|
932
|
+
// builtin/std) type: allow, codegen will catch a genuine mismatch.
|
|
991
|
-
|
|
933
|
+
_ => Ok(PlumType::TNamed(call.type_name.clone())),
|
|
992
934
|
}
|
|
993
935
|
}
|
|
994
936
|
|
|
@@ -1026,6 +968,10 @@ fn checkPattern(pat: &ast::CasePattern, subject_ty: &PlumType, env: &mut TypeEnv
|
|
|
1026
968
|
}
|
|
1027
969
|
}
|
|
1028
970
|
ast::CasePattern::Class { name, fields } => match ctx.enum_variants.get(name) {
|
|
971
|
+
// Also how a record-shaped, single-variant enum (the `type X = ...`
|
|
972
|
+
// replacement) destructures — its one variant already IS the whole
|
|
973
|
+
// value, so there's no tag to check, just its declared fields (in
|
|
974
|
+
// declaration order) to match against, same as any other variant.
|
|
1029
975
|
Some(info) => {
|
|
1030
976
|
if fields.len() != info.field_types.len() {
|
|
1031
977
|
return Err(format!(
|
|
@@ -1038,31 +984,13 @@ fn checkPattern(pat: &ast::CasePattern, subject_ty: &PlumType, env: &mut TypeEnv
|
|
|
1038
984
|
}
|
|
1039
985
|
Ok(())
|
|
1040
986
|
}
|
|
1041
|
-
// Not an enum variant — a PLAIN CLASS is destructured the same way (see
|
|
1042
|
-
// `plum-wasm-codegen`'s matching `compileClassDestructureArm`): every
|
|
1043
|
-
// instance is already "the one variant", so there's no tag to check,
|
|
1044
|
-
// just its declared fields (in declaration order) to match against.
|
|
1045
|
-
None => match ctx.classes.get(name) {
|
|
1046
|
-
Some(class_fields) => {
|
|
1047
|
-
if fields.len() != class_fields.len() {
|
|
1048
|
-
return Err(format!(
|
|
1049
|
-
"constructor pattern '{}' expects {} field(s), got {}",
|
|
1050
|
-
name, class_fields.len(), fields.len()
|
|
1051
|
-
));
|
|
1052
|
-
}
|
|
1053
|
-
for (f, (_, fty)) in fields.iter().zip(class_fields.iter()) {
|
|
1054
|
-
checkPattern(f, fty, env, ctx)?;
|
|
1055
|
-
}
|
|
1056
|
-
Ok(())
|
|
1057
|
-
}
|
|
1058
|
-
|
|
987
|
+
// Unmodeled/builtin variant: allow, codegen will catch.
|
|
1059
|
-
|
|
988
|
+
None => {
|
|
1060
|
-
|
|
989
|
+
for f in fields {
|
|
1061
|
-
|
|
990
|
+
checkPattern(f, &PlumType::TVar("_".to_string()), env, ctx)?;
|
|
1062
|
-
}
|
|
1063
|
-
Ok(())
|
|
1064
991
|
}
|
|
992
|
+
Ok(())
|
|
1065
|
-
}
|
|
993
|
+
}
|
|
1066
994
|
},
|
|
1067
995
|
}
|
|
1068
996
|
}
|
|
@@ -1073,7 +1001,7 @@ fn checkPattern(pat: &ast::CasePattern, subject_ty: &PlumType, env: &mut TypeEnv
|
|
|
1073
1001
|
/// closure assigned to a variable (not passed directly as a call argument,
|
|
1074
1002
|
/// which the checker never even sees as a `Closure` literal at all) gives
|
|
1075
1003
|
/// every param a fresh, unresolved `TVar`, and `TVar` can't be looked up in
|
|
1076
|
-
/// `ctx.
|
|
1004
|
+
/// `ctx.enum_variants` — any field access on it is a hard error, even though the
|
|
1077
1005
|
/// exact same shape works fine once compiled (`plum-wasm-codegen`'s own
|
|
1078
1006
|
/// `resolveClosureParamTypesFromUsage` already does this same resolution, just
|
|
1079
1007
|
/// too late to help the type-CHECK pass, which runs first). Deliberately a
|
|
@@ -1095,10 +1023,10 @@ fn resolveClosureParamFromFieldUsage(
|
|
|
1095
1023
|
ast::Expr::Attribute(a) => {
|
|
1096
1024
|
if let (ast::AttrKind::Field(field_name), ast::Expr::Var(n)) = (&a.attr, &a.object) {
|
|
1097
1025
|
if params.contains(n.as_str()) && !resolved.contains_key(n.as_str()) {
|
|
1098
|
-
let mut matches = ctx.
|
|
1026
|
+
let mut matches = ctx.enum_variants.values()
|
|
1099
|
-
.filter(|
|
|
1027
|
+
.filter(|info| info.field_names.iter().any(|fname| fname == field_name));
|
|
1100
|
-
if let (Some(
|
|
1028
|
+
if let (Some(info), None) = (matches.next(), matches.next()) {
|
|
1101
|
-
resolved.insert(n.clone(), PlumType::TNamed(
|
|
1029
|
+
resolved.insert(n.clone(), PlumType::TNamed(info.enum_name.clone()));
|
|
1102
1030
|
}
|
|
1103
1031
|
}
|
|
1104
1032
|
}
|
|
@@ -1133,39 +1061,46 @@ fn resolveClosureParamFromFieldUsage(
|
|
|
1133
1061
|
/// Resolves `class_name`'s field named `field_name` to its type — shared by
|
|
1134
1062
|
/// `.field` reads (`inferExpr`'s `AttrKind::Field` arm) and `.field = value`
|
|
1135
1063
|
/// writes (`checkStmt`'s `AssignTarget::Field` arm), so mutation gets the same
|
|
1136
|
-
///
|
|
1064
|
+
/// discriminant-enum-param / single-owning-variant fallback chain reads
|
|
1137
|
-
///
|
|
1065
|
+
/// already have. See the call sites' own comments for why each fallback is
|
|
1138
|
-
///
|
|
1066
|
+
/// safe (in particular: exactly one variant may own a given field name; more
|
|
1139
|
-
///
|
|
1067
|
+
/// than one is ambiguous and a compile error) — this also covers a
|
|
1068
|
+
/// record-shaped, single-variant enum (the `type X = ...` replacement), whose
|
|
1069
|
+
/// one variant is trivially its own unique owner of every field it declares.
|
|
1140
|
-
fn lookupFieldType(class_name: &str, field_name: &str, ctx: &CheckCtx) -> Result<PlumType, String> {
|
|
1070
|
+
pub(crate) fn lookupFieldType(class_name: &str, field_name: &str, ctx: &CheckCtx) -> Result<PlumType, String> {
|
|
1141
|
-
match ctx.
|
|
1071
|
+
match ctx.enum_params.get(class_name) {
|
|
1142
|
-
Some(
|
|
1072
|
+
Some(params) => params.iter()
|
|
1143
1073
|
.find(|(n, _)| n == field_name)
|
|
1144
1074
|
.map(|(_, t)| t.clone())
|
|
1145
1075
|
.ok_or_else(|| format!("no field '{}' on type '{}'", field_name, class_name)),
|
|
1146
|
-
None => match ctx.enum_params.get(class_name) {
|
|
1147
|
-
Some(params) => params.iter()
|
|
1148
|
-
.find(|(n, _)| n == field_name)
|
|
1149
|
-
.map(|(_, t)| t.clone())
|
|
1150
|
-
.ok_or_else(|| format!("no field '{}' on type '{}'", field_name, class_name)),
|
|
1151
|
-
|
|
1076
|
+
None => {
|
|
1152
|
-
|
|
1077
|
+
let owners: Vec<&EnumVariantInfo> = ctx.enum_variants.values()
|
|
1153
|
-
|
|
1078
|
+
.filter(|info| info.enum_name == class_name
|
|
1154
|
-
|
|
1079
|
+
&& info.field_names.iter().any(|n| n == field_name))
|
|
1155
|
-
|
|
1080
|
+
.collect();
|
|
1156
|
-
|
|
1081
|
+
match owners.as_slice() {
|
|
1157
|
-
|
|
1082
|
+
[info] => {
|
|
1158
|
-
|
|
1083
|
+
let idx = info.field_names.iter().position(|n| n == field_name).expect("just filtered on this");
|
|
1159
|
-
|
|
1084
|
+
Ok(info.field_types[idx].clone())
|
|
1160
|
-
}
|
|
1161
|
-
[] => Ok(PlumType::TVar("_".to_string())),
|
|
1162
|
-
_ => Err(format!(
|
|
1163
|
-
"field '{}' is ambiguous across multiple variants of enum '{}' — use a match",
|
|
1164
|
-
field_name, class_name
|
|
1165
|
-
)),
|
|
1166
1085
|
}
|
|
1086
|
+
// Empty is genuinely ambiguous between two cases that look identical
|
|
1087
|
+
// from here: `class_name` names a real, known NAMED-payload-capable
|
|
1088
|
+
// enum that simply has no field by this name (a hard error), or it's
|
|
1089
|
+
// an unresolved/unmodeled type name, OR a real enum whose variants
|
|
1090
|
+
// are all positional/payload-free (`enum Option = | Some(Int) |
|
|
1091
|
+
// None`, no `field_names` at all to check against — permissive,
|
|
1092
|
+
// codegen will catch a genuine mismatch, exactly as it always has).
|
|
1093
|
+
// Distinguish by whether any variant belonging to this enum name
|
|
1094
|
+
// declares named fields at all.
|
|
1095
|
+
[] if ctx.enum_variants.values().any(|info| info.enum_name == class_name && !info.field_names.is_empty()) =>
|
|
1096
|
+
Err(format!("no field '{}' on type '{}'", field_name, class_name)),
|
|
1097
|
+
[] => Ok(PlumType::TVar("_".to_string())),
|
|
1098
|
+
_ => Err(format!(
|
|
1099
|
+
"field '{}' is ambiguous across multiple variants of enum '{}' — use a match",
|
|
1100
|
+
field_name, class_name
|
|
1101
|
+
)),
|
|
1167
1102
|
}
|
|
1168
|
-
}
|
|
1103
|
+
}
|
|
1169
1104
|
}
|
|
1170
1105
|
}
|
|
1171
1106
|
|
|
@@ -1310,6 +1245,31 @@ pub fn inferExpr(expr: &ast::Expr, env: &TypeEnv, ctx: &CheckCtx) -> Result<Plum
|
|
|
1310
1245
|
// handling just below, exactly as if this special case didn't
|
|
1311
1246
|
// exist for this call.
|
|
1312
1247
|
}
|
|
1248
|
+
// `Type(1, 2, 3)` — a call with bare POSITIONAL args on a record-shaped
|
|
1249
|
+
// type's name (as opposed to `Type(field: value, ...)`, which parses as a
|
|
1250
|
+
// `ClassCall` instead — see `class_argument_list` vs `fn_argument_list` in
|
|
1251
|
+
// the grammar) — is sugar for `Type.init(1, 2, 3)`, the same constructor
|
|
1252
|
+
// pattern as `Type()`'s own `ClassCall` handling above, letting `init`
|
|
1253
|
+
// take real params (e.g. `List`'s own variadic `init(values: ...T)`)
|
|
1254
|
+
// instead of being limited to zero args. Checked BEFORE the ordinary
|
|
1255
|
+
// positional-variant-construction case just below: a record-shaped
|
|
1256
|
+
// enum's own name is ALSO its one variant's name (see
|
|
1257
|
+
// `buildGlobalTables`), so without this ordering a call meant as
|
|
1258
|
+
// `List.init(1, 2, 3)` could instead misfire as "construct the List
|
|
1259
|
+
// variant positionally" whenever the arg count happens to match its
|
|
1260
|
+
// real field count — a record-shaped type never supported positional
|
|
1261
|
+
// field construction via bare `Type(...)` even before enums/classes
|
|
1262
|
+
// merged into one representation, only through `init` or the named
|
|
1263
|
+
// `Type(field: value, ...)` form.
|
|
1264
|
+
if ctx.enum_variants.get(&call.name).is_some_and(|info| info.enum_name == call.name)
|
|
1265
|
+
&& ctx.methods.contains_key(&(call.name.clone(), "init".to_string()))
|
|
1266
|
+
{
|
|
1267
|
+
let synthetic = ast::Expr::Attribute(Box::new(ast::AttributeExpr {
|
|
1268
|
+
object: ast::Expr::TypeName(call.name.clone()),
|
|
1269
|
+
attr: ast::AttrKind::Method(ast::FnCall { name: "init".to_string(), args: call.args.clone() }),
|
|
1270
|
+
}));
|
|
1271
|
+
return inferExpr(&synthetic, env, ctx);
|
|
1272
|
+
}
|
|
1313
1273
|
if let Some(info) = ctx.enum_variants.get(&call.name) {
|
|
1314
1274
|
if call.args.len() != info.field_types.len() {
|
|
1315
1275
|
return Err(format!(
|
|
@@ -1339,22 +1299,6 @@ pub fn inferExpr(expr: &ast::Expr, env: &TypeEnv, ctx: &CheckCtx) -> Result<Plum
|
|
|
1339
1299
|
}
|
|
1340
1300
|
return Ok(PlumType::TNamed(info.enum_name.clone()));
|
|
1341
1301
|
}
|
|
1342
|
-
// `Type(1, 2, 3)` — a call with bare POSITIONAL args on a class name (as
|
|
1343
|
-
// opposed to `Type(field: value, ...)`, which parses as a `ClassCall`
|
|
1344
|
-
// instead — see `class_argument_list` vs `fn_argument_list` in the
|
|
1345
|
-
// grammar) — is sugar for `Type.init(1, 2, 3)`, the same constructor
|
|
1346
|
-
// pattern as `Type()`'s own `ClassCall` handling above, letting `init`
|
|
1347
|
-
// take real params (e.g. `List`'s own variadic `init(values: ...T)`)
|
|
1348
|
-
// instead of being limited to zero args.
|
|
1349
|
-
if ctx.classes.contains_key(&call.name)
|
|
1350
|
-
&& ctx.methods.contains_key(&(call.name.clone(), "init".to_string()))
|
|
1351
|
-
{
|
|
1352
|
-
let synthetic = ast::Expr::Attribute(Box::new(ast::AttributeExpr {
|
|
1353
|
-
object: ast::Expr::TypeName(call.name.clone()),
|
|
1354
|
-
attr: ast::AttrKind::Method(ast::FnCall { name: "init".to_string(), args: call.args.clone() }),
|
|
1355
|
-
}));
|
|
1356
|
-
return inferExpr(&synthetic, env, ctx);
|
|
1357
|
-
}
|
|
1358
1302
|
match lookup(env, &call.name) {
|
|
1359
1303
|
Ok(PlumType::TFun(param_types, ret)) => {
|
|
1360
1304
|
match param_types.last() {
|
plum-checker/src/monomorphize.rs
CHANGED
|
@@ -4,8 +4,8 @@ use crate::types::PlumType;
|
|
|
4
4
|
|
|
5
5
|
/// A single uppercase letter (`T`, `U`, `K`, ...) is the grammar's only legal
|
|
6
6
|
/// spelling for a generic type parameter — this is how we recognize one, since
|
|
7
|
-
/// `ast::Fn`
|
|
7
|
+
/// `ast::Fn` (unlike `ast::Enum`/`ast::Trait`) carries no explicit generics
|
|
8
|
-
///
|
|
8
|
+
/// declaration list.
|
|
9
9
|
pub fn isGenericParamName(name: &str) -> bool {
|
|
10
10
|
let mut chars = name.chars();
|
|
11
11
|
match (chars.next(), chars.next()) {
|
|
@@ -14,11 +14,6 @@ pub fn isGenericParamName(name: &str) -> bool {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/// The generic parameter names introduced by a `Class`, in declaration order.
|
|
18
|
-
pub fn classGenericParams(c: &ast::Class) -> Vec<String> {
|
|
19
|
-
c.generics.iter().map(|g| g.name.clone()).collect()
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
/// Collects every distinct single-uppercase-letter type name appearing
|
|
23
18
|
/// anywhere in `t`, RECURSING into `t.generics` — so `List[T]` (a container
|
|
24
19
|
/// type applied to the bare letter `T`) is recognized as introducing `T`,
|
|
@@ -54,8 +49,8 @@ pub fn fnGenericParams(f: &ast::Fn) -> Vec<String> {
|
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
/// The generic parameter names an `Enum` introduces — its OWN declared
|
|
57
|
-
/// generics (`enum Option[T] = ...`
|
|
52
|
+
/// generics (`enum Option[T] = ...`) if it declares any, else (for an
|
|
58
|
-
///
|
|
53
|
+
/// enum that doesn't) every distinct
|
|
59
54
|
/// single-uppercase-letter variant field type name, in first-appearance
|
|
60
55
|
/// order — the older, purely-implicit inference this replaces.
|
|
61
56
|
pub fn enumGenericParams(e: &ast::Enum) -> Vec<String> {
|
|
@@ -178,22 +173,6 @@ pub fn mangle(base: &str, type_args: &[PlumType]) -> String {
|
|
|
178
173
|
out
|
|
179
174
|
}
|
|
180
175
|
|
|
181
|
-
/// Produces a concrete, specialized copy of a generic class under `mangled_name`,
|
|
182
|
-
/// substituting every field whose declared type names one of the class's generic
|
|
183
|
-
/// parameters with its resolved concrete type. The class's own `generics` list is
|
|
184
|
-
/// cleared on the copy (it is now fully concrete).
|
|
185
|
-
pub fn specializeClass(c: &ast::Class, subst: &Substitution, mangled_name: &str) -> ast::Class {
|
|
186
|
-
ast::Class {
|
|
187
|
-
name: mangled_name.to_string(),
|
|
188
|
-
implements: c.implements.clone(),
|
|
189
|
-
generics: vec![],
|
|
190
|
-
fields: c.fields.iter().map(|f| ast::Field {
|
|
191
|
-
name: f.name.clone(),
|
|
192
|
-
ty: substituteType(&f.ty, subst),
|
|
193
|
-
}).collect(),
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
176
|
/// Produces a concrete, specialized copy of a generic function (or method) under
|
|
198
177
|
/// `mangled_name`. `new_type_param` overrides the receiver-type name for a method
|
|
199
178
|
/// whose receiver class was itself specialized (e.g. a method declared on `Box`
|
|
@@ -375,6 +354,7 @@ pub fn specializeEnum(e: &ast::Enum, subst: &Substitution, mangled_name: &str) -
|
|
|
375
354
|
ast::Enum {
|
|
376
355
|
name: mangled_name.to_string(),
|
|
377
356
|
generics: vec![],
|
|
357
|
+
implements: e.implements.clone(),
|
|
378
358
|
params: e.params.clone(),
|
|
379
359
|
variants: e.variants.iter().map(|v| ast::EnumVariant {
|
|
380
360
|
name: mangle(&v.name, &type_args),
|
|
@@ -387,10 +367,9 @@ pub fn specializeEnum(e: &ast::Enum, subst: &Substitution, mangled_name: &str) -
|
|
|
387
367
|
|
|
388
368
|
use std::collections::BTreeSet;
|
|
389
369
|
use crate::types::{TypeEnv, TypeScheme};
|
|
390
|
-
use crate::{
|
|
370
|
+
use crate::{MethodEnv, EnumVariants, EnumVariantInfo, EnumParams, CheckCtx};
|
|
391
371
|
|
|
392
372
|
enum PendingSpecialization<'a> {
|
|
393
|
-
Class { base: &'a ast::Class, subst: Substitution, mangled: String },
|
|
394
373
|
Fn { base: &'a ast::Fn, subst: Substitution, mangled: String, new_receiver: Option<String> },
|
|
395
374
|
Enum { base: &'a ast::Enum, subst: Substitution, mangled: String },
|
|
396
375
|
}
|
|
@@ -539,14 +518,10 @@ fn renameVarInArg(arg: &mut ast::Arg, old: &str, new: &str) {
|
|
|
539
518
|
}
|
|
540
519
|
|
|
541
520
|
struct Monomorphizer<'a> {
|
|
542
|
-
classes_generic: BTreeMap<String, &'a ast::Class>,
|
|
543
521
|
fns_generic: BTreeMap<String, &'a ast::Fn>,
|
|
544
|
-
methods_generic_on: BTreeMap<String, Vec<&'a ast::Fn>>,
|
|
545
|
-
///
|
|
522
|
+
/// Every method declared on a generic enum (e.g. `Result`'s `isOk`/`isErr`,
|
|
546
|
-
///
|
|
523
|
+
/// or a record-shaped `List`/`Map`'s own methods), keyed by the enum's own
|
|
547
|
-
/// because the two need separate lookups keyed by their own base-name maps
|
|
548
|
-
/// (`enums_generic_by_name` vs `classes_generic`) at both classification and
|
|
549
|
-
///
|
|
524
|
+
/// bare name.
|
|
550
525
|
methods_generic_on_enum: BTreeMap<String, Vec<&'a ast::Fn>>,
|
|
551
526
|
/// Bare variant name (e.g. `"Some"`) -> the generic `Enum` it belongs to. Keyed
|
|
552
527
|
/// by variant name because a construction site (`Some(5)`) parses as a `FnCall`
|
|
@@ -566,22 +541,21 @@ struct Monomorphizer<'a> {
|
|
|
566
541
|
/// sites like `Some(5)`).
|
|
567
542
|
enums_generic_by_name: BTreeMap<String, &'a ast::Enum>,
|
|
568
543
|
/// Free functions that are NOT generic by `fnGenericParams`'s lowercase-letter
|
|
569
|
-
/// convention, but whose param type(s) bare-name a generic
|
|
544
|
+
/// convention, but whose param type(s) bare-name a generic enum (e.g.
|
|
570
545
|
/// `unwrapOr(o: Option, ...)`) — such a function still needs its own
|
|
571
|
-
/// per-call-site specialization, since its receiver generic
|
|
546
|
+
/// per-call-site specialization, since its receiver generic enum is
|
|
572
547
|
/// dropped from the monomorphized output and the bare name would otherwise
|
|
573
548
|
/// resolve to nothing.
|
|
574
549
|
fns_bare_generic: BTreeMap<String, &'a ast::Fn>,
|
|
575
|
-
/// Mangled
|
|
550
|
+
/// Mangled enum name -> (its own TEMPLATE name, the generic-param
|
|
576
551
|
/// bindings it was specialized with), e.g. `"List$Int" -> ("List", {"T":
|
|
577
|
-
/// TInt})`. Populated wherever
|
|
552
|
+
/// TInt})`. Populated wherever an enum specialization's `mangled`
|
|
578
553
|
/// name is first computed. Used by `resolveMethodOwnGenerics` to look up
|
|
579
554
|
/// a method's OWNER template (to find its original, doubly-generic
|
|
580
555
|
/// `ast::Fn`) and the receiver's own already-known bindings (e.g. `T`),
|
|
581
556
|
/// given only the receiver's mangled type name at a method call site.
|
|
582
557
|
class_specialization_info: BTreeMap<String, (String, BTreeMap<String, PlumType>)>,
|
|
583
558
|
global_env: TypeEnv,
|
|
584
|
-
classes: ClassEnv,
|
|
585
559
|
methods: MethodEnv,
|
|
586
560
|
enum_variants: EnumVariants,
|
|
587
561
|
enum_params: EnumParams,
|
|
@@ -646,7 +620,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
646
620
|
// silently no-op'ing on the very next statement (`l.map(...)`).
|
|
647
621
|
let mut merged_env = self.global_env.clone();
|
|
648
622
|
merged_env.extend(env.iter().map(|(k, v)| (k.clone(), v.clone())));
|
|
649
|
-
let ctx = CheckCtx {
|
|
623
|
+
let ctx = CheckCtx { methods: &self.methods, enum_variants: &self.enum_variants, enum_params: &self.enum_params, min_required: &self.min_required };
|
|
650
624
|
crate::inferExpr(e, &merged_env, &ctx).unwrap_or(PlumType::TVar("_".to_string()))
|
|
651
625
|
}
|
|
652
626
|
|
|
@@ -812,7 +786,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
812
786
|
let needs = match &f.returns {
|
|
813
787
|
None => resolve_return,
|
|
814
788
|
Some(rt) => {
|
|
815
|
-
// A generic
|
|
789
|
+
// A generic enum named WITH concrete type args already
|
|
816
790
|
// (`-> List[Str]`, `-> Result[Int, Str]`) is already fully
|
|
817
791
|
// resolved — only a genuinely BARE reference (`-> List`,
|
|
818
792
|
// `-> Box`, no `[...]` at all) is the "unresolved" shape this
|
|
@@ -824,7 +798,6 @@ impl<'a> Monomorphizer<'a> {
|
|
|
824
798
|
// class name — corrupting an otherwise-correct declaration.
|
|
825
799
|
rt.generics.is_empty()
|
|
826
800
|
&& (isGenericParamName(&rt.name)
|
|
827
|
-
|| self.classes_generic.contains_key(&rt.name)
|
|
828
801
|
|| self.enums_generic_by_name.contains_key(&rt.name))
|
|
829
802
|
}
|
|
830
803
|
};
|
|
@@ -889,25 +862,6 @@ impl<'a> Monomorphizer<'a> {
|
|
|
889
862
|
};
|
|
890
863
|
self.manglePattern(f, fty, field_mangling.as_ref(), case_env, body, guard.as_deref_mut());
|
|
891
864
|
}
|
|
892
|
-
} else if let PlumType::TNamed(concrete_name) = ty {
|
|
893
|
-
// A PLAIN CLASS pattern (`Node(value, prev, next)`, not an enum
|
|
894
|
-
// variant) — `ty` is already this subject's own concrete, possibly
|
|
895
|
-
// mangled class name (whatever resolved it — a field type, a
|
|
896
|
-
// variant's own payload, ...— already went through the same
|
|
897
|
-
// generic-field-type resolution ordinary `.field` ACCESS relies
|
|
898
|
-
// on). Rewrite the pattern's bare/generic name to match (a no-op
|
|
899
|
-
// if it's already concrete, e.g. a non-generic class) — without
|
|
900
|
-
// this, a GENERIC class's pattern name is left as the bare
|
|
901
|
-
// template name (`Node`), which no longer exists in the final
|
|
902
|
-
// monomorphized `ClassEnv` (only `Node$Int`/`Node$Str`/... do),
|
|
903
|
-
// so the checker/codegen's own `ctx.classes` lookup would silently
|
|
904
|
-
// fall back to treating every field as an unresolved `TVar`.
|
|
905
|
-
if let Some(class_fields) = self.classes.get(concrete_name).cloned() {
|
|
906
|
-
*name = concrete_name.clone();
|
|
907
|
-
for (f, (_, fty)) in fields.iter_mut().zip(class_fields.iter()) {
|
|
908
|
-
self.manglePattern(f, fty, None, case_env, body, guard.as_deref_mut());
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
865
|
}
|
|
912
866
|
}
|
|
913
867
|
_ => {}
|
|
@@ -926,9 +880,8 @@ impl<'a> Monomorphizer<'a> {
|
|
|
926
880
|
// written into does.
|
|
927
881
|
if let ast::AssignTarget::Field(object, field_name) = target {
|
|
928
882
|
if let PlumType::TNamed(class_name) = self.infer(object, env) {
|
|
883
|
+
let ctx = CheckCtx { methods: &self.methods, enum_variants: &self.enum_variants, enum_params: &self.enum_params, min_required: &self.min_required };
|
|
929
|
-
if let
|
|
884
|
+
if let Ok(field_ty) = crate::lookupFieldType(&class_name, field_name, &ctx) {
|
|
930
|
-
.and_then(|fields| fields.iter().find(|(n, _)| n == field_name).map(|(_, t)| t.clone()))
|
|
931
|
-
{
|
|
932
885
|
self.resolveBareVariantAgainstExpected(value, &field_ty);
|
|
933
886
|
self.wrapPrimitiveAgainstExpected(value, &field_ty, env);
|
|
934
887
|
}
|
|
@@ -1061,13 +1014,22 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1061
1014
|
Ok(())
|
|
1062
1015
|
}
|
|
1063
1016
|
|
|
1017
|
+
/// Resolves a `Type(field: value, ...)` construction of a generic,
|
|
1018
|
+
/// record-shaped (single-variant, named-payload) enum — the `type X[T] =
|
|
1019
|
+
/// ...` replacement shape, e.g. `List[T]`/`Map[K, V]`. `ClassCall` is the
|
|
1020
|
+
/// shared AST node this call shape ALSO uses for a non-generic or already-
|
|
1021
|
+
/// mangled enum variant's own named-payload construction (see
|
|
1022
|
+
/// `plum-checker`'s `inferClassCallRaw`) — this only fires for one that's
|
|
1023
|
+
/// still a bare, generic TEMPLATE name.
|
|
1064
1024
|
fn resolveClassInstantiation(&mut self, call: &mut ast::ClassCall, env: &TypeEnv) -> Result<(), String> {
|
|
1065
|
-
let Some(
|
|
1025
|
+
let Some(e) = self.enums_generic_by_name.get(call.type_name.as_str()).copied() else { return Ok(()) };
|
|
1066
|
-
let params =
|
|
1026
|
+
let params = enumGenericParams(e);
|
|
1027
|
+
let variant = &e.variants[0];
|
|
1067
1028
|
let mut bindings: BTreeMap<String, PlumType> = BTreeMap::new();
|
|
1068
1029
|
for gp in ¶ms {
|
|
1069
|
-
if let Some(
|
|
1030
|
+
if let Some(idx) = variant.fields.iter().position(|t| t.name == *gp) {
|
|
1031
|
+
let field_name = &variant.field_names[idx];
|
|
1070
|
-
if let Some(fa) = call.fields.iter().find(|fa| fa.name ==
|
|
1032
|
+
if let Some(fa) = call.fields.iter().find(|fa| &fa.name == field_name) {
|
|
1071
1033
|
// A bare payload-free variant reference (`None`) carries
|
|
1072
1034
|
// no type of its own to bind a generic param FROM — skip
|
|
1073
1035
|
// it here; it gets resolved AGAINST the binding (once
|
|
@@ -1079,7 +1041,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1079
1041
|
}
|
|
1080
1042
|
}
|
|
1081
1043
|
}
|
|
1082
|
-
// No field is EVER directly typed as a bare generic param for a
|
|
1044
|
+
// No field is EVER directly typed as a bare generic param for a type
|
|
1083
1045
|
// like `List[T]` (its fields are `Option[Node[T]]`/`Int`, never a bare
|
|
1084
1046
|
// `T`) — and even where one exists, constructing with a payload-free
|
|
1085
1047
|
// value (`List(head: None, ...)`) gives no VALUE to infer a type from
|
|
@@ -1095,7 +1057,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1095
1057
|
// `plumTypeFromAst` alone would silently flatten to the
|
|
1096
1058
|
// bare, unmangled `TNamed("Pair")` (it drops `.generics`
|
|
1097
1059
|
// entirely) — exactly the same recursive resolution
|
|
1098
|
-
// `resolveFieldType` already does for a
|
|
1060
|
+
// `resolveFieldType` already does for a field's own
|
|
1099
1061
|
// declared type, just applied here to an explicit
|
|
1100
1062
|
// call-site type argument instead.
|
|
1101
1063
|
let resolved = self.resolveFieldType(gt);
|
|
@@ -1134,48 +1096,23 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1134
1096
|
call.type_name, call.type_name
|
|
1135
1097
|
));
|
|
1136
1098
|
}
|
|
1137
|
-
let type_args: Vec<PlumType> = params.iter().map(|p| bindings[p].clone()).collect();
|
|
1138
|
-
let mangled =
|
|
1099
|
+
let mangled = self.ensureEnumSpecialized(e, ¶ms, bindings);
|
|
1139
|
-
self.class_specialization_info.entry(mangled.clone()).or_insert_with(|| (call.type_name.clone(), bindings.clone()));
|
|
1140
1100
|
|
|
1141
1101
|
// Now that every generic param is bound, resolve any bare
|
|
1142
|
-
// payload-free-variant field values (`None`) against THIS
|
|
1102
|
+
// payload-free-variant field values (`None`) against THIS
|
|
1143
|
-
//
|
|
1103
|
+
// specialization's own field types — codegen only ever sees the
|
|
1144
|
-
// mangled
|
|
1104
|
+
// mangled specialization (the generic template is dropped), so a
|
|
1145
1105
|
// still-bare `None` would be an unresolvable reference by the time it
|
|
1146
|
-
// gets there. `specializeClass` is a pure function; calling it here
|
|
1147
|
-
// ahead of the worklist actually processing this specialization is
|
|
1148
|
-
// fine — the worklist dedups on `mangled` regardless of how many
|
|
1149
|
-
//
|
|
1106
|
+
// gets there.
|
|
1150
|
-
let
|
|
1107
|
+
if let Some(info) = self.enum_variants.get(&mangled).cloned() {
|
|
1151
|
-
|
|
1108
|
+
for fa in &mut call.fields {
|
|
1152
|
-
|
|
1109
|
+
if let Some(idx) = info.field_names.iter().position(|n| n == &fa.name) {
|
|
1153
|
-
let field_ty = self.resolveFieldType(&field.ty);
|
|
1154
|
-
|
|
1110
|
+
let expected = info.field_types[idx].clone();
|
|
1155
|
-
|
|
1111
|
+
self.resolveBareVariantAgainstExpected(&mut fa.value, &expected);
|
|
1156
|
-
|
|
1112
|
+
self.wrapPrimitiveAgainstExpected(&mut fa.value, &expected, env);
|
|
1113
|
+
}
|
|
1157
1114
|
}
|
|
1158
1115
|
}
|
|
1159
|
-
|
|
1160
|
-
if !self.specialized.contains(&mangled) && !self.enqueued.contains(&mangled) {
|
|
1161
|
-
self.enqueued.insert(mangled.clone());
|
|
1162
|
-
self.worklist.push(PendingSpecialization::Class { base: class, subst: Substitution(bindings.clone()), mangled: mangled.clone() });
|
|
1163
|
-
}
|
|
1164
|
-
// Register this specialization's own field types and its methods'
|
|
1165
|
-
// signatures right now — a construction site like this one can appear
|
|
1166
|
-
// inside an ORDINARY (non-generic) function, which gets rewritten in
|
|
1167
|
-
// the pass BEFORE the worklist above ever runs. Any later statement in
|
|
1168
|
-
// that SAME function body (e.g. `l.get(1)` followed by a `match` on
|
|
1169
|
-
// its result) needs `self.methods`/`self.classes` to already know
|
|
1170
|
-
// about "List$Int" right now, not once the worklist eventually
|
|
1171
|
-
// catches up.
|
|
1172
|
-
if !self.classes.contains_key(&mangled) {
|
|
1173
|
-
let field_types: Vec<(String, PlumType)> = spec_class.fields.iter()
|
|
1174
|
-
.map(|f| (f.name.clone(), crate::plumTypeFromAst(&self.resolveFieldType(&f.ty))))
|
|
1175
|
-
.collect();
|
|
1176
|
-
self.classes.insert(mangled.clone(), field_types);
|
|
1177
|
-
}
|
|
1178
|
-
self.registerClassMethodSignatures(class, &mangled, &bindings);
|
|
1179
1116
|
call.type_name = mangled;
|
|
1180
1117
|
Ok(())
|
|
1181
1118
|
}
|
|
@@ -1212,8 +1149,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1212
1149
|
let mut method_and_bindings: Option<(&'a ast::Fn, BTreeMap<String, PlumType>)> = None;
|
|
1213
1150
|
if let PlumType::TNamed(mangled_receiver) = self.inferConcrete(object, env) {
|
|
1214
1151
|
if let Some((template_name, class_bindings)) = self.class_specialization_info.get(&mangled_receiver).cloned() {
|
|
1215
|
-
let method = self.
|
|
1152
|
+
let method = self.methods_generic_on_enum.get(template_name.as_str())
|
|
1216
|
-
.or_else(|| self.methods_generic_on_enum.get(template_name.as_str()))
|
|
1217
1153
|
.and_then(|methods| methods.iter().find(|m| m.name == call.name).copied());
|
|
1218
1154
|
if let Some(method) = method {
|
|
1219
1155
|
method_and_bindings = Some((method, class_bindings));
|
|
@@ -1251,24 +1187,23 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1251
1187
|
}
|
|
1252
1188
|
|
|
1253
1189
|
/// If `call` invokes a method that has generic param(s) of its OWN, beyond
|
|
1254
|
-
/// its receiver
|
|
1190
|
+
/// its receiver enum's own (e.g. `List[T].map`'s `U`) — a shape
|
|
1255
|
-
/// `
|
|
1191
|
+
/// `registerEnumMethodSignatures`/the `PendingSpecialization::Enum`
|
|
1256
|
-
/// worklist
|
|
1192
|
+
/// worklist arm deliberately skip producing anything for — infers those
|
|
1257
1193
|
/// extra param(s) from `call`'s own arguments (a plain arg's own type for a
|
|
1258
1194
|
/// bare-letter param, or a closure argument's INFERRED BODY TYPE for a
|
|
1259
1195
|
/// closure-typed param whose return is the letter), then specializes+
|
|
1260
1196
|
/// registers a new per-CALL-SITE mangled method (e.g. `"map$Str"`) — the
|
|
1261
1197
|
/// same idea as a truly generic free function's own `fns_generic`/
|
|
1262
1198
|
/// `resolveFnInstantiation`, just combined with the receiver's own already-
|
|
1263
|
-
/// known
|
|
1199
|
+
/// known enum-level bindings (e.g. `T = Int`) — and rewrites `call.name`
|
|
1264
1200
|
/// to it. No-op if `object`'s type isn't a specialization this pass has
|
|
1265
1201
|
/// recorded `class_specialization_info` for, or the method has no extra
|
|
1266
1202
|
/// generics of its own (the ordinary, already-handled case).
|
|
1267
1203
|
fn resolveMethodOwnGenerics(&mut self, object: &ast::Expr, call: &mut ast::FnCall, env: &TypeEnv) -> Result<(), String> {
|
|
1268
1204
|
let PlumType::TNamed(mangled_receiver) = self.inferConcrete(object, env) else { return Ok(()) };
|
|
1269
1205
|
let Some((template_name, class_bindings)) = self.class_specialization_info.get(&mangled_receiver).cloned() else { return Ok(()) };
|
|
1270
|
-
let method = self.
|
|
1206
|
+
let method = self.methods_generic_on_enum.get(template_name.as_str())
|
|
1271
|
-
.or_else(|| self.methods_generic_on_enum.get(template_name.as_str()))
|
|
1272
1207
|
.and_then(|methods| methods.iter().find(|m| m.name == call.name).copied());
|
|
1273
1208
|
let Some(method) = method else { return Ok(()) };
|
|
1274
1209
|
let owner_params: Vec<String> = class_bindings.keys().cloned().collect();
|
|
@@ -1394,22 +1329,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1394
1329
|
Ok(())
|
|
1395
1330
|
}
|
|
1396
1331
|
|
|
1397
|
-
/// Eagerly computes and registers (into `self.methods`) the `(mangled,
|
|
1398
|
-
/// method_name) -> TFun` signature of every method declared on `class`,
|
|
1399
|
-
/// for the specialization named `mangled` under `bindings` — without
|
|
1400
|
-
/// producing the actual `ast::Fn` items (that still only happens once the
|
|
1401
|
-
/// worklist entry for this specialization is popped, avoiding duplicate
|
|
1402
|
-
/// emission). Needed so a call site that appears in a function processed
|
|
1403
|
-
/// BEFORE the worklist runs (see callers) can still resolve a method call
|
|
1404
|
-
/// against this specialization immediately.
|
|
1405
|
-
fn registerClassMethodSignatures(&mut self, class: &'a ast::Class, mangled: &str, bindings: &BTreeMap<String, PlumType>) {
|
|
1406
|
-
let Some(methods) = self.methods_generic_on.get(class.name.as_str()).cloned() else { return };
|
|
1407
|
-
let owner_params = classGenericParams(class);
|
|
1408
|
-
self.registerMethodSignatures(&class.name, mangled, methods, &owner_params, bindings);
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
/// The enum counterpart to `registerClassMethodSignatures` — see its doc
|
|
1412
|
-
///
|
|
1332
|
+
/// Without this, a method call chained directly onto a FRESH
|
|
1413
1333
|
/// enum construction in the same statement (`Some(4).filter(...)`, or
|
|
1414
1334
|
/// any `SomeVariant(...).method(...)` one-liner for a generic enum) could
|
|
1415
1335
|
/// never resolve: the receiver's specialization is only ENQUEUED here,
|
|
@@ -1606,9 +1526,8 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1606
1526
|
if template_name != &declared.name {
|
|
1607
1527
|
return;
|
|
1608
1528
|
}
|
|
1609
|
-
let owner_params: Vec<String> = self.
|
|
1529
|
+
let owner_params: Vec<String> = self.enums_generic_by_name.get(template_name.as_str())
|
|
1610
|
-
.map(|
|
|
1530
|
+
.map(|e| enumGenericParams(e))
|
|
1611
|
-
.or_else(|| self.enums_generic_by_name.get(template_name.as_str()).map(|e| enumGenericParams(e)))
|
|
1612
1531
|
.unwrap_or_default();
|
|
1613
1532
|
for (declared_arg, owner_param) in declared.generics.iter().zip(owner_params.iter()) {
|
|
1614
1533
|
if let Some(bound) = class_bindings.get(owner_param) {
|
|
@@ -1707,7 +1626,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1707
1626
|
continue;
|
|
1708
1627
|
}
|
|
1709
1628
|
let n = &t.name;
|
|
1710
|
-
if
|
|
1629
|
+
if self.enums_generic_by_name.contains_key(n.as_str())
|
|
1711
1630
|
&& !names.iter().any(|x| x == n)
|
|
1712
1631
|
{
|
|
1713
1632
|
names.push(n.clone());
|
|
@@ -1824,24 +1743,25 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1824
1743
|
Ok(())
|
|
1825
1744
|
}
|
|
1826
1745
|
|
|
1827
|
-
/// `Type(1, 2, 3)` — a bare-positional-args call on a GENERIC
|
|
1746
|
+
/// `Type(1, 2, 3)` — a bare-positional-args call on a GENERIC record-shaped
|
|
1828
|
-
/// declares an `init` method (e.g. `List`'s own
|
|
1747
|
+
/// enum name that declares an `init` method (e.g. `List`'s own
|
|
1748
|
+
/// `init(values: ...T)`) — is sugar for `Type.init(1, 2, 3)` (see
|
|
1829
|
-
///
|
|
1749
|
+
/// `plum-checker`'s `inferExpr` / `plum-wasm-codegen`'s matching `FnCall`
|
|
1830
|
-
///
|
|
1750
|
+
/// handling, which run AFTER monomorphization and only ever see concrete,
|
|
1831
|
-
///
|
|
1751
|
+
/// mangled names). Infers `init`'s own generic bindings from `call.args`'
|
|
1832
|
-
///
|
|
1752
|
+
/// types (mirroring `resolveEnumInstantiation`'s args-based inference for
|
|
1833
|
-
///
|
|
1753
|
+
/// a variant's fields), specializes the enum for them, and rewrites
|
|
1834
|
-
///
|
|
1754
|
+
/// `call.name` to the mangled name — same rewrite target
|
|
1835
|
-
/// `resolveClassInstantiation` uses for the named `Type(field: value, ...)`
|
|
1755
|
+
/// `resolveClassInstantiation` uses for the named `Type(field: value, ...)`
|
|
1836
|
-
/// shape, just reached from a differently-shaped call site.
|
|
1756
|
+
/// call shape, just reached from a differently-shaped call site.
|
|
1837
1757
|
fn resolveClassInitInstantiation(&mut self, call: &mut ast::FnCall, env: &TypeEnv) -> Result<(), String> {
|
|
1838
|
-
let Some(
|
|
1758
|
+
let Some(e) = self.enums_generic_by_name.get(call.name.as_str()).copied() else { return Ok(()) };
|
|
1839
|
-
let Some(init_fn) = self.
|
|
1759
|
+
let Some(init_fn) = self.methods_generic_on_enum.get(call.name.as_str())
|
|
1840
1760
|
.and_then(|methods| methods.iter().find(|f| f.name == "init").copied())
|
|
1841
1761
|
else {
|
|
1842
1762
|
return Ok(());
|
|
1843
1763
|
};
|
|
1844
|
-
let params =
|
|
1764
|
+
let params = enumGenericParams(e);
|
|
1845
1765
|
let mut bindings: BTreeMap<String, PlumType> = BTreeMap::new();
|
|
1846
1766
|
for (i, arg) in call.args.iter().enumerate() {
|
|
1847
1767
|
let Some(param) = init_fn.params.get(i).or_else(|| init_fn.params.last()) else { break };
|
|
@@ -1879,21 +1799,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1879
1799
|
if bindings.len() != params.len() {
|
|
1880
1800
|
return Ok(());
|
|
1881
1801
|
}
|
|
1882
|
-
let type_args: Vec<PlumType> = params.iter().map(|p| bindings[p].clone()).collect();
|
|
1883
|
-
let mangled =
|
|
1802
|
+
let mangled = self.ensureEnumSpecialized(e, ¶ms, bindings);
|
|
1884
|
-
self.class_specialization_info.entry(mangled.clone()).or_insert_with(|| (call.name.clone(), bindings.clone()));
|
|
1885
|
-
if !self.specialized.contains(&mangled) && !self.enqueued.contains(&mangled) {
|
|
1886
|
-
self.enqueued.insert(mangled.clone());
|
|
1887
|
-
self.worklist.push(PendingSpecialization::Class { base: class, subst: Substitution(bindings.clone()), mangled: mangled.clone() });
|
|
1888
|
-
}
|
|
1889
|
-
if !self.classes.contains_key(&mangled) {
|
|
1890
|
-
let spec_class = specializeClass(class, &Substitution(bindings.clone()), &mangled);
|
|
1891
|
-
let field_types: Vec<(String, PlumType)> = spec_class.fields.iter()
|
|
1892
|
-
.map(|f| (f.name.clone(), crate::plumTypeFromAst(&self.resolveFieldType(&f.ty))))
|
|
1893
|
-
.collect();
|
|
1894
|
-
self.classes.insert(mangled.clone(), field_types);
|
|
1895
|
-
}
|
|
1896
|
-
self.registerClassMethodSignatures(class, &mangled, &bindings);
|
|
1897
1803
|
call.name = mangled;
|
|
1898
1804
|
Ok(())
|
|
1899
1805
|
}
|
|
@@ -1913,6 +1819,16 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1913
1819
|
let mangled = mangle(&e.name, &type_args);
|
|
1914
1820
|
self.class_specialization_info.entry(mangled.clone()).or_insert_with(|| (e.name.clone(), bindings.clone()));
|
|
1915
1821
|
if !self.enum_variant_mangling.contains_key(&mangled) {
|
|
1822
|
+
// Insert a placeholder table BEFORE recursively resolving any variant's
|
|
1823
|
+
// own field types below — a self-referential generic enum (e.g. a
|
|
1824
|
+
// linked-list `Node[T]`'s own `next: Option[Node[T]]` field) would
|
|
1825
|
+
// otherwise recurse right back into specializing itself here before
|
|
1826
|
+
// this specialization is even marked as in-progress, infinitely. The
|
|
1827
|
+
// recursive re-entry only ever needs this specialization's MANGLED
|
|
1828
|
+
// NAME back (see `resolveFieldType`'s `ast::Type { name: mangled, ...
|
|
1829
|
+
// }` return), not its fully-resolved variant table — that gets
|
|
1830
|
+
// overwritten with the real one below once the recursion unwinds.
|
|
1831
|
+
self.enum_variant_mangling.insert(mangled.clone(), BTreeMap::new());
|
|
1916
1832
|
let mut table = BTreeMap::new();
|
|
1917
1833
|
for (tag, v) in e.variants.iter().enumerate() {
|
|
1918
1834
|
let mangled_variant = mangle(&v.name, &type_args);
|
|
@@ -1955,7 +1871,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1955
1871
|
}
|
|
1956
1872
|
|
|
1957
1873
|
/// Fully resolves a FIELD's declared type into something that will actually
|
|
1958
|
-
/// exist after monomorphization. `
|
|
1874
|
+
/// exist after monomorphization. `specializeEnum`'s own field substitution
|
|
1959
1875
|
/// only replaces a bare generic-param NAME (`T` -> `Int`) — a field declared
|
|
1960
1876
|
/// `Option[Node[T]]` becomes `Option[Node[Int]]` this way, which is now
|
|
1961
1877
|
/// fully CONCRETE but still names the generic TEMPLATES `Option`/`Node`
|
|
@@ -1963,16 +1879,16 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1963
1879
|
/// their mangled specializations, e.g. `Node$Int`, survive). Recursively
|
|
1964
1880
|
/// resolves any nested generic arguments first (so `Node[Int]` inside
|
|
1965
1881
|
/// `Option[Node[Int]]` becomes `Node$Int` before `Option[...]` itself is
|
|
1966
|
-
/// resolved), then — if the type names a known generic
|
|
1882
|
+
/// resolved), then — if the type names a known generic enum applied to
|
|
1967
|
-
///
|
|
1883
|
+
/// arguments — mangles it to that specialization's real name and enqueues
|
|
1968
|
-
///
|
|
1884
|
+
/// the specialization if it hasn't been already, via the same
|
|
1969
|
-
/// `ensureEnumSpecialized` used for enum construction sites
|
|
1885
|
+
/// `ensureEnumSpecialized` used for enum construction sites. A field
|
|
1970
|
-
/// don't have an equivalent shared helper, so that half is inlined here).
|
|
1971
|
-
///
|
|
1886
|
+
/// that's already concrete (no generics), or whose name isn't a known
|
|
1972
|
-
///
|
|
1887
|
+
/// generic template, is returned unchanged (or with just its nested
|
|
1973
|
-
/// generics resolved) — this is ALSO called on every ordinary
|
|
1888
|
+
/// generics resolved) — this is ALSO called on every ordinary
|
|
1974
|
-
///
|
|
1889
|
+
/// (non-generic) enum's fields, not just specialized ones, since a plain
|
|
1975
|
-
/// perfectly well have a field like `items:
|
|
1890
|
+
/// record-shaped enum can perfectly well have a field like `items:
|
|
1891
|
+
/// List[Int]`.
|
|
1976
1892
|
fn resolveFieldType(&mut self, ty: &ast::Type) -> ast::Type {
|
|
1977
1893
|
if ty.generics.is_empty() {
|
|
1978
1894
|
return ty.clone();
|
|
@@ -1992,45 +1908,6 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1992
1908
|
}
|
|
1993
1909
|
let type_args: Vec<PlumType> = resolved_args.iter().map(crate::plumTypeFromAst).collect();
|
|
1994
1910
|
|
|
1995
|
-
if let Some(class) = self.classes_generic.get(ty.name.as_str()).copied() {
|
|
1996
|
-
let params = classGenericParams(class);
|
|
1997
|
-
if params.len() == type_args.len() {
|
|
1998
|
-
let bindings: BTreeMap<String, PlumType> = params.into_iter().zip(type_args.iter().cloned()).collect();
|
|
1999
|
-
let mangled = mangle(&ty.name, &type_args);
|
|
2000
|
-
self.class_specialization_info.entry(mangled.clone()).or_insert_with(|| (ty.name.clone(), bindings.clone()));
|
|
2001
|
-
if !self.specialized.contains(&mangled) && !self.enqueued.contains(&mangled) {
|
|
2002
|
-
self.enqueued.insert(mangled.clone());
|
|
2003
|
-
self.worklist.push(PendingSpecialization::Class { base: class, subst: Substitution(bindings.clone()), mangled: mangled.clone() });
|
|
2004
|
-
}
|
|
2005
|
-
// Register this specialization's field types right now, even
|
|
2006
|
-
// though its actual `Item::Class`/method output is only pushed
|
|
2007
|
-
// to `m.produced` once the worklist entry above is popped —
|
|
2008
|
-
// another method being rewritten in THIS SAME worklist round
|
|
2009
|
-
// (e.g. a sibling method on the class currently being
|
|
2010
|
-
// specialized) may need to resolve a field access against it
|
|
2011
|
-
// immediately, well before that later worklist entry runs.
|
|
2012
|
-
if !self.classes.contains_key(&mangled) {
|
|
2013
|
-
// Insert a placeholder BEFORE recursing into the fields below —
|
|
2014
|
-
// a self-referential class (`Node[T]`'s own `prev`/`next` fields
|
|
2015
|
-
// point back to `Option[Node[T]]`) would otherwise recurse into
|
|
2016
|
-
// resolving its own not-yet-registered specialization forever.
|
|
2017
|
-
// The recursive re-entry only needs this specialization's
|
|
2018
|
-
// MANGLED NAME to build its own field's type, not its fields —
|
|
2019
|
-
// those get filled in for real below once the recursion unwinds.
|
|
2020
|
-
self.classes.insert(mangled.clone(), vec![]);
|
|
2021
|
-
let mut spec_class = specializeClass(class, &Substitution(bindings.clone()), &mangled);
|
|
2022
|
-
for f in &mut spec_class.fields {
|
|
2023
|
-
f.ty = self.resolveFieldType(&f.ty);
|
|
2024
|
-
}
|
|
2025
|
-
self.classes.insert(
|
|
2026
|
-
mangled.clone(),
|
|
2027
|
-
spec_class.fields.iter().map(|f| (f.name.clone(), crate::plumTypeFromAst(&f.ty))).collect(),
|
|
2028
|
-
);
|
|
2029
|
-
self.registerClassMethodSignatures(class, &mangled, &bindings);
|
|
2030
|
-
}
|
|
2031
|
-
return ast::Type { name: mangled, generics: vec![] };
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
1911
|
if let Some(e) = self.enums_generic_by_name.get(ty.name.as_str()).copied() {
|
|
2035
1912
|
let params = enumGenericParams(e);
|
|
2036
1913
|
if params.len() == type_args.len() {
|
|
@@ -2150,16 +2027,14 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2150
2027
|
// "union" enum field (`Wrapper(value: 5)` where `value:
|
|
2151
2028
|
// Number`) or a named-payload enum variant field (`Circle
|
|
2152
2029
|
// (radius: 5)`, though there `radius: Int` already matches so
|
|
2153
|
-
// this is a no-op) —
|
|
2030
|
+
// this is a no-op) — a named variant's OWN field types don't
|
|
2154
|
-
//
|
|
2031
|
+
// depend on generic specialization, so this can run
|
|
2155
|
-
//
|
|
2032
|
+
// unconditionally here regardless of whether `call.type_name`
|
|
2156
|
-
//
|
|
2033
|
+
// turns out to be generic or not below.
|
|
2157
2034
|
for fa in &mut call.fields {
|
|
2158
|
-
let expected_ty = self.classes.get(call.type_name.as_str())
|
|
2159
|
-
.and_then(|fields| fields.iter().find(|(n, _)| n == &fa.name).map(|(_, t)| t.clone()))
|
|
2160
|
-
|
|
2035
|
+
let expected_ty = self.enum_variants.get(call.type_name.as_str()).and_then(|info| {
|
|
2161
|
-
|
|
2036
|
+
info.field_names.iter().position(|n| n == &fa.name).map(|i| info.field_types[i].clone())
|
|
2162
|
-
|
|
2037
|
+
});
|
|
2163
2038
|
if let Some(expected) = expected_ty {
|
|
2164
2039
|
self.wrapPrimitiveAgainstExpected(&mut fa.value, &expected, env);
|
|
2165
2040
|
}
|
|
@@ -2167,7 +2042,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2167
2042
|
// Runs next (before the generic per-field rewrite below): a
|
|
2168
2043
|
// bare payload-free variant field value (`Node(..., next:
|
|
2169
2044
|
// None)`) carries no type of its own to infer a generic
|
|
2170
|
-
// param from, and needs the
|
|
2045
|
+
// param from, and needs the enum's OWN (about-to-be-
|
|
2171
2046
|
// specialized) field type to resolve which specialization it
|
|
2172
2047
|
// actually means — `resolveClassInstantiation` handles that
|
|
2173
2048
|
// internally once it knows the full binding set.
|
|
@@ -2388,20 +2263,17 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2388
2263
|
/// result has no generic syntax left in it — `checkSource`/`compileSource` run
|
|
2389
2264
|
/// on it completely unmodified.
|
|
2390
2265
|
pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
2391
|
-
let (global_env,
|
|
2266
|
+
let (global_env, methods, enum_variants, enum_params) = crate::buildGlobalTables(source);
|
|
2392
2267
|
let min_required = crate::buildMinRequiredArgs(source);
|
|
2393
2268
|
|
|
2394
2269
|
let mut m = Monomorphizer {
|
|
2395
|
-
classes_generic: BTreeMap::new(),
|
|
2396
2270
|
fns_generic: BTreeMap::new(),
|
|
2397
|
-
methods_generic_on: BTreeMap::new(),
|
|
2398
2271
|
methods_generic_on_enum: BTreeMap::new(),
|
|
2399
2272
|
enums_generic_by_variant: BTreeMap::new(),
|
|
2400
2273
|
enums_generic_by_name: BTreeMap::new(),
|
|
2401
2274
|
enum_variant_mangling: BTreeMap::new(),
|
|
2402
2275
|
fns_bare_generic: BTreeMap::new(),
|
|
2403
2276
|
global_env,
|
|
2404
|
-
classes,
|
|
2405
2277
|
methods,
|
|
2406
2278
|
enum_variants,
|
|
2407
2279
|
enum_params,
|
|
@@ -2418,38 +2290,33 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2418
2290
|
};
|
|
2419
2291
|
|
|
2420
2292
|
for item in &source.items {
|
|
2421
|
-
|
|
2293
|
+
if let ast::Item::Enum(e) = item {
|
|
2422
|
-
ast::Item::Class(c) if !c.generics.is_empty() => { m.classes_generic.insert(c.name.clone(), c); }
|
|
2423
|
-
|
|
2294
|
+
if !enumGenericParams(e).is_empty() {
|
|
2424
2295
|
m.enums_generic_by_name.insert(e.name.clone(), e);
|
|
2425
2296
|
for v in &e.variants {
|
|
2426
2297
|
m.enums_generic_by_variant.insert(v.name.clone(), e);
|
|
2427
2298
|
}
|
|
2428
2299
|
}
|
|
2429
|
-
_ => {}
|
|
2430
2300
|
}
|
|
2431
2301
|
}
|
|
2432
|
-
// Classifies every method by its receiver (generic
|
|
2302
|
+
// Classifies every method by its receiver (generic enum, or neither),
|
|
2433
|
-
// populating `
|
|
2303
|
+
// populating `methods_generic_on_enum` — MUST run before the
|
|
2434
|
-
//
|
|
2304
|
+
// `fn_return_generic_enum` loop below, which calls `resolveFieldType` and
|
|
2435
|
-
//
|
|
2305
|
+
// can therefore trigger `registerEnumMethodSignatures` for a generic enum
|
|
2436
|
-
//
|
|
2306
|
+
// as a SIDE EFFECT (e.g. resolving a nested `List[Str]` inside some other
|
|
2437
|
-
//
|
|
2307
|
+
// function's `-> Result[List[Str], Str]` return type).
|
|
2438
|
-
// `
|
|
2308
|
+
// `registerEnumMethodSignatures` looks up `methods_generic_on_enum` and,
|
|
2439
|
-
// it not yet populated, silently registers ZERO methods for that
|
|
2309
|
+
// finding it not yet populated, silently registers ZERO methods for that
|
|
2440
2310
|
// specialization (`Some(methods) = ... else { return }`) — and because the
|
|
2441
|
-
// specialization is marked done at that point (`self.
|
|
2311
|
+
// specialization is marked done at that point (`self.enum_variant_mangling`
|
|
2442
|
-
// the mangled name), no later call ever retries it, permanently
|
|
2312
|
+
// already has the mangled name), no later call ever retries it, permanently
|
|
2443
|
-
// e.g. `List$Str.reject` unresolvable for the rest of this pass
|
|
2313
|
+
// leaving e.g. `List$Str.reject` unresolvable for the rest of this pass
|
|
2444
|
-
// much later, and confusingly, as a chained method call
|
|
2314
|
+
// (surfacing much later, and confusingly, as a chained method call
|
|
2445
|
-
// deep inside some unrelated enum-construction site).
|
|
2315
|
+
// inferring to `TVar` deep inside some unrelated enum-construction site).
|
|
2446
2316
|
for item in &source.items {
|
|
2447
2317
|
if let ast::Item::Fn(f) = item {
|
|
2448
|
-
let receiver_is_generic_class = f.type_param.as_deref().map(|r| m.classes_generic.contains_key(r)).unwrap_or(false);
|
|
2449
2318
|
let receiver_is_generic_enum = f.type_param.as_deref().map(|r| m.enums_generic_by_name.contains_key(r)).unwrap_or(false);
|
|
2450
|
-
if
|
|
2319
|
+
if receiver_is_generic_enum {
|
|
2451
|
-
m.methods_generic_on.entry(f.type_param.clone().unwrap()).or_default().push(f);
|
|
2452
|
-
} else if receiver_is_generic_enum {
|
|
2453
2320
|
m.methods_generic_on_enum.entry(f.type_param.clone().unwrap()).or_default().push(f);
|
|
2454
2321
|
} else if f.type_param.is_none() && !fnGenericParams(f).is_empty() {
|
|
2455
2322
|
m.fns_generic.insert(f.name.clone(), f);
|
|
@@ -2516,7 +2383,7 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2516
2383
|
for item in &source.items {
|
|
2517
2384
|
if let ast::Item::Fn(f) = item {
|
|
2518
2385
|
let receiver_is_generic = f.type_param.as_deref()
|
|
2519
|
-
.map(|r| m.
|
|
2386
|
+
.map(|r| m.enums_generic_by_name.contains_key(r))
|
|
2520
2387
|
.unwrap_or(false);
|
|
2521
2388
|
let is_generic_fn = f.type_param.is_none() && !fnGenericParams(f).is_empty();
|
|
2522
2389
|
let is_bare_generic_fn = f.type_param.is_none() && m.fns_bare_generic.contains_key(f.name.as_str());
|
|
@@ -2548,29 +2415,6 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2548
2415
|
|
|
2549
2416
|
for item in &source.items {
|
|
2550
2417
|
match item {
|
|
2551
|
-
ast::Item::Class(c) if c.generics.is_empty() => {
|
|
2552
|
-
let mut c2 = c.clone();
|
|
2553
|
-
for f in &mut c2.fields {
|
|
2554
|
-
f.ty = m.resolveFieldType(&f.ty);
|
|
2555
|
-
}
|
|
2556
|
-
// `self.classes` was seeded from the ORIGINAL, pre-monomorphization
|
|
2557
|
-
// source (see `buildGlobalTables`, which — like `plumTypeFromAst`
|
|
2558
|
-
// generally — doesn't resolve a field's own nested generics), so a
|
|
2559
|
-
// field like `items: List[Int]` is still recorded there as bare
|
|
2560
|
-
// `List`, not `List$Int`. Refresh it now that `resolveFieldType`
|
|
2561
|
-
// has produced the real, resolved (and, for a generic field,
|
|
2562
|
-
// mangled) type — otherwise any LATER inference of a `ClassCall`
|
|
2563
|
-
// against this class (e.g. `self.infer` on a nested constructor
|
|
2564
|
-
// call, during another item's own monomorphization) unifies the
|
|
2565
|
-
// stale `List` against an actual `List$Int` value and fails, and
|
|
2566
|
-
// that failure then gets silently swallowed by `infer`'s
|
|
2567
|
-
// `unwrap_or(TVar("_"))` fallback further up the call chain.
|
|
2568
|
-
m.classes.insert(
|
|
2569
|
-
c2.name.clone(),
|
|
2570
|
-
c2.fields.iter().map(|f| (f.name.clone(), crate::plumTypeFromAst(&f.ty))).collect(),
|
|
2571
|
-
);
|
|
2572
|
-
m.produced.push(ast::Item::Class(c2));
|
|
2573
|
-
}
|
|
2574
2418
|
ast::Item::Enum(e) if enumGenericParams(e).is_empty() => {
|
|
2575
2419
|
let mut e2 = e.clone();
|
|
2576
2420
|
for v in &mut e2.variants {
|
|
@@ -2579,20 +2423,33 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2579
2423
|
}
|
|
2580
2424
|
}
|
|
2581
2425
|
// Refresh `self.enum_variants` with the resolved field types —
|
|
2582
|
-
//
|
|
2426
|
+
// `self.enum_variants` was seeded from the ORIGINAL,
|
|
2583
|
-
//
|
|
2427
|
+
// pre-monomorphization source (see `buildGlobalTables`, which —
|
|
2584
|
-
//
|
|
2428
|
+
// like `plumTypeFromAst` generally — doesn't resolve a field's
|
|
2585
|
-
//
|
|
2429
|
+
// own nested generics), so a variant field like `List[Json]` is
|
|
2430
|
+
// still recorded there as bare `List`, not `List$Json`.
|
|
2431
|
+
// Otherwise any LATER inference of a construction site for this
|
|
2432
|
+
// variant (e.g. `self.infer` during another item's own
|
|
2433
|
+
// monomorphization) unifies that stale name against the real,
|
|
2586
|
-
//
|
|
2434
|
+
// specialized value's type and fails, and that failure then
|
|
2435
|
+
// gets silently swallowed by `infer`'s `unwrap_or(TVar("_"))`
|
|
2436
|
+
// fallback further up the call chain.
|
|
2587
2437
|
for v in &e2.variants {
|
|
2438
|
+
let existing = m.enum_variants.get(v.name.as_str());
|
|
2588
|
-
let tag =
|
|
2439
|
+
let tag = existing.map(|info| info.tag).unwrap_or(0);
|
|
2589
2440
|
// Bare-type variant sugar (see `buildGlobalTables`): `v.fields`
|
|
2590
|
-
// is empty in the AST for `|
|
|
2441
|
+
// is empty in the AST for `| Cat` or `| Int`, but its resolved
|
|
2591
|
-
// field type was already recorded there
|
|
2442
|
+
// field type was already recorded there (by the initial
|
|
2443
|
+
// `buildGlobalTables` seeding, before this loop runs) as a
|
|
2592
|
-
// self-wrap `TNamed(v.name)`
|
|
2444
|
+
// single self-wrap `TNamed(v.name)` (a record-shaped enum) or
|
|
2445
|
+
// primitive bare-wrap (`Int`/`Float`) — reuse that same shape
|
|
2593
|
-
// instead of clobbering it back to an empty vec.
|
|
2446
|
+
// here instead of clobbering it back to an empty vec.
|
|
2594
|
-
let
|
|
2447
|
+
let existing_field_types = existing.map(|info| info.field_types.clone()).unwrap_or_default();
|
|
2448
|
+
let is_bare_wrap = v.fields.is_empty()
|
|
2595
|
-
(
|
|
2449
|
+
&& (existing_field_types == [PlumType::TNamed(v.name.clone())]
|
|
2450
|
+
|| existing_field_types == [crate::plumTypeFromName(&v.name)]);
|
|
2451
|
+
let (field_types, field_names) = if is_bare_wrap {
|
|
2452
|
+
(existing_field_types, Vec::new())
|
|
2596
2453
|
} else {
|
|
2597
2454
|
(v.fields.iter().map(crate::plumTypeFromAst).collect(), v.field_names.clone())
|
|
2598
2455
|
};
|
|
@@ -2618,7 +2475,7 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2618
2475
|
}
|
|
2619
2476
|
ast::Item::Fn(f) => {
|
|
2620
2477
|
let receiver_is_generic = f.type_param.as_deref()
|
|
2621
|
-
.map(|r| m.
|
|
2478
|
+
.map(|r| m.enums_generic_by_name.contains_key(r))
|
|
2622
2479
|
.unwrap_or(false);
|
|
2623
2480
|
let is_generic_fn = f.type_param.is_none() && !fnGenericParams(f).is_empty();
|
|
2624
2481
|
let is_bare_generic_fn = f.type_param.is_none() && m.fns_bare_generic.contains_key(f.name.as_str());
|
|
@@ -2630,7 +2487,7 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2630
2487
|
m.produced.push(ast::Item::Fn(f2));
|
|
2631
2488
|
}
|
|
2632
2489
|
}
|
|
2633
|
-
_ => {} // generic
|
|
2490
|
+
_ => {} // generic Enum declarations dropped here — templates only
|
|
2634
2491
|
}
|
|
2635
2492
|
}
|
|
2636
2493
|
|
|
@@ -2641,83 +2498,6 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2641
2498
|
return Err("monomorphize: exceeded specialization limit (possible unbounded generic recursion)".to_string());
|
|
2642
2499
|
}
|
|
2643
2500
|
match pending {
|
|
2644
|
-
PendingSpecialization::Class { base, subst, mangled } => {
|
|
2645
|
-
if !m.specialized.insert(mangled.clone()) { continue; }
|
|
2646
|
-
let mut spec_class = specializeClass(base, &subst, &mangled);
|
|
2647
|
-
// `specializeClass` only substitutes a field's bare generic-param
|
|
2648
|
-
// NAME (`T` -> `Int`) — a field like `Option[Node[T]]` becomes
|
|
2649
|
-
// `Option[Node[Int]]`, still a generic instantiation, not yet a
|
|
2650
|
-
// real (mangled) type. Resolve those the rest of the way now.
|
|
2651
|
-
for f in &mut spec_class.fields {
|
|
2652
|
-
f.ty = m.resolveFieldType(&f.ty);
|
|
2653
|
-
}
|
|
2654
|
-
// Register the specialized class's fields so inference inside its
|
|
2655
|
-
// own (and other items') bodies can resolve `receiver.field` on the
|
|
2656
|
-
// mangled type — `self.classes` was built from the ORIGINAL source
|
|
2657
|
-
// and would otherwise not know this freshly-minted class.
|
|
2658
|
-
m.classes.insert(
|
|
2659
|
-
mangled.clone(),
|
|
2660
|
-
spec_class.fields.iter().map(|f| (f.name.clone(), crate::plumTypeFromAst(&f.ty))).collect(),
|
|
2661
|
-
);
|
|
2662
|
-
m.produced.push(ast::Item::Class(spec_class));
|
|
2663
|
-
// Same self-nesting landmine as `registerMethodSignatures` —
|
|
2664
|
-
// if THIS specialization is itself already nested
|
|
2665
|
-
// (`isSelfNested`), skips (only) a method whose return type
|
|
2666
|
-
// would construct yet another nested specialization
|
|
2667
|
-
// (`methodMentionsTemplate`; see `isSelfNested`'s doc
|
|
2668
|
-
// comment). Without this, `List.chunk() -> List[List[T]]`
|
|
2669
|
-
// would recurse into producing an unbounded chain of
|
|
2670
|
-
// ever-more-nested `List` specializations via THIS SAME
|
|
2671
|
-
// loop — and since this loop runs once per separate
|
|
2672
|
-
// WORKLIST pop rather than recursively, a guard scoped to a
|
|
2673
|
-
// single call stack isn't enough on its own.
|
|
2674
|
-
{
|
|
2675
|
-
let already_nested = m.isSelfNested(&base.name, &subst.0);
|
|
2676
|
-
if let Some(methods) = m.methods_generic_on.get(base.name.as_str()).cloned() {
|
|
2677
|
-
let owner_params = classGenericParams(base);
|
|
2678
|
-
for method in methods {
|
|
2679
|
-
// A method with generic param(s) of its OWN (e.g. `map`'s
|
|
2680
|
-
// `U`, beyond `List[T]`'s own `T`) can't be produced HERE —
|
|
2681
|
-
// `U` is only known at a particular CALL SITE, not at class-
|
|
2682
|
-
// specialization time. `resolveMethodOwnGenerics` queues a
|
|
2683
|
-
// `PendingSpecialization::Fn` for each concrete `U` it
|
|
2684
|
-
// actually sees used, instead.
|
|
2685
|
-
if !methodOwnGenericParams(method, &owner_params).is_empty() {
|
|
2686
|
-
continue;
|
|
2687
|
-
}
|
|
2688
|
-
if already_nested && Monomorphizer::methodMentionsTemplate(method, &base.name) {
|
|
2689
|
-
continue;
|
|
2690
|
-
}
|
|
2691
|
-
let mut specialized_method = specializeFn(method, &subst, &method.name, Some(mangled.clone()));
|
|
2692
|
-
// Params (not returns — see `resolveFnParamTypes`'s doc
|
|
2693
|
-
// comment) must run before `rewriteFnBody`/registration
|
|
2694
|
-
// below: both read the signature's types directly off
|
|
2695
|
-
// this AST, and the real checker later rebuilds its own
|
|
2696
|
-
// tables from this exact (post-monomorphize) AST too.
|
|
2697
|
-
m.resolveFnParamTypes(&mut specialized_method);
|
|
2698
|
-
m.rewriteFnBody(&mut specialized_method, true)?;
|
|
2699
|
-
m.resolveFnReturnType(&mut specialized_method);
|
|
2700
|
-
// Register the specialized method's signature under its
|
|
2701
|
-
// (mangled receiver, method name) key so any later body that
|
|
2702
|
-
// dispatches to it can resolve its concrete return type.
|
|
2703
|
-
let param_types: Vec<PlumType> = specialized_method.params.iter().map(|p| match &p.ty {
|
|
2704
|
-
ast::ParamType::Type(t) => crate::plumTypeFromAst(t),
|
|
2705
|
-
ast::ParamType::Variadic(t) => PlumType::TVariadic(Box::new(crate::plumTypeFromAst(t))),
|
|
2706
|
-
ast::ParamType::Fn(params, ret) => {
|
|
2707
|
-
let param_types = params.iter().map(crate::plumTypeFromAst).collect();
|
|
2708
|
-
let ret_ty = ret.as_ref().map(|r| crate::plumTypeFromAst(r)).unwrap_or(PlumType::TUnit);
|
|
2709
|
-
PlumType::TFun(param_types, Box::new(ret_ty))
|
|
2710
|
-
}
|
|
2711
|
-
}).collect();
|
|
2712
|
-
let ret = specialized_method.returns.as_ref()
|
|
2713
|
-
.map(crate::plumTypeFromAst)
|
|
2714
|
-
.unwrap_or(PlumType::TUnit);
|
|
2715
|
-
m.methods.insert((mangled.clone(), specialized_method.name.clone()), PlumType::TFun(param_types, Box::new(ret)));
|
|
2716
|
-
m.produced.push(ast::Item::Fn(specialized_method));
|
|
2717
|
-
}
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
2501
|
PendingSpecialization::Fn { base, subst, mangled, new_receiver } => {
|
|
2722
2502
|
if !m.specialized.insert(mangled.clone()) { continue; }
|
|
2723
2503
|
let mut specialized_fn = specializeFn(base, &subst, &mangled, new_receiver);
|
|
@@ -2747,21 +2527,32 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2747
2527
|
// `specializeEnum` only substitutes a bare generic-param NAME —
|
|
2748
2528
|
// a field like `List[T]` becomes `List[Int]`, still a generic
|
|
2749
2529
|
// instantiation, not yet a real (mangled) type. Resolve those
|
|
2750
|
-
// the rest of the way now
|
|
2530
|
+
// the rest of the way now.
|
|
2751
2531
|
for v in &mut spec_enum.variants {
|
|
2752
2532
|
for f in &mut v.fields {
|
|
2753
2533
|
*f = m.resolveFieldType(f);
|
|
2754
2534
|
}
|
|
2755
2535
|
}
|
|
2756
2536
|
m.produced.push(ast::Item::Enum(spec_enum));
|
|
2757
|
-
// Guarded against the
|
|
2537
|
+
// Guarded against the self-nesting landmine documented on
|
|
2758
|
-
// `
|
|
2538
|
+
// `isSelfNested` — without this, `List.chunk() -> List[List[T]]`
|
|
2539
|
+
// would recurse into producing an unbounded chain of
|
|
2540
|
+
// ever-more-nested `List` specializations via THIS SAME loop —
|
|
2541
|
+
// and since this loop runs once per separate WORKLIST pop
|
|
2542
|
+
// rather than recursively, a guard scoped to a single call
|
|
2543
|
+
// stack isn't enough on its own.
|
|
2759
2544
|
{
|
|
2760
2545
|
let already_nested = m.isSelfNested(&base.name, &subst.0);
|
|
2761
2546
|
if let Some(methods) = m.methods_generic_on_enum.get(base.name.as_str()).cloned() {
|
|
2762
2547
|
let owner_params = enumGenericParams(base);
|
|
2763
2548
|
for method in methods {
|
|
2764
|
-
//
|
|
2549
|
+
// A method with generic param(s) of its OWN (e.g.
|
|
2550
|
+
// `map`'s `U`, beyond `List[T]`'s own `T`) can't be
|
|
2551
|
+
// produced HERE — `U` is only known at a particular
|
|
2552
|
+
// CALL SITE, not at enum-specialization time.
|
|
2553
|
+
// `resolveMethodOwnGenerics` queues a
|
|
2554
|
+
// `PendingSpecialization::Fn` for each concrete `U`
|
|
2555
|
+
// it actually sees used, instead.
|
|
2765
2556
|
if !methodOwnGenericParams(method, &owner_params).is_empty() {
|
|
2766
2557
|
continue;
|
|
2767
2558
|
}
|
plum-checker/tests/checker_tests.rs
CHANGED
|
@@ -6,13 +6,11 @@ use plum_core::{ast::*, AstParser};
|
|
|
6
6
|
|
|
7
7
|
fn emptyCtx() -> CheckCtx<'static> {
|
|
8
8
|
use std::sync::OnceLock;
|
|
9
|
-
static CLASSES: OnceLock<plum_checker::ClassEnv> = OnceLock::new();
|
|
10
9
|
static METHODS: OnceLock<plum_checker::MethodEnv> = OnceLock::new();
|
|
11
10
|
static ENUM_VARIANTS: OnceLock<plum_checker::EnumVariants> = OnceLock::new();
|
|
12
11
|
static ENUM_PARAMS: OnceLock<plum_checker::EnumParams> = OnceLock::new();
|
|
13
12
|
static MIN_REQUIRED: OnceLock<plum_checker::MinRequiredArgs> = OnceLock::new();
|
|
14
13
|
CheckCtx {
|
|
15
|
-
classes: CLASSES.get_or_init(Default::default),
|
|
16
14
|
methods: METHODS.get_or_init(Default::default),
|
|
17
15
|
enum_variants: ENUM_VARIANTS.get_or_init(Default::default),
|
|
18
16
|
enum_params: ENUM_PARAMS.get_or_init(Default::default),
|
|
@@ -147,7 +145,7 @@ fn boolLiteralWrongReturnTypeIsError() {
|
|
|
147
145
|
|
|
148
146
|
#[test]
|
|
149
147
|
fn methodSelfFieldAccessPasses() {
|
|
150
|
-
let src = "
|
|
148
|
+
let src = "enum Cat =\n | Cat(name: Str, age: Int)\n\n fun getName() -> Str =\n self.name\n";
|
|
151
149
|
let source = parse(src);
|
|
152
150
|
let result = checkSource(&source);
|
|
153
151
|
assert!(result.is_ok(), "expected Ok, got {:?}", result.err());
|
|
@@ -155,7 +153,7 @@ fn methodSelfFieldAccessPasses() {
|
|
|
155
153
|
|
|
156
154
|
#[test]
|
|
157
155
|
fn methodSelfUnknownFieldIsError() {
|
|
158
|
-
let src = "
|
|
156
|
+
let src = "enum Cat =\n | Cat(name: Str)\n\n fun getAge() -> Int =\n self.age\n";
|
|
159
157
|
let source = parse(src);
|
|
160
158
|
let result = checkSource(&source);
|
|
161
159
|
assert!(result.is_err());
|
|
@@ -163,14 +161,14 @@ fn methodSelfUnknownFieldIsError() {
|
|
|
163
161
|
|
|
164
162
|
#[test]
|
|
165
163
|
fn nestedMethodTypeChecks() {
|
|
166
|
-
let src = "
|
|
164
|
+
let src = "enum Cat =\n | Cat(name: Str, age: Int)\n\n fun getName(self) -> Str =\n self.name\n";
|
|
167
165
|
let result = checkSource(&parse(src));
|
|
168
166
|
assert!(result.is_ok(), "expected Ok, got {:?}", result.err());
|
|
169
167
|
}
|
|
170
168
|
|
|
171
169
|
#[test]
|
|
172
170
|
fn nestedMethodUnknownFieldIsError() {
|
|
173
|
-
let src = "
|
|
171
|
+
let src = "enum Cat =\n | Cat(name: Str)\n\n fun getAge(self) -> Int =\n self.age\n";
|
|
174
172
|
assert!(checkSource(&parse(src)).is_err());
|
|
175
173
|
}
|
|
176
174
|
|
|
@@ -184,7 +182,7 @@ fn selfOutsideMethodIsError() {
|
|
|
184
182
|
|
|
185
183
|
#[test]
|
|
186
184
|
fn classCallChecksFieldTypes() {
|
|
187
|
-
let src = "
|
|
185
|
+
let src = "enum Cat =\n | Cat(name: Str, age: Int)\n\nfun makeCat() -> Cat =\n Cat(name: \"x\", age: 1)\n";
|
|
188
186
|
let source = parse(src);
|
|
189
187
|
let result = checkSource(&source);
|
|
190
188
|
assert!(result.is_ok(), "expected Ok, got {:?}", result.err());
|
|
@@ -192,7 +190,7 @@ fn classCallChecksFieldTypes() {
|
|
|
192
190
|
|
|
193
191
|
#[test]
|
|
194
192
|
fn classCallWrongFieldTypeIsError() {
|
|
195
|
-
let src = "
|
|
193
|
+
let src = "enum Cat =\n | Cat(name: Str, age: Int)\n\nfun makeCat() -> Cat =\n Cat(name: \"x\", age: \"y\")\n";
|
|
196
194
|
let source = parse(src);
|
|
197
195
|
let result = checkSource(&source);
|
|
198
196
|
assert!(result.is_err());
|
|
@@ -200,7 +198,7 @@ fn classCallWrongFieldTypeIsError() {
|
|
|
200
198
|
|
|
201
199
|
#[test]
|
|
202
200
|
fn classCallUnknownFieldIsError() {
|
|
203
|
-
let src = "
|
|
201
|
+
let src = "enum Cat =\n | Cat(name: Str)\n\nfun makeCat() -> Cat =\n Cat(name: \"x\", age: 1)\n";
|
|
204
202
|
let source = parse(src);
|
|
205
203
|
let result = checkSource(&source);
|
|
206
204
|
assert!(result.is_err());
|
|
@@ -208,7 +206,7 @@ fn classCallUnknownFieldIsError() {
|
|
|
208
206
|
|
|
209
207
|
#[test]
|
|
210
208
|
fn methodCallViaAttributeTypeChecksArgs() {
|
|
211
|
-
let src = "
|
|
209
|
+
let src = "enum Cat =\n | Cat(name: Str)\n\n fun rename(n: Str) -> Str =\n n\n\nfun use(c: Cat) -> Str =\n c.rename(\"x\")\n";
|
|
212
210
|
let source = parse(src);
|
|
213
211
|
let result = checkSource(&source);
|
|
214
212
|
assert!(result.is_ok(), "expected Ok, got {:?}", result.err());
|
|
@@ -435,42 +433,16 @@ fun bad(s: Shape) -> Float =
|
|
|
435
433
|
}
|
|
436
434
|
|
|
437
435
|
#[test]
|
|
438
|
-
fn classNameCollidingWithNonSelfWrapEnumVariantIsAClearError() {
|
|
439
|
-
// `Cat(...)` is ambiguous when `Cat` is both a class and an enum variant
|
|
440
|
-
// whose payload ISN'T that same class (`Cat[Int]`, not bare `Cat`):
|
|
441
|
-
// downstream code consults `enum_variants` first, so the class
|
|
442
|
-
// constructor would otherwise be silently shadowed with no diagnostic.
|
|
443
|
-
|
|
436
|
+
fn bareTypeEnumVariantSugarWrapsTheSameNamedRecordShapedEnum() {
|
|
444
|
-
// `Cat` bare
|
|
437
|
+
// `| Cat` (bare, no `[...]`) naming an already-declared record-shaped enum
|
|
438
|
+
// (the `type Cat = ...` replacement) is sugar for wrapping that whole
|
|
439
|
+
// record as the variant's single payload field — shorthand for `| Cat[Cat]`
|
|
440
|
+
// under a separate tag name. This is the DOP-friendly form: a sealed union
|
|
441
|
+
// of plain records, matching Java's "sealed interface implemented directly
|
|
442
|
+
// by records" shape.
|
|
445
443
|
let src = "\
|
|
446
|
-
|
|
444
|
+
enum Cat =
|
|
447
|
-
name: Str
|
|
445
|
+
| Cat(name: Str)
|
|
448
|
-
|
|
449
|
-
enum Animal =
|
|
450
|
-
| Cat(Int)
|
|
451
|
-
| Dog
|
|
452
|
-
";
|
|
453
|
-
let source = parse(src);
|
|
454
|
-
let result = checkSource(&source);
|
|
455
|
-
assert!(result.is_err(), "expected Err");
|
|
456
|
-
let errs = result.unwrap_err();
|
|
457
|
-
assert!(
|
|
458
|
-
errs.iter().any(|e| e.message.contains("is declared as both a class and an enum variant")),
|
|
459
|
-
"got: {:?}",
|
|
460
|
-
errs
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
#[test]
|
|
465
|
-
fn bareTypeEnumVariantSugarWrapsTheSameNamedClass() {
|
|
466
|
-
// `| Cat` (bare, no `[...]`) naming an already-declared `type Cat` is
|
|
467
|
-
// sugar for wrapping that whole class as the variant's single payload
|
|
468
|
-
// field — shorthand for `| Cat[Cat]` under a separate tag name. This is
|
|
469
|
-
// the DOP-friendly form: a sealed union of plain records, matching
|
|
470
|
-
// Java's "sealed interface implemented directly by records" shape.
|
|
471
|
-
let src = "\
|
|
472
|
-
type Cat =
|
|
473
|
-
name: Str
|
|
474
446
|
|
|
475
447
|
enum Animal =
|
|
476
448
|
| Cat
|
|
@@ -491,8 +463,8 @@ fun catName(a: Animal) -> Str =
|
|
|
491
463
|
#[test]
|
|
492
464
|
fn genericClassInstantiatedAtTwoConcreteTypesTypeChecks() {
|
|
493
465
|
let src = "\
|
|
494
|
-
|
|
466
|
+
enum Box[T] =
|
|
495
|
-
value: T
|
|
467
|
+
| Box(value: T)
|
|
496
468
|
|
|
497
469
|
fun makeIntBox() -> Box =
|
|
498
470
|
Box(value: 5)
|
|
@@ -653,8 +625,8 @@ fun useStr() -> Str =
|
|
|
653
625
|
#[test]
|
|
654
626
|
fn genericMethodOnGenericClassTypeChecks() {
|
|
655
627
|
let src = "\
|
|
656
|
-
|
|
628
|
+
enum Box[T] =
|
|
657
|
-
value: T
|
|
629
|
+
| Box(value: T)
|
|
658
630
|
|
|
659
631
|
fun getValue() -> T =
|
|
660
632
|
self.value
|
|
@@ -679,8 +651,8 @@ fn unboundedRecursiveGenericInstantiationIsAClearError() {
|
|
|
679
651
|
// declaration, which is never itself a call site and so never reaches the
|
|
680
652
|
// worklist at all) and must fail with a clear, bounded error rather than hang.
|
|
681
653
|
let src = "\
|
|
682
|
-
|
|
654
|
+
enum Box[T] =
|
|
683
|
-
value: T
|
|
655
|
+
| Box(value: T)
|
|
684
656
|
|
|
685
657
|
fun recurse(v: T) -> Int =
|
|
686
658
|
b = Box(value: v)
|
|
@@ -736,8 +708,8 @@ fn ordinaryFunctionWithBareGenericClassParamTypeChecks() {
|
|
|
736
708
|
// now, but the identical root cause: `Box` is dropped from the monomorphized
|
|
737
709
|
// output, so a bare `Box`-typed param would otherwise reference nothing.
|
|
738
710
|
let src = "\
|
|
739
|
-
|
|
711
|
+
enum Box[T] =
|
|
740
|
-
value: T
|
|
712
|
+
| Box(value: T)
|
|
741
713
|
|
|
742
714
|
fun getBoxValue() -> T =
|
|
743
715
|
self.value
|
|
@@ -784,9 +756,8 @@ fun each(cb: fn(Int) -> Bool) -> Bool =
|
|
|
784
756
|
#[test]
|
|
785
757
|
fn fieldAssignmentTargetWithMatchingTypePasses() {
|
|
786
758
|
let src = "\
|
|
787
|
-
|
|
759
|
+
enum Cat =
|
|
788
|
-
name: Str
|
|
760
|
+
| Cat(name: Str, age: Int)
|
|
789
|
-
age: Int
|
|
790
761
|
|
|
791
762
|
fun haveBirthday() =
|
|
792
763
|
self.age = self.age + 1
|
|
@@ -798,9 +769,8 @@ type Cat =
|
|
|
798
769
|
#[test]
|
|
799
770
|
fn fieldAssignmentTargetWithMismatchedTypeIsError() {
|
|
800
771
|
let src = "\
|
|
801
|
-
|
|
772
|
+
enum Cat =
|
|
802
|
-
name: Str
|
|
773
|
+
| Cat(name: Str, age: Int)
|
|
803
|
-
age: Int
|
|
804
774
|
|
|
805
775
|
fun breakCat() =
|
|
806
776
|
self.age = \"oops\"
|
|
@@ -813,9 +783,8 @@ type Cat =
|
|
|
813
783
|
#[test]
|
|
814
784
|
fn fieldAssignmentTargetUnknownFieldIsError() {
|
|
815
785
|
let src = "\
|
|
816
|
-
|
|
786
|
+
enum Cat =
|
|
817
|
-
name: Str
|
|
787
|
+
| Cat(name: Str, age: Int)
|
|
818
|
-
age: Int
|
|
819
788
|
|
|
820
789
|
fun breakCat() =
|
|
821
790
|
self.nope = 1
|
|
@@ -981,8 +950,8 @@ fn classImplementingTraitWithMatchingMethodPasses() {
|
|
|
981
950
|
trait Greeter =
|
|
982
951
|
greet(name: Str) -> Str
|
|
983
952
|
|
|
984
|
-
|
|
953
|
+
enum Robot(Greeter) =
|
|
985
|
-
id: Int
|
|
954
|
+
| Robot(id: Int)
|
|
986
955
|
|
|
987
956
|
fun greet(self, name: Str) -> Str =
|
|
988
957
|
\"beep {name}\"
|
|
@@ -997,8 +966,8 @@ fn classClaimingTraitButMissingMethodIsError() {
|
|
|
997
966
|
trait Greeter =
|
|
998
967
|
greet(name: Str) -> Str
|
|
999
968
|
|
|
1000
|
-
|
|
969
|
+
enum Robot(Greeter) =
|
|
1001
|
-
id: Int
|
|
970
|
+
| Robot(id: Int)
|
|
1002
971
|
";
|
|
1003
972
|
let source = parse(src);
|
|
1004
973
|
let result = checkSource(&source);
|
|
@@ -1013,8 +982,8 @@ fn classClaimingTraitWithWrongParamCountIsError() {
|
|
|
1013
982
|
trait Greeter =
|
|
1014
983
|
greet(name: Str) -> Str
|
|
1015
984
|
|
|
1016
|
-
|
|
985
|
+
enum Robot(Greeter) =
|
|
1017
|
-
id: Int
|
|
986
|
+
| Robot(id: Int)
|
|
1018
987
|
|
|
1019
988
|
fun greet(self) -> Str =
|
|
1020
989
|
\"beep\"
|
|
@@ -1032,8 +1001,8 @@ fn classClaimingTraitWithWrongParamTypeIsError() {
|
|
|
1032
1001
|
trait Greeter =
|
|
1033
1002
|
greet(name: Str) -> Str
|
|
1034
1003
|
|
|
1035
|
-
|
|
1004
|
+
enum Robot(Greeter) =
|
|
1036
|
-
id: Int
|
|
1005
|
+
| Robot(id: Int)
|
|
1037
1006
|
|
|
1038
1007
|
fun greet(self, name: Int) -> Str =
|
|
1039
1008
|
\"beep\"
|
|
@@ -1051,8 +1020,8 @@ fn classClaimingTraitWithWrongReturnTypeIsError() {
|
|
|
1051
1020
|
trait Greeter =
|
|
1052
1021
|
greet(name: Str) -> Str
|
|
1053
1022
|
|
|
1054
|
-
|
|
1023
|
+
enum Robot(Greeter) =
|
|
1055
|
-
id: Int
|
|
1024
|
+
| Robot(id: Int)
|
|
1056
1025
|
|
|
1057
1026
|
fun greet(self, name: Str) -> Int =
|
|
1058
1027
|
0
|
|
@@ -1070,8 +1039,8 @@ fn classClaimingUndeclaredTraitNameIsNotEnforced() {
|
|
|
1070
1039
|
// never declared as a real `trait` anywhere — silently unenforced, not an
|
|
1071
1040
|
// error, since there is nothing real to check it against.
|
|
1072
1041
|
let src = "\
|
|
1073
|
-
|
|
1042
|
+
enum Robot(NotARealTrait) =
|
|
1074
|
-
id: Int
|
|
1043
|
+
| Robot(id: Int)
|
|
1075
1044
|
";
|
|
1076
1045
|
let source = parse(src);
|
|
1077
1046
|
assert!(checkSource(&source).is_ok(), "expected Ok, got: {:?}", checkSource(&source));
|
|
@@ -1091,8 +1060,8 @@ enum Option[T] =
|
|
|
1091
1060
|
trait Err =
|
|
1092
1061
|
cause() -> Option
|
|
1093
1062
|
|
|
1094
|
-
|
|
1063
|
+
enum MyErr(Err) =
|
|
1095
|
-
pos: Int
|
|
1064
|
+
| MyErr(pos: Int)
|
|
1096
1065
|
|
|
1097
1066
|
fun cause(self) -> Option[MyErr] =
|
|
1098
1067
|
None
|
|
@@ -1104,9 +1073,8 @@ type MyErr(Err) =
|
|
|
1104
1073
|
#[test]
|
|
1105
1074
|
fn matchDestructuresPlainClassAndBindsFieldTypes() {
|
|
1106
1075
|
let src = "\
|
|
1107
|
-
|
|
1076
|
+
enum Point =
|
|
1108
|
-
x: Int
|
|
1077
|
+
| Point(x: Int, y: Int)
|
|
1109
|
-
y: Int
|
|
1110
1078
|
|
|
1111
1079
|
fun sumPoint(p: Point) -> Int =
|
|
1112
1080
|
match p
|
|
@@ -1119,9 +1087,8 @@ fun sumPoint(p: Point) -> Int =
|
|
|
1119
1087
|
#[test]
|
|
1120
1088
|
fn matchClassPatternWrongFieldCountIsError() {
|
|
1121
1089
|
let src = "\
|
|
1122
|
-
|
|
1090
|
+
enum Point =
|
|
1123
|
-
x: Int
|
|
1091
|
+
| Point(x: Int, y: Int)
|
|
1124
|
-
y: Int
|
|
1125
1092
|
|
|
1126
1093
|
fun sumPoint(p: Point) -> Int =
|
|
1127
1094
|
match p
|
plum-checker/tests/monomorphize_tests.rs
CHANGED
|
@@ -13,17 +13,6 @@ fn isGenericParamNameAcceptsSingleUppercaseLettersOnly() {
|
|
|
13
13
|
assert!(!isGenericParamName(""));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
#[test]
|
|
17
|
-
fn classGenericParamsReadsDeclaredGenericsList() {
|
|
18
|
-
let c = ast::Class {
|
|
19
|
-
name: "Box".to_string(),
|
|
20
|
-
implements: vec![],
|
|
21
|
-
generics: vec![ast::GenericParam { name: "T".to_string(), bounds: vec![] }],
|
|
22
|
-
fields: vec![ast::Field { name: "value".to_string(), ty: ast::Type { name: "T".to_string(), generics: vec![] } }],
|
|
23
|
-
};
|
|
24
|
-
assert_eq!(classGenericParams(&c), vec!["T".to_string()]);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
16
|
#[test]
|
|
28
17
|
fn fnGenericParamsDetectsImplicitUppercaseLetterTypesInOrder() {
|
|
29
18
|
let f = ast::Fn {
|
|
@@ -45,6 +34,7 @@ fn enumGenericParamsDetectsImplicitUppercaseLetterVariantFields() {
|
|
|
45
34
|
let e = ast::Enum {
|
|
46
35
|
name: "Option".to_string(),
|
|
47
36
|
generics: vec![],
|
|
37
|
+
implements: vec![],
|
|
48
38
|
params: vec![],
|
|
49
39
|
variants: vec![
|
|
50
40
|
ast::EnumVariant { name: "Some".to_string(), fields: vec![ast::Type { name: "T".to_string(), generics: vec![] }], field_names: vec![], values: vec![] },
|
|
@@ -61,22 +51,6 @@ fn mangleJoinsBaseNameAndTypeArgs() {
|
|
|
61
51
|
assert_eq!(mangle("Green", &[]), "Green");
|
|
62
52
|
}
|
|
63
53
|
|
|
64
|
-
#[test]
|
|
65
|
-
fn specializeClassSubstitutesGenericFieldTypesAndClearsGenericsList() {
|
|
66
|
-
let c = ast::Class {
|
|
67
|
-
name: "Box".to_string(),
|
|
68
|
-
implements: vec![],
|
|
69
|
-
generics: vec![ast::GenericParam { name: "T".to_string(), bounds: vec![] }],
|
|
70
|
-
fields: vec![ast::Field { name: "value".to_string(), ty: ast::Type { name: "T".to_string(), generics: vec![] } }],
|
|
71
|
-
};
|
|
72
|
-
let mut bindings = std::collections::BTreeMap::new();
|
|
73
|
-
bindings.insert("T".to_string(), PlumType::TInt);
|
|
74
|
-
let specialized = specializeClass(&c, &Substitution(bindings), "Box$Int");
|
|
75
|
-
assert_eq!(specialized.name, "Box$Int");
|
|
76
|
-
assert!(specialized.generics.is_empty());
|
|
77
|
-
assert_eq!(specialized.fields[0].ty.name, "Int");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
54
|
#[test]
|
|
81
55
|
fn specializeFnSubstitutesGenericParamAndReturnTypes() {
|
|
82
56
|
let f = ast::Fn {
|
|
@@ -121,6 +95,7 @@ fn specializeEnumSubstitutesGenericVariantFieldNames() {
|
|
|
121
95
|
let e = ast::Enum {
|
|
122
96
|
name: "Option".to_string(),
|
|
123
97
|
generics: vec![],
|
|
98
|
+
implements: vec![],
|
|
124
99
|
params: vec![],
|
|
125
100
|
variants: vec![
|
|
126
101
|
ast::EnumVariant { name: "Some".to_string(), fields: vec![ast::Type { name: "T".to_string(), generics: vec![] }], field_names: vec![], values: vec![] },
|
plum-core/src/ast.rs
CHANGED
|
@@ -65,7 +65,6 @@ pub struct Import {
|
|
|
65
65
|
|
|
66
66
|
#[derive(Debug, Clone, PartialEq)]
|
|
67
67
|
pub enum Item {
|
|
68
|
-
Class(Class),
|
|
69
68
|
Trait(Trait),
|
|
70
69
|
Enum(Enum),
|
|
71
70
|
Fn(Fn),
|
|
@@ -75,26 +74,12 @@ pub enum Item {
|
|
|
75
74
|
|
|
76
75
|
// ---------- Type definitions ----------
|
|
77
76
|
|
|
78
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
79
|
-
pub struct Class {
|
|
80
|
-
pub name: String,
|
|
81
|
-
pub implements: Vec<String>,
|
|
82
|
-
pub generics: Vec<GenericParam>,
|
|
83
|
-
pub fields: Vec<Field>,
|
|
84
|
-
}
|
|
85
|
-
|
|
86
77
|
#[derive(Debug, Clone, PartialEq)]
|
|
87
78
|
pub struct GenericParam {
|
|
88
79
|
pub name: String,
|
|
89
80
|
pub bounds: Vec<String>,
|
|
90
81
|
}
|
|
91
82
|
|
|
92
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
93
|
-
pub struct Field {
|
|
94
|
-
pub name: String,
|
|
95
|
-
pub ty: Type,
|
|
96
|
-
}
|
|
97
|
-
|
|
98
83
|
#[derive(Debug, Clone, PartialEq)]
|
|
99
84
|
pub struct Trait {
|
|
100
85
|
pub name: String,
|
|
@@ -113,8 +98,13 @@ pub struct TraitMethod {
|
|
|
113
98
|
pub struct Enum {
|
|
114
99
|
pub name: String,
|
|
115
100
|
/// The enum's OWN declared generic type parameters (`enum Option[T] =
|
|
116
|
-
/// ...`)
|
|
101
|
+
/// ...`). Empty for a non-generic enum.
|
|
117
102
|
pub generics: Vec<GenericParam>,
|
|
103
|
+
/// Trait names this enum claims to implement (`enum Cat(ToStr) = ...`).
|
|
104
|
+
/// Mutually exclusive with `params` at the grammar level (see
|
|
105
|
+
/// `grammar.js`'s `enum` rule) — a discriminant enum's shared per-variant
|
|
106
|
+
/// values and a plain enum's trait conformance never coexist.
|
|
107
|
+
pub implements: Vec<String>,
|
|
118
108
|
pub params: Vec<EnumParam>,
|
|
119
109
|
pub variants: Vec<EnumVariant>,
|
|
120
110
|
}
|
plum-core/src/builtin_usage.rs
CHANGED
|
@@ -24,11 +24,6 @@ pub fn usedBuiltinTypeNames(source: &Source) -> HashSet<String> {
|
|
|
24
24
|
let mut names = HashSet::new();
|
|
25
25
|
for item in &source.items {
|
|
26
26
|
match item {
|
|
27
|
-
Item::Class(c) => {
|
|
28
|
-
for f in &c.fields {
|
|
29
|
-
walkType(&f.ty, &mut names);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
27
|
Item::Trait(t) => {
|
|
33
28
|
for m in &t.methods {
|
|
34
29
|
for p in &m.params {
|
plum-core/src/loader.rs
CHANGED
|
@@ -85,15 +85,16 @@ fn checkBuiltinImports(path: &Path, source: &Source) -> Result<(), String> {
|
|
|
85
85
|
if !used.contains(name) {
|
|
86
86
|
continue;
|
|
87
87
|
}
|
|
88
|
-
// A file can also "declare itself"
|
|
88
|
+
// A file can also "declare itself" either as an ordinary enum sharing
|
|
89
|
-
// payload-free variant name of one of its OWN enums (a "union" enum's
|
|
90
|
-
//
|
|
89
|
+
// this exact name (e.g. `enum Str = | Str(data: Buffer) ...`, the
|
|
90
|
+
// record-shaped form `type Str = ...` used to be) or by giving
|
|
91
|
+
// `Int`/`Float` as a bare, payload-free variant name of one of its OWN
|
|
92
|
+
// enums (a "union" enum's primitive bare-wrap sugar, e.g. `enum Number
|
|
91
|
-
// `plum-checker`'s `buildGlobalTables`)
|
|
93
|
+
// = | Int | Float` — see `plum-checker`'s `buildGlobalTables`) —
|
|
92
|
-
//
|
|
94
|
+
// either way, the file is the one DECLARING the type, not merely
|
|
93
|
-
//
|
|
95
|
+
// using it.
|
|
94
96
|
let declares_self = source.items.iter().any(|i| match i {
|
|
95
|
-
Item::Class(c) => c.name == name,
|
|
96
|
-
Item::Enum(e) => e.variants.iter().any(|v| v.name == name && v.fields.is_empty()),
|
|
97
|
+
Item::Enum(e) => e.name == name || e.variants.iter().any(|v| v.name == name && v.fields.is_empty()),
|
|
97
98
|
_ => false,
|
|
98
99
|
});
|
|
99
100
|
if declares_self {
|
|
@@ -205,11 +206,10 @@ fn mergeItems(
|
|
|
205
206
|
/// A collision key that mirrors `plum-checker`'s own separation of
|
|
206
207
|
/// namespaces: methods are keyed by `(receiver, name)` (so `length<Cat>` and
|
|
207
208
|
/// `length<Box>` never collide, exactly like `plum_checker::MethodEnv`),
|
|
208
|
-
/// while
|
|
209
|
+
/// while traits/enums/consts/free-functions are each their own flat,
|
|
209
|
-
///
|
|
210
|
+
/// kind-qualified namespace.
|
|
210
211
|
fn itemNameKey(item: &Item) -> String {
|
|
211
212
|
match item {
|
|
212
|
-
Item::Class(c) => format!("class::{}", c.name),
|
|
213
213
|
Item::Trait(t) => format!("trait::{}", t.name),
|
|
214
214
|
Item::Enum(e) => format!("enum::{}", e.name),
|
|
215
215
|
Item::Const(c) => format!("const::{}", c.name),
|
plum-core/src/parser.rs
CHANGED
|
@@ -65,16 +65,6 @@ impl<'a> AstParser<'a> {
|
|
|
65
65
|
imports.push(self.parseImport(child));
|
|
66
66
|
topLevelOrder.push(TopLevelKind::Import);
|
|
67
67
|
}
|
|
68
|
-
"class" => {
|
|
69
|
-
let c = self.parseClass(child);
|
|
70
|
-
let nested = self.collectNestedFns(child, &c.name);
|
|
71
|
-
items.push(Item::Class(c));
|
|
72
|
-
topLevelOrder.push(TopLevelKind::Other);
|
|
73
|
-
for f in nested {
|
|
74
|
-
items.push(Item::Fn(f));
|
|
75
|
-
topLevelOrder.push(TopLevelKind::Other);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
68
|
"trait" => {
|
|
79
69
|
items.push(Item::Trait(self.parseTrait(child)));
|
|
80
70
|
topLevelOrder.push(TopLevelKind::Other);
|
|
@@ -139,34 +129,6 @@ impl<'a> AstParser<'a> {
|
|
|
139
129
|
.collect()
|
|
140
130
|
}
|
|
141
131
|
|
|
142
|
-
fn parseClass(&self, node: Node) -> Class {
|
|
143
|
-
// class: "type" type_identifier generics? ("(" type_identifier,* ")")? "=" body
|
|
144
|
-
// Named children in order: type_identifier (name), generics? (declaration), type_identifier* (implements), field*
|
|
145
|
-
let named: Vec<Node> = self.namedChildren(node);
|
|
146
|
-
|
|
147
|
-
let name = named.first().map(|n| self.text(*n)).unwrap_or_default();
|
|
148
|
-
|
|
149
|
-
// Skip the optional `generics` declaration node before looking for implements.
|
|
150
|
-
let after_generics = if named.get(1).map(|n| n.kind()) == Some("generics") { 2 } else { 1 };
|
|
151
|
-
|
|
152
|
-
// implements = type_identifiers that appear before any `field` node
|
|
153
|
-
let implements: Vec<String> = named[after_generics..]
|
|
154
|
-
.iter()
|
|
155
|
-
.take_while(|n| n.kind() == "type_identifier")
|
|
156
|
-
.map(|n| self.text(*n))
|
|
157
|
-
.collect();
|
|
158
|
-
|
|
159
|
-
let generics = self.parseGenericsField(node);
|
|
160
|
-
|
|
161
|
-
let fields: Vec<Field> = named
|
|
162
|
-
.iter()
|
|
163
|
-
.filter(|n| n.kind() == "field")
|
|
164
|
-
.map(|n| self.parseField(*n))
|
|
165
|
-
.collect();
|
|
166
|
-
|
|
167
|
-
Class { name, implements, generics, fields }
|
|
168
|
-
}
|
|
169
|
-
|
|
170
132
|
fn parseGenericsField(&self, node: Node) -> Vec<GenericParam> {
|
|
171
133
|
// generics: "[" generic_type,* "]" where generic_type: generic (":" sep1(type_identifier, "+"))?
|
|
172
134
|
//
|
|
@@ -197,16 +159,6 @@ impl<'a> AstParser<'a> {
|
|
|
197
159
|
params
|
|
198
160
|
}
|
|
199
161
|
|
|
200
|
-
fn parseField(&self, node: Node) -> Field {
|
|
201
|
-
// class_field (aliased to field): var_identifier ":" type
|
|
202
|
-
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
|
|
203
|
-
let ty = node
|
|
204
|
-
.named_child(1)
|
|
205
|
-
.map(|n| self.parseType(n))
|
|
206
|
-
.unwrap_or(Type { name: String::new(), generics: vec![] });
|
|
207
|
-
Field { name, ty }
|
|
208
|
-
}
|
|
209
|
-
|
|
210
162
|
fn parseTrait(&self, node: Node) -> Trait {
|
|
211
163
|
// trait: "trait" type_identifier generics? "=" body
|
|
212
164
|
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
|
|
@@ -238,7 +190,20 @@ impl<'a> AstParser<'a> {
|
|
|
238
190
|
}
|
|
239
191
|
|
|
240
192
|
fn parseEnum(&self, node: Node) -> Enum {
|
|
193
|
+
// enum: "enum" type_identifier generics? (implements | params)? "=" body
|
|
194
|
+
// `implements` (bare `type_identifier`,*) and `params` (`enum_param`,*) are
|
|
195
|
+
// mutually exclusive at the grammar level — same comment-safe positional
|
|
196
|
+
// technique `parseClass` used to use for its own `implements` list, since
|
|
197
|
+
// `implements`'s items are bare `type_identifier` nodes, the same kind as
|
|
198
|
+
// the enum's own name at position 0.
|
|
199
|
+
let named: Vec<Node> = self.namedChildren(node);
|
|
241
|
-
let name =
|
|
200
|
+
let name = named.first().map(|n| self.text(*n)).unwrap_or_default();
|
|
201
|
+
let after_generics = if named.get(1).map(|n| n.kind()) == Some("generics") { 2 } else { 1 };
|
|
202
|
+
let implements: Vec<String> = named[after_generics..]
|
|
203
|
+
.iter()
|
|
204
|
+
.take_while(|n| n.kind() == "type_identifier")
|
|
205
|
+
.map(|n| self.text(*n))
|
|
206
|
+
.collect();
|
|
242
207
|
let generics = self.parseGenericsField(node);
|
|
243
208
|
let params = self.childrenOfKind(node, "enum_param")
|
|
244
209
|
.into_iter()
|
|
@@ -248,7 +213,7 @@ impl<'a> AstParser<'a> {
|
|
|
248
213
|
.into_iter()
|
|
249
214
|
.map(|f| self.parseEnumVariant(f))
|
|
250
215
|
.collect();
|
|
251
|
-
Enum { name, generics, params, variants }
|
|
216
|
+
Enum { name, generics, implements, params, variants }
|
|
252
217
|
}
|
|
253
218
|
|
|
254
219
|
fn parseEnumParam(&self, node: Node) -> EnumParam {
|
plum-core/tests/formatter_test.rs
CHANGED
|
@@ -41,9 +41,8 @@ fn keywordsGetSpacedCorrectly() {
|
|
|
41
41
|
module test
|
|
42
42
|
import std/os
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
enum Cat(IAnimal) =
|
|
45
|
-
name: Str
|
|
45
|
+
| Cat(name: Str, age: Int)
|
|
46
|
-
age: Int
|
|
47
46
|
|
|
48
47
|
fun speak(self) -> Str =
|
|
49
48
|
\"meow\"
|
|
@@ -69,7 +68,7 @@ fun main() =
|
|
|
69
68
|
let once = formatSource(input).expect("first pass");
|
|
70
69
|
assert!(once.contains("module test"), "got: {once}");
|
|
71
70
|
assert!(once.contains("import std/os"), "got: {once}");
|
|
72
|
-
assert!(once.contains("
|
|
71
|
+
assert!(once.contains("enum Cat"), "got: {once}");
|
|
73
72
|
assert!(once.contains("fun speak"), "got: {once}");
|
|
74
73
|
assert!(once.contains("if x < 0"), "got: {once}");
|
|
75
74
|
assert!(once.contains("else if x == 0"), "got: {once}");
|
plum-core/tests/loader_test.rs
CHANGED
|
@@ -114,8 +114,8 @@ fn sameMethodNameOnDifferentReceiversAcrossFilesIsNotACollision() {
|
|
|
114
114
|
writeLibFile(&lib_path, "cat", "\
|
|
115
115
|
module fixtures
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
enum Cat =
|
|
118
|
-
age: Unit
|
|
118
|
+
| Cat(age: Unit)
|
|
119
119
|
|
|
120
120
|
fun length(self) -> Unit =
|
|
121
121
|
self.age
|
|
@@ -125,8 +125,8 @@ module fixtures
|
|
|
125
125
|
|
|
126
126
|
import cat
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
enum Box =
|
|
129
|
-
items: Unit
|
|
129
|
+
| Box(items: Unit)
|
|
130
130
|
|
|
131
131
|
fun length(self) -> Unit =
|
|
132
132
|
self.items
|
plum-core/tests/parser_test.rs
CHANGED
|
@@ -26,8 +26,8 @@ fn onlyEnum(source: &Source) -> &Enum {
|
|
|
26
26
|
#[test]
|
|
27
27
|
fn nestedClassMethodsBecomeTopLevelFnItemsWithTypeParamSet() {
|
|
28
28
|
let src = "\
|
|
29
|
-
|
|
29
|
+
enum Cat =
|
|
30
|
-
name: Str
|
|
30
|
+
| Cat(name: Str)
|
|
31
31
|
|
|
32
32
|
fun getName(self) -> Str =
|
|
33
33
|
self.name
|
|
@@ -36,8 +36,8 @@ type Cat =
|
|
|
36
36
|
todo
|
|
37
37
|
";
|
|
38
38
|
let source = parse(src);
|
|
39
|
-
assert_eq!(source.items.len(), 3, "class + 2 nested methods, in order");
|
|
39
|
+
assert_eq!(source.items.len(), 3, "class-shaped enum + 2 nested methods, in order");
|
|
40
|
-
let Item::
|
|
40
|
+
let Item::Enum(class) = &source.items[0] else { panic!("expected an Enum item first") };
|
|
41
41
|
assert_eq!(class.name, "Cat");
|
|
42
42
|
let Item::Fn(get_name) = &source.items[1] else { panic!("expected getName immediately after the class") };
|
|
43
43
|
assert_eq!(get_name.name, "getName");
|
|
@@ -87,8 +87,8 @@ enum Step(n: Int) =
|
|
|
87
87
|
#[test]
|
|
88
88
|
fn classAndEnumWithNoNestedMethodsProduceNoExtraFnItems() {
|
|
89
89
|
let src = "\
|
|
90
|
-
|
|
90
|
+
enum Dog =
|
|
91
|
-
name: Str
|
|
91
|
+
| Dog(name: Str)
|
|
92
92
|
|
|
93
93
|
enum Bool =
|
|
94
94
|
| True
|
|
@@ -96,7 +96,7 @@ enum Bool =
|
|
|
96
96
|
";
|
|
97
97
|
let source = parse(src);
|
|
98
98
|
assert_eq!(source.items.len(), 2, "no nested methods means no extra Item::Fn entries");
|
|
99
|
-
assert!(matches!(source.items[0], Item::
|
|
99
|
+
assert!(matches!(source.items[0], Item::Enum(_)));
|
|
100
100
|
assert!(matches!(source.items[1], Item::Enum(_)));
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -144,7 +144,7 @@ fn fnWithoutReceiverButWithReturnTypeHasNoTypeParam() {
|
|
|
144
144
|
|
|
145
145
|
#[test]
|
|
146
146
|
fn nestedMethodWithReturnTypeHasCorrectTypeParam() {
|
|
147
|
-
let src = "
|
|
147
|
+
let src = "enum Cat =\n | Cat(name: Str)\n\n fun toStr() -> Str =\n \"x\"\n";
|
|
148
148
|
let source = parse(src);
|
|
149
149
|
let f = onlyFn(&source);
|
|
150
150
|
assert_eq!(f.type_param, Some("Cat".to_string()));
|
plum-examples/closures.plum
CHANGED
|
@@ -30,8 +30,8 @@ fun useFloatClosure() -> Float =
|
|
|
30
30
|
offset + v
|
|
31
31
|
eachF(floatCb)
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
enum ClosureCat =
|
|
34
|
-
age: Int
|
|
34
|
+
| ClosureCat(age: Int)
|
|
35
35
|
|
|
36
36
|
fun eachCat(cb: fn(ClosureCat) -> Int) -> Int =
|
|
37
37
|
cb(ClosureCat(age: 7))
|
plum-examples/functions.plum
CHANGED
|
@@ -43,8 +43,8 @@ fun factorial(x: Int) -> Int =
|
|
|
43
43
|
fun double(n: Int) -> Int =
|
|
44
44
|
n * 2
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
enum GenericBox[T] =
|
|
47
|
-
value: T
|
|
47
|
+
| GenericBox(value: T)
|
|
48
48
|
|
|
49
49
|
fun getIntValue() -> Int =
|
|
50
50
|
self.value
|
|
@@ -56,8 +56,8 @@ fun useGenericBox() -> Int =
|
|
|
56
56
|
fun identity(value: T) -> T =
|
|
57
57
|
value
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
enum GenericBox2[T] =
|
|
60
|
-
value: T
|
|
60
|
+
| GenericBox2(value: T)
|
|
61
61
|
|
|
62
62
|
fun getValue() -> Int =
|
|
63
63
|
self.value
|
|
@@ -87,8 +87,8 @@ fun unwrapFnOptionStrOr(o: FnOption, default: Int) -> Int =
|
|
|
87
87
|
FnSome(v) => 4
|
|
88
88
|
FnNone => default
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
enum GenericBox3[T] =
|
|
91
|
-
value: T
|
|
91
|
+
| GenericBox3(value: T)
|
|
92
92
|
|
|
93
93
|
fun getBoxValue() -> Int =
|
|
94
94
|
self.value
|
plum-examples/match.plum
CHANGED
|
@@ -138,9 +138,8 @@ fun bothOptions(a: Option, b: Option) -> Int =
|
|
|
138
138
|
Some(x), Some(y) => x + y
|
|
139
139
|
_, _ => 0
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
enum Point =
|
|
142
|
-
x: Int
|
|
142
|
+
| Point(x: Int, y: Int)
|
|
143
|
-
y: Int
|
|
144
143
|
|
|
145
144
|
fun sum(self) -> Int =
|
|
146
145
|
match self
|
plum-examples/methods.plum
CHANGED
|
@@ -2,9 +2,8 @@ import std/Str
|
|
|
2
2
|
import std/Bool
|
|
3
3
|
import std/Number
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
enum Cat =
|
|
6
|
-
name: Str
|
|
6
|
+
| Cat(name: Str, age: Int)
|
|
7
|
-
age: Int
|
|
8
7
|
|
|
9
8
|
fun getAge(self) -> Int =
|
|
10
9
|
self.age
|
|
@@ -12,9 +11,8 @@ type Cat =
|
|
|
12
11
|
fun birthday(self) -> Int =
|
|
13
12
|
self.age + 1
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
enum Wrapper =
|
|
16
|
-
inner: Cat
|
|
15
|
+
| Wrapper(inner: Cat, tag: Int)
|
|
17
|
-
tag: Int
|
|
18
16
|
|
|
19
17
|
fun innerAge(self) -> Int =
|
|
20
18
|
self.inner.age
|
plum-examples/oop_visitor.plum
CHANGED
|
@@ -41,10 +41,8 @@ enum Rating =
|
|
|
41
41
|
trait Book =
|
|
42
42
|
collectInterestingInfo() -> Str
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
enum FantasyBook(Book) =
|
|
45
|
-
title: Str
|
|
46
|
-
pages: Int
|
|
47
|
-
hasMythicalCreatures: Bool
|
|
45
|
+
| FantasyBook(title: Str, pages: Int, hasMythicalCreatures: Bool)
|
|
48
46
|
|
|
49
47
|
fun collectInterestingInfo(self) -> Str =
|
|
50
48
|
if self.hasMythicalCreatures
|
|
@@ -52,10 +50,8 @@ type FantasyBook(Book) =
|
|
|
52
50
|
else
|
|
53
51
|
"Fantasy book \"{self.title}\" has no mythical creatures"
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
enum ScifiBook(Book) =
|
|
56
|
-
title: Str
|
|
54
|
+
| ScifiBook(title: Str, pages: Int, theme: Str)
|
|
57
|
-
pages: Int
|
|
58
|
-
theme: Str
|
|
59
55
|
|
|
60
56
|
fun collectInterestingInfo(self) -> Str =
|
|
61
57
|
if self.theme == "space exploration"
|
|
@@ -63,10 +59,8 @@ type ScifiBook(Book) =
|
|
|
63
59
|
else
|
|
64
60
|
"Scifi book \"{self.title}\" has theme {self.theme}"
|
|
65
61
|
|
|
66
|
-
|
|
62
|
+
enum ChildrensTaleBook(Book) =
|
|
67
|
-
title: Str
|
|
68
|
-
pages: Int
|
|
69
|
-
moralLesson: Str
|
|
63
|
+
| ChildrensTaleBook(title: Str, pages: Int, moralLesson: Str)
|
|
70
64
|
|
|
71
65
|
fun collectInterestingInfo(self) -> Str =
|
|
72
66
|
if self.pages == 0
|
|
@@ -74,11 +68,8 @@ type ChildrensTaleBook(Book) =
|
|
|
74
68
|
else
|
|
75
69
|
"Childrens book \"{self.title}\" teaches: {self.moralLesson}"
|
|
76
70
|
|
|
77
|
-
|
|
71
|
+
enum NonFictionBook(Book) =
|
|
78
|
-
title: Str
|
|
79
|
-
pages: Int
|
|
80
|
-
rating1: Rating
|
|
72
|
+
| NonFictionBook(title: Str, pages: Int, rating1: Rating, rating2: Rating)
|
|
81
|
-
rating2: Rating
|
|
82
73
|
|
|
83
74
|
fun collectInterestingInfo(self) -> Str =
|
|
84
75
|
match self.rating1, self.rating2
|
plum-examples/types.plum
CHANGED
|
@@ -3,18 +3,17 @@ import std/Bool
|
|
|
3
3
|
import std/Str
|
|
4
4
|
import std/Number
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
enum Point =
|
|
7
|
-
x: Int
|
|
7
|
+
| Point(x: Int, y: Int)
|
|
8
|
-
y: Int
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
enum Named(ToStr) =
|
|
11
|
-
name: Str
|
|
10
|
+
| Named(name: Str)
|
|
12
11
|
|
|
13
12
|
fun toStr(self) -> Str =
|
|
14
13
|
self.name
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
enum Box[T] =
|
|
17
|
-
value: T
|
|
16
|
+
| Box(value: T)
|
|
18
17
|
|
|
19
18
|
trait Shape =
|
|
20
19
|
area() -> Float
|
|
@@ -47,32 +46,28 @@ fun makeIntBox() -> Box =
|
|
|
47
46
|
fun makeStrBox() -> Box =
|
|
48
47
|
Box(value: "x")
|
|
49
48
|
|
|
50
|
-
# ----
|
|
49
|
+
# ---- record-shaped and sum-type enum regression tests ----
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
enum Cat =
|
|
53
|
-
name: Str
|
|
52
|
+
| Cat(name: Str, age: Int)
|
|
54
|
-
age: Int
|
|
55
53
|
|
|
56
54
|
fun getAge() -> Int =
|
|
57
55
|
self.age
|
|
58
56
|
|
|
59
|
-
|
|
57
|
+
enum Dog =
|
|
60
|
-
name: Str
|
|
58
|
+
| Dog(name: Str, age: Int)
|
|
61
|
-
age: Int
|
|
62
59
|
|
|
63
60
|
fun getAge(self) -> Int =
|
|
64
61
|
self.age
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
enum Pair =
|
|
67
|
-
a: Int
|
|
64
|
+
| Pair(a: Int, b: Int)
|
|
68
|
-
b: Int
|
|
69
65
|
|
|
70
|
-
|
|
66
|
+
enum Wrapper =
|
|
71
|
-
inner: Pair
|
|
67
|
+
| Wrapper(inner: Pair, tag: Int)
|
|
72
|
-
tag: Int
|
|
73
68
|
|
|
74
|
-
|
|
69
|
+
enum LoopBox =
|
|
75
|
-
v: Int
|
|
70
|
+
| LoopBox(v: Int)
|
|
76
71
|
|
|
77
72
|
fun sumLoopBoxes() -> Int =
|
|
78
73
|
total := 0
|
|
@@ -121,8 +116,8 @@ enum ShapeWithFields =
|
|
|
121
116
|
fun numberKind(n: Number) -> Str =
|
|
122
117
|
n.kind()
|
|
123
118
|
|
|
124
|
-
|
|
119
|
+
enum OptionBox =
|
|
125
|
-
value: Option[Int]
|
|
120
|
+
| OptionBox(value: Option[Int])
|
|
126
121
|
|
|
127
122
|
fun unwrap(default: Int) -> Int =
|
|
128
123
|
match self.value
|
plum-std/Array.plum
CHANGED
|
@@ -33,7 +33,9 @@ import std/Number
|
|
|
33
33
|
# hood — so `get` on an index that was never `set` traps rather than
|
|
34
34
|
# returning some default `T` value (there is no way to conjure a default `T`
|
|
35
35
|
# generically).
|
|
36
|
-
|
|
36
|
+
enum Array[T] =
|
|
37
|
+
| Array
|
|
38
|
+
|
|
37
39
|
# A new, empty array.
|
|
38
40
|
fun init() -> Array[T] =
|
|
39
41
|
todo
|
plum-std/Buffer.plum
CHANGED
|
@@ -14,9 +14,8 @@ import std/Number
|
|
|
14
14
|
# `bytes.Buffer`'s asymptotic behavior — unlike the previous `Str`-backed
|
|
15
15
|
# version of this type, which recopied everything written so far on every
|
|
16
16
|
# write.
|
|
17
|
-
|
|
17
|
+
enum Buffer(ToStr) =
|
|
18
|
-
data: []Byte
|
|
18
|
+
| Buffer(data: []Byte, len: Int)
|
|
19
|
-
len: Int
|
|
20
19
|
|
|
21
20
|
# A no-param `init` nested in the type is `Buffer()`'s constructor — a
|
|
22
21
|
# zero-arg `Buffer()` call desugars to `Buffer.init()` (see `plum-checker`'s
|
plum-std/Byte.plum
CHANGED
|
@@ -9,7 +9,9 @@ import std/Str
|
|
|
9
9
|
# recognized conversion, not an ordinary function call, exactly like
|
|
10
10
|
# `Int(x)`/`Float(x)` (see `plum-checker`/`plum-wasm-codegen`'s special-cased
|
|
11
11
|
# handling of these three names).
|
|
12
|
-
|
|
12
|
+
enum Byte =
|
|
13
|
+
| Byte
|
|
14
|
+
|
|
13
15
|
fun toInt(self) -> Int =
|
|
14
16
|
Int(self)
|
|
15
17
|
|
plum-std/ByteSlice.plum
CHANGED
|
@@ -14,7 +14,9 @@ import std/Number
|
|
|
14
14
|
# — there's no way to express raw array length/indexing/construction/copying
|
|
15
15
|
# in Plum source itself; `copyStrToBytes`/`bytesToStr` build on top of them
|
|
16
16
|
# instead of needing intrinsics of their own.
|
|
17
|
-
|
|
17
|
+
enum ByteSlice =
|
|
18
|
+
| ByteSlice
|
|
19
|
+
|
|
18
20
|
# Number of bytes in the slice.
|
|
19
21
|
fun length(self) -> Int =
|
|
20
22
|
todo
|
plum-std/Http.plum
CHANGED
|
@@ -12,10 +12,8 @@ import std/Number
|
|
|
12
12
|
|
|
13
13
|
# An HTTP response: status code, response headers, and the raw response
|
|
14
14
|
# body as a `Str` (a byte array, so binary bodies round-trip intact).
|
|
15
|
-
|
|
15
|
+
enum Response =
|
|
16
|
-
status: Int
|
|
17
|
-
headers: Map[Str, Str]
|
|
16
|
+
| Response(status: Int, headers: Map[Str, Str], body: Str)
|
|
18
|
-
body: Str
|
|
19
17
|
|
|
20
18
|
# 2xx status codes are success; everything else (redirects, 4xx, 5xx) is
|
|
21
19
|
# surfaced here rather than as an `Err` from `request` — a non-2xx
|
plum-std/Json.plum
CHANGED
|
@@ -84,9 +84,8 @@ enum Json =
|
|
|
84
84
|
# message — implements the shared `Err` trait (`libs/std/err.plum`) rather
|
|
85
85
|
# than being a plain `Str`, so a caller that wants the position can get it
|
|
86
86
|
# without re-parsing the message.
|
|
87
|
-
|
|
87
|
+
enum JsonParseError(Err) =
|
|
88
|
-
pos: Int
|
|
89
|
-
text: Str
|
|
88
|
+
| JsonParseError(pos: Int, text: Str)
|
|
90
89
|
|
|
91
90
|
fun code(self) -> Int =
|
|
92
91
|
1
|
|
@@ -107,9 +106,8 @@ type JsonParseError(Err) =
|
|
|
107
106
|
# but `Readable` is an undefined trait with no methods anywhere in `libs/std`
|
|
108
107
|
# — there is nothing to call on it — so this reads directly from a `Str`
|
|
109
108
|
# instead, the same way every other stdlib parser, e.g. `Int.fromStr`, does.)
|
|
110
|
-
|
|
109
|
+
enum JsonParser =
|
|
111
|
-
src: Str
|
|
110
|
+
| JsonParser(src: Str, pos: Int)
|
|
112
|
-
pos: Int
|
|
113
111
|
|
|
114
112
|
fun fail(self, text: Str) -> JsonParseError =
|
|
115
113
|
JsonParseError(pos: self.pos, text: text)
|
plum-std/List.plum
CHANGED
|
@@ -7,17 +7,13 @@ import std/Bool
|
|
|
7
7
|
import std/Str
|
|
8
8
|
|
|
9
9
|
# A node stores the data in a list and contains pointers to the previous and next sibling nodes
|
|
10
|
-
|
|
10
|
+
enum Node[T] =
|
|
11
|
-
value: T
|
|
12
|
-
prev: Option[Node[T]]
|
|
11
|
+
| Node(value: T, prev: Option[Node[T]], next: Option[Node[T]])
|
|
13
|
-
next: Option[Node[T]]
|
|
14
12
|
|
|
15
13
|
# A list is a data structure describing a contiguous section of an array stored separately from the slice variable itself.
|
|
16
14
|
# It contains the pointers to the start and end nodes (head, tail) and maintains the size as well
|
|
17
|
-
|
|
15
|
+
enum List[T: ToStr](ToStr) =
|
|
18
|
-
head: Option[Node[T]]
|
|
16
|
+
| List(head: Option[Node[T]], tail: Option[Node[T]], size: Int)
|
|
19
|
-
tail: Option[Node[T]]
|
|
20
|
-
size: Int
|
|
21
17
|
|
|
22
18
|
# `List[T](1, 2, 3)` (or, for an empty list, `List[T]()` — explicit
|
|
23
19
|
# generics required either way, since nothing else in a call this shape
|
plum-std/Map.plum
CHANGED
|
@@ -29,9 +29,8 @@ trait Hashable =
|
|
|
29
29
|
# so a `Pair` used as a `List` element always has `K`/`V: ToStr` too,
|
|
30
30
|
# and `List`'s own eagerly-compiled `join`/`toStr` (which call `.toStr()` on
|
|
31
31
|
# every element) need this to exist for every `Map` specialization anyway.
|
|
32
|
-
|
|
32
|
+
enum Pair[K, V](ToStr) =
|
|
33
|
-
key: K
|
|
33
|
+
| Pair(key: K, val: V)
|
|
34
|
-
val: V
|
|
35
34
|
|
|
36
35
|
fun toStr(self) -> Str =
|
|
37
36
|
return "{self.key.toStr()}: {self.val.toStr()}"
|
|
@@ -48,9 +47,8 @@ BUCKET_COUNT = 16
|
|
|
48
47
|
# `get`/`set`/`remove` (versus the single, whole-map linear scan a plain
|
|
49
48
|
# association list costs), degrading to O(n) only if many keys collide into
|
|
50
49
|
# the same bucket.
|
|
51
|
-
|
|
50
|
+
enum Map[K: Hashable, V] =
|
|
52
|
-
buckets: Array[List[Pair[K, V]]]
|
|
51
|
+
| Map(buckets: Array[List[Pair[K, V]]], size: Int)
|
|
53
|
-
size: Int
|
|
54
52
|
|
|
55
53
|
# `Map[Str, Int]()` — explicit generics required (nothing in a no-arg call
|
|
56
54
|
# pins down `K`/`V`), builds `BUCKET_COUNT` empty bucket lists up front via
|
plum-std/Str.plum
CHANGED
|
@@ -26,8 +26,8 @@ trait ToStr =
|
|
|
26
26
|
# means constructing a brand new `Str`/`Buffer` pair (`Buffer.write`'s own
|
|
27
27
|
# callers are always building up a FRESH buffer before wrapping it, never
|
|
28
28
|
# mutating an already-published `Str`'s backing storage).
|
|
29
|
-
|
|
29
|
+
enum Str(Comparable, ToStr, Readable, Writable) =
|
|
30
|
-
data: Buffer
|
|
30
|
+
| Str(data: Buffer)
|
|
31
31
|
|
|
32
32
|
# Number of bytes in the string — `self.data` (a `Buffer`) already tracks
|
|
33
33
|
# this, so unlike `byteAt`/`byteToStr` this needs no compiler intrinsic of
|
plum-std/Time.plum
CHANGED
|
@@ -7,8 +7,8 @@ import std/Bool
|
|
|
7
7
|
extern fun rawNowMillis() -> Int
|
|
8
8
|
|
|
9
9
|
# A single point in time, stored as milliseconds since the Unix epoch.
|
|
10
|
-
|
|
10
|
+
enum Time(ToStr) =
|
|
11
|
-
value: Int
|
|
11
|
+
| Time(value: Int)
|
|
12
12
|
|
|
13
13
|
fun toMillis(self) -> Int =
|
|
14
14
|
return self.value
|
|
@@ -78,10 +78,8 @@ fun now() -> Time =
|
|
|
78
78
|
return Time(value: rawNowMillis())
|
|
79
79
|
|
|
80
80
|
# A Gregorian calendar date, decomposed from a day count by `civilFromDays`.
|
|
81
|
-
|
|
81
|
+
enum Civil =
|
|
82
|
-
year: Int
|
|
83
|
-
month: Int
|
|
82
|
+
| Civil(year: Int, month: Int, day: Int)
|
|
84
|
-
day: Int
|
|
85
83
|
|
|
86
84
|
MILLIS_PER_DAY = 86400000
|
|
87
85
|
|
|
@@ -116,8 +114,8 @@ fun floorMod(a: Int, b: Int) -> Int =
|
|
|
116
114
|
|
|
117
115
|
# A span of time, stored as milliseconds. Negative values are meaningful
|
|
118
116
|
# (e.g. the result of `Time.since` when `self` is earlier than `other`).
|
|
119
|
-
|
|
117
|
+
enum Duration =
|
|
120
|
-
value: Int
|
|
118
|
+
| Duration(value: Int)
|
|
121
119
|
|
|
122
120
|
fun millis(n: Int) -> Duration =
|
|
123
121
|
return Duration(value: n)
|
plum-std/Uuid.plum
CHANGED
|
@@ -6,8 +6,8 @@ import std/Bool
|
|
|
6
6
|
|
|
7
7
|
# A UUID, stored as its canonical 36-character
|
|
8
8
|
# `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` text form.
|
|
9
|
-
|
|
9
|
+
enum Uuid(ToStr) =
|
|
10
|
-
value: Str
|
|
10
|
+
| Uuid(value: Str)
|
|
11
11
|
|
|
12
12
|
fun toStr(self) -> Str =
|
|
13
13
|
return self.value
|
plum-tooling/tree-sitter-plum/grammar.js
CHANGED
|
@@ -78,7 +78,7 @@ module.exports = grammar({
|
|
|
78
78
|
seq(
|
|
79
79
|
optional($.module),
|
|
80
80
|
repeat($.import),
|
|
81
|
-
repeat(choice($.
|
|
81
|
+
repeat(choice($.trait, $.enum, $.fn, $.const, $.test)),
|
|
82
82
|
),
|
|
83
83
|
|
|
84
84
|
module: ($) => seq("module", $.mod_identifier),
|
|
@@ -119,20 +119,6 @@ module.exports = grammar({
|
|
|
119
119
|
),
|
|
120
120
|
variadic_type: ($) => seq("...", $.type),
|
|
121
121
|
|
|
122
|
-
class: ($) =>
|
|
123
|
-
seq(
|
|
124
|
-
"type",
|
|
125
|
-
field("name", $.type_identifier),
|
|
126
|
-
field("generics", optional($.generics)),
|
|
127
|
-
field("implements", optional(seq("(", commaSep1($.type_identifier), ")"))),
|
|
128
|
-
"=",
|
|
129
|
-
$._indent,
|
|
130
|
-
field("fields", optional(repeat(alias($.class_field, $.field)))),
|
|
131
|
-
field("methods", optional(repeat($.fn))),
|
|
132
|
-
$._dedent,
|
|
133
|
-
),
|
|
134
|
-
class_field: ($) => seq(field("name", $.var_identifier), ":", field("type", $.type)),
|
|
135
|
-
|
|
136
122
|
trait: ($) =>
|
|
137
123
|
seq(
|
|
138
124
|
"trait",
|
|
@@ -173,7 +159,16 @@ module.exports = grammar({
|
|
|
173
159
|
"enum",
|
|
174
160
|
field("name", $.type_identifier),
|
|
175
161
|
field("generics", optional($.generics)),
|
|
162
|
+
// Mutually exclusive with each other (an enum is either a plain sum
|
|
163
|
+
// type possibly claiming trait conformance, OR a discriminant enum
|
|
164
|
+
// with shared per-variant values — never both) — disambiguated by
|
|
165
|
+
// content, not just position: `implements` holds bare type names
|
|
166
|
+
// (`ToStr`), `params` holds `name: Type` pairs (`n: Int`), and
|
|
167
|
+
// `var_identifier`/`type_identifier` are distinct tokens.
|
|
168
|
+
optional(choice(
|
|
169
|
+
field("implements", seq("(", commaSep1($.type_identifier), ")")),
|
|
176
|
-
|
|
170
|
+
field("params", seq("(", commaSep1($.enum_param), ")")),
|
|
171
|
+
)),
|
|
177
172
|
"=",
|
|
178
173
|
$._indent,
|
|
179
174
|
optional(repeat(alias($.enum_field, $.field))),
|
plum-tooling/tree-sitter-plum/queries/plum/format.scm
CHANGED
|
@@ -9,9 +9,6 @@
|
|
|
9
9
|
(source
|
|
10
10
|
(fn) @allow_blank_line_before)
|
|
11
11
|
|
|
12
|
-
(source
|
|
13
|
-
(class) @allow_blank_line_before)
|
|
14
|
-
|
|
15
12
|
(source
|
|
16
13
|
(enum) @allow_blank_line_before)
|
|
17
14
|
|
|
@@ -96,8 +93,6 @@
|
|
|
96
93
|
|
|
97
94
|
(import "import" @append_space)
|
|
98
95
|
|
|
99
|
-
(class "type" @append_space)
|
|
100
|
-
|
|
101
96
|
(trait "trait" @append_space)
|
|
102
97
|
|
|
103
98
|
(enum "enum" @append_space)
|
|
@@ -158,35 +153,13 @@
|
|
|
158
153
|
":" @append_space
|
|
159
154
|
|
|
160
155
|
; ============================================================
|
|
161
|
-
;
|
|
156
|
+
; trait/enum bodies — unlike a `fn`'s body, these have no
|
|
162
157
|
; wrapping `(body ...)` node of their own (the grammar puts their
|
|
163
|
-
; `field`/`fn` children directly under `
|
|
158
|
+
; `field`/`fn` children directly under `trait`/`enum`), so
|
|
164
159
|
; they need their own hardline + indent rules instead of reusing
|
|
165
160
|
; the `(body)` rule below.
|
|
166
161
|
; ============================================================
|
|
167
162
|
|
|
168
|
-
(class
|
|
169
|
-
(field) @prepend_hardline)
|
|
170
|
-
|
|
171
|
-
(class
|
|
172
|
-
(fn) @prepend_hardline)
|
|
173
|
-
|
|
174
|
-
; A blank line between two fields/methods (or a field and a method) inside a
|
|
175
|
-
; `type` body is preserved if the source already had one — same as top-level
|
|
176
|
-
; items — rather than always being collapsed to a single hardline.
|
|
177
|
-
(class
|
|
178
|
-
(field) @allow_blank_line_before)
|
|
179
|
-
|
|
180
|
-
(class
|
|
181
|
-
(fn) @allow_blank_line_before)
|
|
182
|
-
|
|
183
|
-
(class
|
|
184
|
-
"=" @append_indent_start)
|
|
185
|
-
|
|
186
|
-
(class
|
|
187
|
-
(_) @append_indent_end
|
|
188
|
-
.)
|
|
189
|
-
|
|
190
163
|
(trait
|
|
191
164
|
(field) @prepend_hardline)
|
|
192
165
|
|
|
@@ -203,7 +176,9 @@
|
|
|
203
176
|
(enum
|
|
204
177
|
(fn) @prepend_hardline)
|
|
205
178
|
|
|
179
|
+
; A blank line between two fields/methods (or a field and a method) inside an
|
|
180
|
+
; `enum` body is preserved if the source already had one — same as top-level
|
|
206
|
-
;
|
|
181
|
+
; items — rather than always being collapsed to a single hardline.
|
|
207
182
|
(enum
|
|
208
183
|
(field) @allow_blank_line_before)
|
|
209
184
|
|
|
@@ -218,13 +193,13 @@
|
|
|
218
193
|
.)
|
|
219
194
|
|
|
220
195
|
; `enum_field` (a variant like `| Some[T]`) is ALIASED to the same visible
|
|
221
|
-
; node kind ("field") as
|
|
196
|
+
; node kind ("field") as trait_field, but only it has a literal
|
|
222
197
|
; "|" — safe to target unscoped by parent.
|
|
223
198
|
(field
|
|
224
199
|
"|" @append_space)
|
|
225
200
|
|
|
226
201
|
; ============================================================
|
|
227
|
-
; match — same situation as
|
|
202
|
+
; match — same situation as trait/enum: `case`/`guard_case`
|
|
228
203
|
; children sit directly under `match`, no wrapping `(body ...)`.
|
|
229
204
|
; ============================================================
|
|
230
205
|
|
plum-tooling/tree-sitter-plum/queries/plum/highlights.scm
CHANGED
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"||"
|
|
60
60
|
"&&"
|
|
61
61
|
"..."
|
|
62
|
+
".."
|
|
62
63
|
] @operator
|
|
63
64
|
|
|
64
65
|
[
|
|
@@ -71,7 +72,6 @@
|
|
|
71
72
|
[
|
|
72
73
|
"import"
|
|
73
74
|
"module"
|
|
74
|
-
"type"
|
|
75
75
|
"enum"
|
|
76
76
|
"trait"
|
|
77
77
|
"fun"
|
plum-tooling/tree-sitter-plum/queries/plum/indents.scm
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
[
|
|
2
2
|
(enum)
|
|
3
3
|
(trait)
|
|
4
|
-
(class)
|
|
5
4
|
(fn)
|
|
6
5
|
(for)
|
|
7
6
|
(while)
|
|
@@ -16,7 +15,6 @@
|
|
|
16
15
|
(for)
|
|
17
16
|
(while)
|
|
18
17
|
(fn)
|
|
19
|
-
(class)
|
|
20
18
|
(enum)
|
|
21
19
|
(trait)
|
|
22
20
|
] @extend
|
plum-tooling/tree-sitter-plum/queries/plum/tags.scm
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
(class
|
|
2
|
-
name: (type_identifier) @name) @definition.class
|
|
3
|
-
|
|
4
1
|
(enum
|
|
5
2
|
name: (type_identifier) @name) @definition.class
|
|
6
3
|
|
plum-tooling/tree-sitter-plum/queries/plum/textobjects.scm
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
(fn
|
|
2
2
|
body: (_) @function.inside) @function.around
|
|
3
3
|
|
|
4
|
-
(class
|
|
5
|
-
fields: (field) @class.inside) @class.around
|
|
6
|
-
|
|
7
4
|
(trait
|
|
8
5
|
fields: (field) @class.inside) @class.around
|
|
9
6
|
|
plum-tooling/tree-sitter-plum/src/grammar.json
CHANGED
|
Binary file
|
plum-tooling/tree-sitter-plum/src/node-types.json
CHANGED
|
Binary file
|
plum-tooling/tree-sitter-plum/src/parser.c
CHANGED
|
Binary file
|
plum-tooling/tree-sitter-plum/test/corpus/type.txt
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
type
|
|
3
3
|
================================================================================
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
enum Dog =
|
|
6
|
-
name: Str
|
|
6
|
+
| Dog(name: Str, age: B)
|
|
7
|
-
age: B
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
enum Cat(ToStr) =
|
|
10
|
-
name: Str
|
|
9
|
+
| Cat(name: Str, age: Int)
|
|
11
|
-
age: Int
|
|
12
10
|
|
|
13
11
|
fun init(name: Str) -> Cat =
|
|
14
12
|
Cat(name: name, age: 0)
|
|
@@ -25,27 +23,31 @@ type Cat(ToStr) =
|
|
|
25
23
|
--------------------------------------------------------------------------------
|
|
26
24
|
|
|
27
25
|
(source
|
|
28
|
-
(
|
|
26
|
+
(enum
|
|
29
27
|
(type_identifier)
|
|
30
28
|
(field
|
|
29
|
+
(type_identifier)
|
|
30
|
+
(enum_named_field
|
|
31
|
-
|
|
31
|
+
(var_identifier)
|
|
32
|
-
|
|
32
|
+
(type
|
|
33
|
-
|
|
33
|
+
(type_identifier)))
|
|
34
|
-
|
|
34
|
+
(enum_named_field
|
|
35
|
-
|
|
35
|
+
(var_identifier)
|
|
36
|
-
|
|
36
|
+
(type
|
|
37
|
-
|
|
37
|
+
(generic)))))
|
|
38
|
-
(
|
|
38
|
+
(enum
|
|
39
39
|
(type_identifier)
|
|
40
40
|
(type_identifier)
|
|
41
41
|
(field
|
|
42
|
+
(type_identifier)
|
|
43
|
+
(enum_named_field
|
|
42
|
-
|
|
44
|
+
(var_identifier)
|
|
43
|
-
|
|
45
|
+
(type
|
|
44
|
-
|
|
46
|
+
(type_identifier)))
|
|
45
|
-
|
|
47
|
+
(enum_named_field
|
|
46
|
-
|
|
48
|
+
(var_identifier)
|
|
47
|
-
|
|
49
|
+
(type
|
|
48
|
-
|
|
50
|
+
(type_identifier))))
|
|
49
51
|
(fn
|
|
50
52
|
(fn_identifier)
|
|
51
53
|
(param
|
|
@@ -60,14 +62,16 @@ type Cat(ToStr) =
|
|
|
60
62
|
(class_call
|
|
61
63
|
(type_identifier)
|
|
62
64
|
(class_argument_list
|
|
65
|
+
(field_argument
|
|
63
|
-
|
|
66
|
+
(var_identifier)
|
|
64
|
-
|
|
67
|
+
(expression
|
|
65
|
-
|
|
68
|
+
(primary_expression
|
|
66
|
-
|
|
69
|
+
(var_identifier))))
|
|
70
|
+
(field_argument
|
|
67
|
-
|
|
71
|
+
(var_identifier)
|
|
68
|
-
|
|
72
|
+
(expression
|
|
69
|
-
|
|
73
|
+
(primary_expression
|
|
70
|
-
|
|
74
|
+
(integer))))))))))
|
|
71
75
|
(fn
|
|
72
76
|
(fn_identifier)
|
|
73
77
|
(param
|
|
@@ -82,14 +86,16 @@ type Cat(ToStr) =
|
|
|
82
86
|
(class_call
|
|
83
87
|
(type_identifier)
|
|
84
88
|
(class_argument_list
|
|
89
|
+
(field_argument
|
|
85
|
-
|
|
90
|
+
(var_identifier)
|
|
86
|
-
|
|
91
|
+
(expression
|
|
87
|
-
|
|
92
|
+
(primary_expression
|
|
88
|
-
|
|
93
|
+
(var_identifier))))
|
|
94
|
+
(field_argument
|
|
89
|
-
|
|
95
|
+
(var_identifier)
|
|
90
|
-
|
|
96
|
+
(expression
|
|
91
|
-
|
|
97
|
+
(primary_expression
|
|
92
|
-
|
|
98
|
+
(integer))))))))))
|
|
93
99
|
(fn
|
|
94
100
|
(fn_identifier)
|
|
95
101
|
(param
|
|
@@ -104,16 +110,18 @@ type Cat(ToStr) =
|
|
|
104
110
|
(class_call
|
|
105
111
|
(type_identifier)
|
|
106
112
|
(class_argument_list
|
|
113
|
+
(field_argument
|
|
107
|
-
|
|
114
|
+
(var_identifier)
|
|
108
|
-
|
|
115
|
+
(expression
|
|
109
|
-
|
|
116
|
+
(primary_expression
|
|
110
|
-
|
|
117
|
+
(string
|
|
111
|
-
|
|
118
|
+
(string_start)
|
|
112
|
-
|
|
119
|
+
(string_end)))))
|
|
120
|
+
(field_argument
|
|
113
|
-
|
|
121
|
+
(var_identifier)
|
|
114
|
-
|
|
122
|
+
(expression
|
|
115
|
-
|
|
123
|
+
(primary_expression
|
|
116
|
-
|
|
124
|
+
(var_identifier))))))))))
|
|
117
125
|
(fn
|
|
118
126
|
(fn_identifier)
|
|
119
127
|
(type
|
|
@@ -146,8 +154,8 @@ type Cat(ToStr) =
|
|
|
146
154
|
type - nested method declaration
|
|
147
155
|
================================================================================
|
|
148
156
|
|
|
149
|
-
|
|
157
|
+
enum Cat =
|
|
150
|
-
name: Str
|
|
158
|
+
| Cat(name: Str)
|
|
151
159
|
|
|
152
160
|
fun getName(self) -> Str =
|
|
153
161
|
self.name
|
|
@@ -155,12 +163,14 @@ type Cat =
|
|
|
155
163
|
--------------------------------------------------------------------------------
|
|
156
164
|
|
|
157
165
|
(source
|
|
158
|
-
(
|
|
166
|
+
(enum
|
|
159
167
|
(type_identifier)
|
|
160
168
|
(field
|
|
169
|
+
(type_identifier)
|
|
170
|
+
(enum_named_field
|
|
161
|
-
|
|
171
|
+
(var_identifier)
|
|
162
|
-
|
|
172
|
+
(type
|
|
163
|
-
|
|
173
|
+
(type_identifier))))
|
|
164
174
|
(fn
|
|
165
175
|
(fn_identifier)
|
|
166
176
|
(self)
|
plum-tooling/tree-sitter-plum/test/highlight/sample.plum
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
module std
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
enum Cat(ToStr) =
|
|
4
|
-
name: Str
|
|
4
|
+
| Cat(name: Str, age: Int)
|
|
5
|
-
age: Int
|
|
6
5
|
|
|
7
6
|
fun withName(name: Str) -> Cat =
|
|
8
7
|
Cat(name: name, age: self.age)
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -7,7 +7,7 @@ use std::cell::{Cell, RefCell};
|
|
|
7
7
|
use std::collections::{HashMap, HashSet};
|
|
8
8
|
use plum_core::ast;
|
|
9
9
|
use plum_checker::types::{PlumType, TypeEnv, TypeScheme};
|
|
10
|
-
use plum_checker::{
|
|
10
|
+
use plum_checker::{MethodEnv, EnumVariants, EnumVariantInfo, EnumParams};
|
|
11
11
|
|
|
12
12
|
/// One entry in the module's type section. Wasm's type section is a SINGLE shared
|
|
13
13
|
/// index space for function types AND (once wasm-gc is in play) composite
|
|
@@ -328,7 +328,6 @@ type ClosureSigKey = (Vec<ValType>, Option<ValType>);
|
|
|
328
328
|
pub struct CompileCtx<'a> {
|
|
329
329
|
pub func_ids: HashMap<String, u32>,
|
|
330
330
|
pub func_sigs: HashMap<String, FuncSig>,
|
|
331
|
-
pub classes: ClassEnv,
|
|
332
331
|
pub methods: MethodEnv,
|
|
333
332
|
pub enum_variants: EnumVariants,
|
|
334
333
|
pub enum_params: EnumParams,
|
|
@@ -358,7 +357,7 @@ pub struct CompileCtx<'a> {
|
|
|
358
357
|
/// Shared runtime helper `(n: i64) -> ref Str`: allocates a new `array<i8>`
|
|
359
358
|
/// holding `n`'s decimal representation, for interpolating an `Int`.
|
|
360
359
|
pub int_to_string_func: u32,
|
|
361
|
-
/// wasm-gc type-section indices for this program's
|
|
360
|
+
/// wasm-gc type-section indices for this program's enums/Str/closures —
|
|
362
361
|
/// every value's real representation (see `docs/superpowers/plans/2026-07-25-wasm-gc-migration.md`).
|
|
363
362
|
pub gc_types: GcTypeRegistry,
|
|
364
363
|
/// Payload-free variant name (True/False/None/...) -> the global index holding
|
|
@@ -401,7 +400,6 @@ struct LocalCtx<'a> {
|
|
|
401
400
|
string_concat_func: u32,
|
|
402
401
|
string_eq_func: u32,
|
|
403
402
|
int_to_string_func: u32,
|
|
404
|
-
classes: &'a ClassEnv,
|
|
405
403
|
methods: &'a MethodEnv,
|
|
406
404
|
enum_variants: &'a EnumVariants,
|
|
407
405
|
enum_params: &'a EnumParams,
|
|
@@ -506,13 +504,14 @@ fn withGcTypes<R>(f: impl FnOnce(&GcTypeRegistry) -> R) -> R {
|
|
|
506
504
|
}
|
|
507
505
|
|
|
508
506
|
/// A constructor-pattern name (`nested_class_scratch_types`'s recorded name, or any
|
|
509
|
-
/// other spot a pattern's own concrete GC type index is needed) is
|
|
507
|
+
/// other spot a pattern's own concrete GC type index is needed) is always an enum
|
|
510
|
-
/// variant
|
|
508
|
+
/// variant — falls back to `class_type_idx` only for the record-shaped alias case
|
|
509
|
+
/// (see `buildGcTypeRegistry`'s own alias comment), which `variant_type_idx` should
|
|
511
|
-
///
|
|
510
|
+
/// already cover directly; kept as a defensive second lookup.
|
|
512
511
|
fn classOrVariantTypeIdx(r: &GcTypeRegistry, name: &str) -> u32 {
|
|
513
512
|
*r.variant_type_idx.get(name)
|
|
514
513
|
.or_else(|| r.class_type_idx.get(name))
|
|
515
|
-
.unwrap_or_else(|| panic!("internal codegen error: '{}' missing from the GC type registry
|
|
514
|
+
.unwrap_or_else(|| panic!("internal codegen error: '{}' missing from the GC type registry", name))
|
|
516
515
|
}
|
|
517
516
|
|
|
518
517
|
/// Resolves an `ast::Type`/`ast::ParamType`'s bare name (e.g. from a function
|
|
@@ -677,8 +676,8 @@ fn plumTypeToGcValtype(t: &PlumType, registry: &GcTypeRegistry) -> ValType {
|
|
|
677
676
|
PlumType::TByteSlice => ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(registry.byte_array_type_idx) }),
|
|
678
677
|
PlumType::TNamed(name) => match registry.class_type_idx.get(name).or_else(|| registry.enum_super_type_idx.get(name)) {
|
|
679
678
|
Some(idx) => ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(*idx) }),
|
|
680
|
-
// A field typed as a generic enum
|
|
679
|
+
// A field typed as a generic enum (e.g. `Node.next: Option[Node]`)
|
|
681
|
-
// resolves here to the BARE generic name — `
|
|
680
|
+
// resolves here to the BARE generic name — `plumTypeFromAst`
|
|
682
681
|
// has no representation for type arguments, so it can't know this means
|
|
683
682
|
// `Option$Int` once monomorphization specializes (and removes the
|
|
684
683
|
// unspecialized) `Option` — same permissive `anyref` fallback as
|
|
@@ -696,24 +695,22 @@ fn plumTypeToGcValtype(t: &PlumType, registry: &GcTypeRegistry) -> ValType {
|
|
|
696
695
|
}
|
|
697
696
|
}
|
|
698
697
|
|
|
699
|
-
/// Builds the wasm-gc type registry for every concrete
|
|
698
|
+
/// Builds the wasm-gc type registry for every concrete enum actually declared in
|
|
700
|
-
///
|
|
699
|
+
/// `source` (`Bool`/`Str`/etc included, but only if imported — no builtin is
|
|
701
700
|
/// hardcoded here), plus the shared raw byte-array type, and declares them all as
|
|
702
701
|
/// ONE `rec` group via `module.addGcTypes` — a single group sidesteps every ordering
|
|
703
|
-
/// question about mutual/self-references (a
|
|
702
|
+
/// question about mutual/self-references (a record-shaped enum's field naming
|
|
704
|
-
/// enum variant field referencing its own enum,
|
|
703
|
+
/// another such enum's type, an enum variant field referencing its own enum,
|
|
705
|
-
/// since within one `rec` group members may
|
|
704
|
+
/// `Node.next: Option[Node]`, etc.), since within one `rec` group members may
|
|
706
|
-
/// declaration order.
|
|
705
|
+
/// reference each other regardless of declaration order.
|
|
707
706
|
fn buildGcTypeRegistry(
|
|
708
707
|
module: &mut WasmModule,
|
|
709
708
|
source: &ast::Source,
|
|
710
|
-
classes: &ClassEnv,
|
|
711
709
|
enum_variants: &EnumVariants,
|
|
712
710
|
enum_params: &EnumParams,
|
|
713
711
|
) -> GcTypeRegistry {
|
|
714
712
|
enum Slot {
|
|
715
713
|
Str,
|
|
716
|
-
Class(String),
|
|
717
714
|
EnumSuper(String),
|
|
718
715
|
Variant(String),
|
|
719
716
|
/// The single shared `array<anyref>` type every `Array[T]`
|
|
@@ -729,22 +726,6 @@ fn buildGcTypeRegistry(
|
|
|
729
726
|
let mut variant_type_idx: HashMap<String, u32> = HashMap::new();
|
|
730
727
|
let mut array_ref_slot: Option<u32> = None;
|
|
731
728
|
|
|
732
|
-
for item in &source.items {
|
|
733
|
-
if let ast::Item::Class(c) = item {
|
|
734
|
-
if isArraySpecialization(&c.name) {
|
|
735
|
-
let idx = *array_ref_slot.get_or_insert_with(|| {
|
|
736
|
-
let idx = slots.len() as u32;
|
|
737
|
-
slots.push(Slot::ArrayRef);
|
|
738
|
-
idx
|
|
739
|
-
});
|
|
740
|
-
class_type_idx.insert(c.name.clone(), idx);
|
|
741
|
-
} else {
|
|
742
|
-
class_type_idx.insert(c.name.clone(), slots.len() as u32);
|
|
743
|
-
slots.push(Slot::Class(c.name.clone()));
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
729
|
// `Bool` is an ordinary enum now (`type Bool = | True | False` in
|
|
749
730
|
// `libs/std/Bool.plum`) — registered exactly like any other enum
|
|
750
731
|
// declared in `source.items`, present only if actually imported.
|
|
@@ -755,12 +736,42 @@ fn buildGcTypeRegistry(
|
|
|
755
736
|
}
|
|
756
737
|
}
|
|
757
738
|
for (enum_name, variant_names) in &enum_decls {
|
|
739
|
+
// `Array`/`Array$T` is a RECORD-shaped enum (single variant sharing the
|
|
740
|
+
// enum's own name — the `type Array[T] = ...` replacement) whose values
|
|
741
|
+
// are ALL represented by the one shared `array<anyref>` type regardless
|
|
742
|
+
// of `T` (see `isArraySpecialization`'s own doc comment) — alias its
|
|
743
|
+
// enum/variant/class names onto that single existing slot instead of
|
|
744
|
+
// giving it (and every OTHER `Array$...` specialization) its own real
|
|
745
|
+
// struct type.
|
|
746
|
+
if isArraySpecialization(enum_name) {
|
|
747
|
+
let idx = *array_ref_slot.get_or_insert_with(|| {
|
|
748
|
+
let idx = slots.len() as u32;
|
|
749
|
+
slots.push(Slot::ArrayRef);
|
|
750
|
+
idx
|
|
751
|
+
});
|
|
752
|
+
enum_super_type_idx.insert(enum_name.clone(), idx);
|
|
753
|
+
class_type_idx.insert(enum_name.clone(), idx);
|
|
754
|
+
for vname in variant_names {
|
|
755
|
+
variant_type_idx.insert(vname.clone(), idx);
|
|
756
|
+
}
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
758
759
|
enum_super_type_idx.insert(enum_name.clone(), slots.len() as u32);
|
|
759
760
|
slots.push(Slot::EnumSuper(enum_name.clone()));
|
|
760
761
|
for vname in variant_names {
|
|
761
762
|
variant_type_idx.insert(vname.clone(), slots.len() as u32);
|
|
762
763
|
slots.push(Slot::Variant(vname.clone()));
|
|
763
764
|
}
|
|
765
|
+
// A RECORD-shaped enum (the `type X = ...` replacement, e.g. `enum Cat =
|
|
766
|
+
// | Cat(name: Str)`) also gets a `class_type_idx` alias onto its one
|
|
767
|
+
// variant's real struct slot — `plumTypeToValtype`'s `TNamed(name)` arm
|
|
768
|
+
// resolves a value of this type via `class_type_idx` (checked first,
|
|
769
|
+
// falling back to `enum_super_type_idx`), and every OTHER codegen site
|
|
770
|
+
// that already looks up a "class" by this exact name (field access,
|
|
771
|
+
// construction, pattern destructuring, ...) keeps working unchanged.
|
|
772
|
+
if variant_names.len() == 1 && variant_names[0] == *enum_name {
|
|
773
|
+
class_type_idx.insert(enum_name.clone(), variant_type_idx[&variant_names[0]]);
|
|
774
|
+
}
|
|
764
775
|
}
|
|
765
776
|
|
|
766
777
|
// The registry is fully index-complete after pass 1 (every name has an assigned
|
|
@@ -787,18 +798,6 @@ fn buildGcTypeRegistry(
|
|
|
787
798
|
shared: false,
|
|
788
799
|
},
|
|
789
800
|
},
|
|
790
|
-
Slot::Class(name) => {
|
|
791
|
-
let fields = classes.get(name).cloned().unwrap_or_default();
|
|
792
|
-
let field_types: Vec<FieldType> = fields.iter().map(|(_, ty)| FieldType {
|
|
793
|
-
element_type: StorageType::Val(plumTypeToGcValtype(ty, ®istry)),
|
|
794
|
-
mutable: true,
|
|
795
|
-
}).collect();
|
|
796
|
-
SubType {
|
|
797
|
-
is_final: true,
|
|
798
|
-
supertype_idx: None,
|
|
799
|
-
composite_type: CompositeType { inner: CompositeInnerType::Struct(StructType { fields: field_types.into() }), shared: false },
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
801
|
// An ORDINARY enum's supertype declares zero fields (each variant adds its
|
|
803
802
|
// own distinct payload fields below it). A DISCRIMINANT enum (`enum
|
|
804
803
|
// Foo(n: Int) = ...`) is different: every variant shares the EXACT SAME
|
|
@@ -903,14 +902,13 @@ fn initCallableWithNoArgs(params: &[PlumType]) -> bool {
|
|
|
903
902
|
}
|
|
904
903
|
|
|
905
904
|
fn checkCtxOf<'a>(
|
|
906
|
-
ctx_classes: &'a ClassEnv,
|
|
907
905
|
ctx_methods: &'a MethodEnv,
|
|
908
906
|
ctx_enum_variants: &'a EnumVariants,
|
|
909
907
|
ctx_enum_params: &'a EnumParams,
|
|
910
908
|
ctx_min_required: &'a plum_checker::MinRequiredArgs,
|
|
911
909
|
) -> plum_checker::CheckCtx<'a> {
|
|
912
910
|
plum_checker::CheckCtx {
|
|
913
|
-
|
|
911
|
+
methods: ctx_methods, enum_variants: ctx_enum_variants,
|
|
914
912
|
enum_params: ctx_enum_params, min_required: ctx_min_required,
|
|
915
913
|
}
|
|
916
914
|
}
|
|
@@ -921,7 +919,7 @@ fn checkCtxOf<'a>(
|
|
|
921
919
|
/// codegen is being driven directly on unchecked input (as some tests do).
|
|
922
920
|
fn inferLocalType(expr: &ast::Expr, ctx: &LocalCtx) -> PlumType {
|
|
923
921
|
let env = ctx.type_env.borrow();
|
|
924
|
-
let cctx = checkCtxOf(ctx.
|
|
922
|
+
let cctx = checkCtxOf(ctx.methods, ctx.enum_variants, ctx.enum_params, ctx.min_required);
|
|
925
923
|
plum_checker::inferExpr(expr, &env, &cctx).unwrap_or(PlumType::TInt)
|
|
926
924
|
}
|
|
927
925
|
|
|
@@ -1080,12 +1078,12 @@ fn desugarAssertStmt(stmt: &ast::Stmt) -> ast::Stmt {
|
|
|
1080
1078
|
|
|
1081
1079
|
fn compileSourceInner(source: &ast::Source, extra_exports: &[(String, String)]) -> Result<Vec<u8>, String> {
|
|
1082
1080
|
let source = &plum_checker::monomorphize::monomorphizeSource(source)?;
|
|
1083
|
-
let (global_env,
|
|
1081
|
+
let (global_env, methods, enum_variants, enum_params) = plum_checker::buildGlobalTables(source);
|
|
1084
1082
|
let min_required = plum_checker::buildMinRequiredArgs(source);
|
|
1085
1083
|
|
|
1086
1084
|
let mut module = WasmModule::new();
|
|
1087
1085
|
|
|
1088
|
-
let mut gc_types = buildGcTypeRegistry(&mut module, source, &
|
|
1086
|
+
let mut gc_types = buildGcTypeRegistry(&mut module, source, &enum_variants, &enum_params);
|
|
1089
1087
|
CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
|
|
1090
1088
|
|
|
1091
1089
|
CURRENT_CONSTS.with(|c| {
|
|
@@ -1169,6 +1167,15 @@ fn compileSourceInner(source: &ast::Source, extra_exports: &[(String, String)])
|
|
|
1169
1167
|
if !info.field_types.is_empty() {
|
|
1170
1168
|
continue;
|
|
1171
1169
|
}
|
|
1170
|
+
// `Array`/`Array$T`'s bare payload-free variant is aliased onto the
|
|
1171
|
+
// ONE shared `array<anyref>` type every specialization reuses (see
|
|
1172
|
+
// `buildGcTypeRegistry`'s `isArraySpecialization` handling), not a
|
|
1173
|
+
// real struct type — it's never actually instantiated this way
|
|
1174
|
+
// (construction goes through the `init`/`push` compiler intrinsics
|
|
1175
|
+
// instead), so it must not get a singleton global here.
|
|
1176
|
+
if isArraySpecialization(name) {
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1172
1179
|
let variant_idx = *gc_types.variant_type_idx.get(name)
|
|
1173
1180
|
.unwrap_or_else(|| panic!("internal codegen error: payload-free variant '{}' missing from GC type registry", name));
|
|
1174
1181
|
let mut init = Vec::new();
|
|
@@ -1258,7 +1265,7 @@ fn compileSourceInner(source: &ast::Source, extra_exports: &[(String, String)])
|
|
|
1258
1265
|
}
|
|
1259
1266
|
let mut walker = ClosureWalker {
|
|
1260
1267
|
env,
|
|
1261
|
-
cctx: checkCtxOf(&
|
|
1268
|
+
cctx: checkCtxOf(&methods, &enum_variants, &enum_params, &min_required),
|
|
1262
1269
|
fn_decls: &fn_decls,
|
|
1263
1270
|
found: Vec::new(),
|
|
1264
1271
|
locals,
|
|
@@ -1400,7 +1407,7 @@ fn compileSourceInner(source: &ast::Source, extra_exports: &[(String, String)])
|
|
|
1400
1407
|
let int_to_string_func = registerIntToStringHelper(&mut module, gc_types.byte_array_type_idx);
|
|
1401
1408
|
|
|
1402
1409
|
let ctx = CompileCtx {
|
|
1403
|
-
func_ids, func_sigs,
|
|
1410
|
+
func_ids, func_sigs, methods, enum_variants, enum_params, min_required, global_env,
|
|
1404
1411
|
closures, closure_asts, closure_call_types, named_fn_values,
|
|
1405
1412
|
string_concat_func, string_eq_func, int_to_string_func, gc_types, singleton_globals,
|
|
1406
1413
|
};
|
|
@@ -2442,14 +2449,15 @@ fn scanExprForParamTypes(
|
|
|
2442
2449
|
}
|
|
2443
2450
|
ast::Expr::Attribute(a) => {
|
|
2444
2451
|
// `c.field` on a bare, unresolved param implies `c`'s type is whichever
|
|
2445
|
-
//
|
|
2452
|
+
// enum declares a variant with that field name — ambiguous if more than
|
|
2446
|
-
// a field by that name, but resolvable in the common case.
|
|
2453
|
+
// one variant has a field by that name, but resolvable in the common case.
|
|
2447
2454
|
if let ast::AttrKind::Field(field_name) = &a.attr {
|
|
2448
2455
|
if let ast::Expr::Var(n) = &a.object {
|
|
2449
2456
|
if params.contains(n) && !resolved.contains_key(n) {
|
|
2457
|
+
let mut matches = cctx.enum_variants.values()
|
|
2450
|
-
|
|
2458
|
+
.filter(|info| info.field_names.iter().any(|fname| fname == field_name));
|
|
2451
|
-
if let (Some(
|
|
2459
|
+
if let (Some(info), None) = (matches.next(), matches.next()) {
|
|
2452
|
-
resolved.insert(n.clone(), PlumType::TNamed(
|
|
2460
|
+
resolved.insert(n.clone(), PlumType::TNamed(info.enum_name.clone()));
|
|
2453
2461
|
}
|
|
2454
2462
|
}
|
|
2455
2463
|
}
|
|
@@ -2763,11 +2771,6 @@ impl<'a> Collector<'a> {
|
|
|
2763
2771
|
for (f, fty) in fields.iter().zip(field_types.iter()) {
|
|
2764
2772
|
self.collectPattern(f, fty);
|
|
2765
2773
|
}
|
|
2766
|
-
} else if let Some(class_fields) = self.cctx.classes.get(name) {
|
|
2767
|
-
let field_types: Vec<PlumType> = class_fields.iter().map(|(_, t)| t.clone()).collect();
|
|
2768
|
-
for (f, fty) in fields.iter().zip(field_types.iter()) {
|
|
2769
|
-
self.collectPattern(f, fty);
|
|
2770
|
-
}
|
|
2771
2774
|
}
|
|
2772
2775
|
}
|
|
2773
2776
|
_ => {}
|
|
@@ -2952,7 +2955,7 @@ fn compileFnBody(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Resu
|
|
|
2952
2955
|
|
|
2953
2956
|
let mut collector = Collector {
|
|
2954
2957
|
env: base_env.clone(),
|
|
2955
|
-
cctx: checkCtxOf(&ctx.
|
|
2958
|
+
cctx: checkCtxOf(&ctx.methods, &ctx.enum_variants, &ctx.enum_params, &ctx.min_required),
|
|
2956
2959
|
named: Vec::new(),
|
|
2957
2960
|
named_set: Default::default(),
|
|
2958
2961
|
match_scratch: HashMap::new(),
|
|
@@ -3044,7 +3047,6 @@ fn compileFnBody(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Resu
|
|
|
3044
3047
|
string_concat_func: ctx.string_concat_func,
|
|
3045
3048
|
string_eq_func: ctx.string_eq_func,
|
|
3046
3049
|
int_to_string_func: ctx.int_to_string_func,
|
|
3047
|
-
classes: &ctx.classes,
|
|
3048
3050
|
methods: &ctx.methods,
|
|
3049
3051
|
enum_variants: &ctx.enum_variants,
|
|
3050
3052
|
enum_params: &ctx.enum_params,
|
|
@@ -3307,47 +3309,36 @@ fn compileStmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
|
|
|
3307
3309
|
// struct.set expects [(ref null $t) value] on the stack (ref
|
|
3308
3310
|
// pushed first/deeper, value second/on top) — same push order
|
|
3309
3311
|
// this already used for the old memory store. Mirrors the read
|
|
3310
|
-
// side's
|
|
3312
|
+
// side's discriminant-enum-param / single-owning-variant
|
|
3311
|
-
// fallback chain (`AttrKind::Field` below)
|
|
3313
|
+
// fallback chain (`AttrKind::Field` below) — this also covers a
|
|
3312
|
-
//
|
|
3314
|
+
// record-shaped enum's own field (the `type X = ...`
|
|
3315
|
+
// replacement), whose one variant is trivially its own unique
|
|
3316
|
+
// owner of every field it declares.
|
|
3313
|
-
match ctx.
|
|
3317
|
+
match ctx.enum_params.get(&class_name) {
|
|
3314
|
-
Some(
|
|
3318
|
+
Some(params) => {
|
|
3315
|
-
let field_idx =
|
|
3319
|
+
let field_idx = params
|
|
3316
3320
|
.iter()
|
|
3317
3321
|
.position(|(n, _)| n == field_name)
|
|
3318
|
-
.ok_or_else(|| format!("codegen: no field '{}' on
|
|
3322
|
+
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
3319
|
-
let
|
|
3323
|
+
let super_type_idx = *ctx.gc_types.enum_super_type_idx.get(&class_name)
|
|
3320
|
-
.ok_or_else(|| format!("codegen:
|
|
3324
|
+
.ok_or_else(|| format!("codegen: enum '{}' missing from the GC type registry", class_name))?;
|
|
3321
3325
|
compileExpr(object, body, ctx, state)?;
|
|
3322
3326
|
compileExpr(value, body, ctx, state)?;
|
|
3323
|
-
Instruction::StructSet { struct_type_index:
|
|
3327
|
+
Instruction::StructSet { struct_type_index: super_type_idx, field_index: field_idx as u32 }.encode(body);
|
|
3328
|
+
}
|
|
3329
|
+
None => {
|
|
3330
|
+
let (variant_name, info) = ctx.enum_variants.iter()
|
|
3331
|
+
.find(|(_, info)| info.enum_name == class_name && info.field_names.iter().any(|n| n == field_name))
|
|
3332
|
+
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
3333
|
+
let field_idx = info.field_names.iter().position(|n| n == field_name)
|
|
3334
|
+
.expect("just found by this field name");
|
|
3335
|
+
let variant_type_idx = *ctx.gc_types.variant_type_idx.get(variant_name)
|
|
3336
|
+
.ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", variant_name))?;
|
|
3337
|
+
compileExpr(object, body, ctx, state)?;
|
|
3338
|
+
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx)).encode(body);
|
|
3339
|
+
compileExpr(value, body, ctx, state)?;
|
|
3340
|
+
Instruction::StructSet { struct_type_index: variant_type_idx, field_index: field_idx as u32 }.encode(body);
|
|
3324
3341
|
}
|
|
3325
|
-
None => match ctx.enum_params.get(&class_name) {
|
|
3326
|
-
Some(params) => {
|
|
3327
|
-
let field_idx = params
|
|
3328
|
-
.iter()
|
|
3329
|
-
.position(|(n, _)| n == field_name)
|
|
3330
|
-
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
3331
|
-
let super_type_idx = *ctx.gc_types.enum_super_type_idx.get(&class_name)
|
|
3332
|
-
.ok_or_else(|| format!("codegen: enum '{}' missing from the GC type registry", class_name))?;
|
|
3333
|
-
compileExpr(object, body, ctx, state)?;
|
|
3334
|
-
compileExpr(value, body, ctx, state)?;
|
|
3335
|
-
Instruction::StructSet { struct_type_index: super_type_idx, field_index: field_idx as u32 }.encode(body);
|
|
3336
|
-
}
|
|
3337
|
-
None => {
|
|
3338
|
-
let (variant_name, info) = ctx.enum_variants.iter()
|
|
3339
|
-
.find(|(_, info)| info.enum_name == class_name && info.field_names.iter().any(|n| n == field_name))
|
|
3340
|
-
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
3341
|
-
let field_idx = info.field_names.iter().position(|n| n == field_name)
|
|
3342
|
-
.expect("just found by this field name");
|
|
3343
|
-
let variant_type_idx = *ctx.gc_types.variant_type_idx.get(variant_name)
|
|
3344
|
-
.ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", variant_name))?;
|
|
3345
|
-
compileExpr(object, body, ctx, state)?;
|
|
3346
|
-
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx)).encode(body);
|
|
3347
|
-
compileExpr(value, body, ctx, state)?;
|
|
3348
|
-
Instruction::StructSet { struct_type_index: variant_type_idx, field_index: field_idx as u32 }.encode(body);
|
|
3349
|
-
}
|
|
3350
|
-
},
|
|
3351
3342
|
}
|
|
3352
3343
|
}
|
|
3353
3344
|
}
|
|
@@ -3780,11 +3771,7 @@ fn compileCasePositions(
|
|
|
3780
3771
|
ast::CasePattern::String(_) => Err("codegen: string match patterns are not yet supported".to_string()),
|
|
3781
3772
|
ast::CasePattern::Float(_) => Err("codegen: float match patterns are not yet supported".to_string()),
|
|
3782
3773
|
ast::CasePattern::Class { name, fields } => {
|
|
3783
|
-
if ctx.enum_variants.contains_key(name) {
|
|
3784
|
-
|
|
3774
|
+
compileVariantConstructorArm(pat, name, fields, subject_vt, scratch_local, case, pos, all_subjects, rest, result_vt, exhaustive_fallback, body, ctx, state)
|
|
3785
|
-
} else {
|
|
3786
|
-
compileClassDestructureArm(name, fields, subject_vt, scratch_local, case, pos, all_subjects, rest, result_vt, exhaustive_fallback, body, ctx, state)
|
|
3787
|
-
}
|
|
3788
3775
|
}
|
|
3789
3776
|
}
|
|
3790
3777
|
}
|
|
@@ -3891,51 +3878,6 @@ fn compileVariantConstructorArm(
|
|
|
3891
3878
|
Ok(())
|
|
3892
3879
|
}
|
|
3893
3880
|
|
|
3894
|
-
/// Destructures a PLAIN CLASS constructor pattern (`Point(x, y)` where `Point` is a
|
|
3895
|
-
/// `type`, not an enum variant) — the counterpart to `compileVariantConstructorArm`.
|
|
3896
|
-
/// A class subject has only "the one shape": there's no discriminant to test, and
|
|
3897
|
-
/// the subject's own static type already IS this exact concrete GC struct type (the
|
|
3898
|
-
/// checker already verified `name` matches it), so this always matches — just
|
|
3899
|
-
/// destructure fields straight off `scratch_local` itself, no `ref.test`/`ref.cast`
|
|
3900
|
-
/// narrowing or separate scratch local needed at all.
|
|
3901
|
-
#[allow(clippy::too_many_arguments)]
|
|
3902
|
-
fn compileClassDestructureArm(
|
|
3903
|
-
name: &str,
|
|
3904
|
-
fields: &[ast::CasePattern],
|
|
3905
|
-
subject_vt: ValType,
|
|
3906
|
-
scratch_local: u32,
|
|
3907
|
-
case: &ast::Case,
|
|
3908
|
-
pos: usize,
|
|
3909
|
-
all_subjects: &[(ValType, u32)],
|
|
3910
|
-
rest: &[ast::Case],
|
|
3911
|
-
result_vt: Option<ValType>,
|
|
3912
|
-
exhaustive_fallback: bool,
|
|
3913
|
-
body: &mut Vec<u8>,
|
|
3914
|
-
ctx: &LocalCtx,
|
|
3915
|
-
state: &mut ModuleState,
|
|
3916
|
-
) -> Result<(), String> {
|
|
3917
|
-
let class_fields = ctx
|
|
3918
|
-
.classes
|
|
3919
|
-
.get(name)
|
|
3920
|
-
.ok_or_else(|| format!("codegen: unknown constructor '{}' (neither an enum variant nor a class)", name))?;
|
|
3921
|
-
if !matches!(subject_vt, ValType::Ref(_)) {
|
|
3922
|
-
return Err(format!("codegen: constructor pattern '{}' against a non-class subject", name));
|
|
3923
|
-
}
|
|
3924
|
-
if fields.len() != class_fields.len() {
|
|
3925
|
-
return Err(format!(
|
|
3926
|
-
"codegen: constructor pattern '{}' expects {} field(s), got {}",
|
|
3927
|
-
name, class_fields.len(), fields.len()
|
|
3928
|
-
));
|
|
3929
|
-
}
|
|
3930
|
-
let class_type_idx = *ctx.gc_types.class_type_idx.get(name)
|
|
3931
|
-
.ok_or_else(|| format!("codegen: class '{}' missing from the GC type registry", name))?;
|
|
3932
|
-
let field_types: Vec<PlumType> = class_fields.iter().map(|(_, t)| t.clone()).collect();
|
|
3933
|
-
compileFieldPatterns(
|
|
3934
|
-
fields, &field_types, 0, scratch_local, class_type_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state,
|
|
3935
|
-
&mut |body, state| compileCasePositions(case, pos + 1, all_subjects, rest, result_vt, exhaustive_fallback, body, ctx, state),
|
|
3936
|
-
)
|
|
3937
|
-
}
|
|
3938
|
-
|
|
3939
3881
|
/// Checks `fields[fpos..]` (a constructor pattern's own sub-patterns, e.g. the `v` in
|
|
3940
3882
|
/// `Some(v)`, or — recursively — the `Some(v)` in `Wrap(Some(v))`) against the
|
|
3941
3883
|
/// already-loaded value in `container_local`, one field at a time. Once every field
|
|
@@ -4021,8 +3963,9 @@ fn compileFieldPatterns(
|
|
|
4021
3963
|
}
|
|
4022
3964
|
ast::CasePattern::String(_) => Err("codegen: string match patterns are not yet supported".to_string()),
|
|
4023
3965
|
ast::CasePattern::Float(_) => Err("codegen: float match patterns are not yet supported".to_string()),
|
|
4024
|
-
ast::CasePattern::Class { name, fields: inner_fields }
|
|
3966
|
+
ast::CasePattern::Class { name, fields: inner_fields } => {
|
|
4025
|
-
let info = ctx.enum_variants.get(name)
|
|
3967
|
+
let info = ctx.enum_variants.get(name)
|
|
3968
|
+
.ok_or_else(|| format!("codegen: unknown enum variant '{}'", name))?;
|
|
4026
3969
|
if !matches!(plumTypeToValtype(field_ty), ValType::Ref(_)) {
|
|
4027
3970
|
return Err(format!("codegen: constructor pattern '{}' against a non-enum field", name));
|
|
4028
3971
|
}
|
|
@@ -4059,42 +4002,6 @@ fn compileFieldPatterns(
|
|
|
4059
4002
|
Instruction::End.encode(body);
|
|
4060
4003
|
Ok(())
|
|
4061
4004
|
}
|
|
4062
|
-
// A plain class nested inside another constructor pattern's fields (see
|
|
4063
|
-
// `compileClassDestructureArm`'s doc comment) — there's only ever "the one
|
|
4064
|
-
// shape" to match, so no `ref.test`/`ref.cast` narrowing is needed; just
|
|
4065
|
-
// load the field into its own local (`compileFieldPatterns` needs a LOCAL
|
|
4066
|
-
// to repeatedly `struct.get` against, not a bare stack value) and recurse.
|
|
4067
|
-
ast::CasePattern::Class { name, fields: inner_fields } => {
|
|
4068
|
-
let class_fields = ctx
|
|
4069
|
-
.classes
|
|
4070
|
-
.get(name)
|
|
4071
|
-
.ok_or_else(|| format!("codegen: unknown constructor '{}' (neither an enum variant nor a class)", name))?;
|
|
4072
|
-
if !matches!(plumTypeToValtype(field_ty), ValType::Ref(_)) {
|
|
4073
|
-
return Err(format!("codegen: constructor pattern '{}' against a non-class field", name));
|
|
4074
|
-
}
|
|
4075
|
-
if inner_fields.len() != class_fields.len() {
|
|
4076
|
-
return Err(format!(
|
|
4077
|
-
"codegen: constructor pattern '{}' expects {} field(s), got {}",
|
|
4078
|
-
name, class_fields.len(), inner_fields.len()
|
|
4079
|
-
));
|
|
4080
|
-
}
|
|
4081
|
-
let inner_class_type_idx = *ctx.gc_types.class_type_idx.get(name)
|
|
4082
|
-
.ok_or_else(|| format!("codegen: class '{}' missing from the GC type registry", name))?;
|
|
4083
|
-
let key = pat as *const ast::CasePattern as usize;
|
|
4084
|
-
let slot = *ctx
|
|
4085
|
-
.nested_class_scratch
|
|
4086
|
-
.get(&key)
|
|
4087
|
-
.ok_or_else(|| "internal codegen error: missing nested constructor pattern scratch slot".to_string())?;
|
|
4088
|
-
let nested_local = ctx.nested_class_scratch_base + slot;
|
|
4089
|
-
let inner_field_types: Vec<PlumType> = class_fields.iter().map(|(_, t)| t.clone()).collect();
|
|
4090
|
-
|
|
4091
|
-
Instruction::LocalGet(container_local).encode(body);
|
|
4092
|
-
Instruction::StructGet { struct_type_index: container_type_idx, field_index: fpos as u32 }.encode(body);
|
|
4093
|
-
Instruction::LocalSet(nested_local).encode(body);
|
|
4094
|
-
compileFieldPatterns(inner_fields, &inner_field_types, 0, nested_local, inner_class_type_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, &mut |body, state| {
|
|
4095
|
-
compileFieldPatterns(fields, field_types, fpos + 1, container_local, container_type_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)
|
|
4096
|
-
})
|
|
4097
|
-
}
|
|
4098
4005
|
}
|
|
4099
4006
|
}
|
|
4100
4007
|
|
|
@@ -4307,18 +4214,22 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
|
|
|
4307
4214
|
}
|
|
4308
4215
|
} else if is_closure_call {
|
|
4309
4216
|
compileClosureCall(call, body, ctx, state)?;
|
|
4310
|
-
} else if let Some(info) = ctx.enum_variants.get(&call.name) {
|
|
4311
|
-
compileVariantConstruction(info, call, expr, body, ctx, state)?;
|
|
4312
|
-
} else if ctx.classes.contains_key(&call.name) && ctx.methods.contains_key(&(call.name.clone(), "init".to_string())) {
|
|
4313
|
-
|
|
4217
|
+
// `Type(1, 2, 3)` — mirrors `plum-checker`'s own `inferExpr` handling of
|
|
4314
|
-
|
|
4218
|
+
// the same case: sugar for `Type.init(1, 2, 3)`, desugared to that
|
|
4315
|
-
|
|
4219
|
+
// equivalent static method-call expression so the existing
|
|
4316
|
-
|
|
4220
|
+
// `AttrKind::Method` codegen (below) handles it without duplication.
|
|
4221
|
+
// Checked BEFORE the ordinary positional-variant-construction case just
|
|
4222
|
+
// below — see the matching comment in `plum-checker`'s `inferExpr` for
|
|
4223
|
+
// why a record-shaped enum's own name needs this precedence.
|
|
4224
|
+
} else if ctx.enum_variants.get(&call.name).is_some_and(|info| info.enum_name == call.name)
|
|
4225
|
+
&& ctx.methods.contains_key(&(call.name.clone(), "init".to_string())) {
|
|
4317
4226
|
let synthetic = ast::Expr::Attribute(Box::new(ast::AttributeExpr {
|
|
4318
4227
|
object: ast::Expr::TypeName(call.name.clone()),
|
|
4319
4228
|
attr: ast::AttrKind::Method(ast::FnCall { name: "init".to_string(), args: call.args.clone() }),
|
|
4320
4229
|
}));
|
|
4321
4230
|
compileExpr(&synthetic, body, ctx, state)?;
|
|
4231
|
+
} else if let Some(info) = ctx.enum_variants.get(&call.name) {
|
|
4232
|
+
compileVariantConstruction(info, call, expr, body, ctx, state)?;
|
|
4322
4233
|
} else {
|
|
4323
4234
|
fn argExprOf(arg: &ast::Arg) -> &ast::Expr {
|
|
4324
4235
|
match arg {
|
|
@@ -4423,13 +4334,13 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
|
|
|
4423
4334
|
// A NAMED-payload enum variant construction (`Circle(radius: 5)`, as
|
|
4424
4335
|
// opposed to the positional/generic-payload form `Some[T]` — constructed
|
|
4425
4336
|
// via `Expr::FnCall` instead, elsewhere) — mirrors `plum-checker`'s own
|
|
4426
|
-
// `inferClassCallRaw` handling of the same call shape.
|
|
4337
|
+
// `inferClassCallRaw` handling of the same call shape. Against the
|
|
4427
|
-
// shape as the plain-class arm just below, but against the variant's own
|
|
4428
|
-
// GC struct type (`gc_types.variant_type_idx`), matched by
|
|
4338
|
+
// variant's own GC struct type (`gc_types.variant_type_idx`), matched by
|
|
4429
|
-
// only for this named-payload form — see
|
|
4339
|
+
// NAME (recorded only for this named-payload form — see
|
|
4430
|
-
// instead of position.
|
|
4340
|
+
// `EnumVariantInfo::field_names`) instead of position. This ALSO covers a
|
|
4431
|
-
|
|
4341
|
+
// record-shaped enum's own construction (the `type X = ...` replacement,
|
|
4342
|
+
// e.g. `Cat(name: "x")`) — its one variant always has named fields too.
|
|
4432
|
-
|
|
4343
|
+
ast::Expr::ClassCall(call) if ctx.enum_variants.get(&call.type_name).is_some_and(|info| !info.field_names.is_empty()) =>
|
|
4433
4344
|
{
|
|
4434
4345
|
let info = ctx.enum_variants.get(&call.type_name).expect("just checked is_some");
|
|
4435
4346
|
let variant_idx = *ctx.gc_types.variant_type_idx.get(&call.type_name)
|
|
@@ -4458,32 +4369,12 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
|
|
|
4458
4369
|
}
|
|
4459
4370
|
Instruction::StructNew(variant_idx).encode(body);
|
|
4460
4371
|
}
|
|
4372
|
+
// Every named-payload construction now goes through the variant-construction
|
|
4373
|
+
// arm above (record-shaped or not) — reaching here means `call.type_name`
|
|
4374
|
+
// is genuinely unmodeled, which the checker already permits (`inferExpr`'s
|
|
4375
|
+
// "unmodeled type: allow, codegen will catch" case) — a real compile error.
|
|
4461
4376
|
ast::Expr::ClassCall(call) => {
|
|
4462
|
-
// struct.new needs every field value pushed in DECLARATION order (not
|
|
4463
|
-
// `call.fields`'s written order) immediately before the single
|
|
4464
|
-
// construction instruction — no intermediate scratch pointer needed at
|
|
4465
|
-
// all, unlike the old bump-pointer-then-store approach.
|
|
4466
|
-
let fields = ctx
|
|
4467
|
-
.classes
|
|
4468
|
-
.get(&call.type_name)
|
|
4469
|
-
|
|
4377
|
+
return Err(format!("codegen: unknown class or enum variant '{}'", call.type_name));
|
|
4470
|
-
.clone();
|
|
4471
|
-
let class_type_idx = *ctx.gc_types.class_type_idx.get(&call.type_name)
|
|
4472
|
-
.ok_or_else(|| format!("codegen: class '{}' missing from the GC type registry", call.type_name))?;
|
|
4473
|
-
|
|
4474
|
-
for (field_idx, (field_name, _)) in fields.iter().enumerate() {
|
|
4475
|
-
match call.fields.iter().find(|fa| &fa.name == field_name) {
|
|
4476
|
-
Some(fa) => compileExpr(&fa.value, body, ctx, state)?,
|
|
4477
|
-
// See the matching comment in the variant-construction arm above.
|
|
4478
|
-
None => {
|
|
4479
|
-
let spread = call.spread.as_ref()
|
|
4480
|
-
.ok_or_else(|| format!("codegen: class '{}' missing field '{}'", call.type_name, field_name))?;
|
|
4481
|
-
compileExpr(spread, body, ctx, state)?;
|
|
4482
|
-
Instruction::StructGet { struct_type_index: class_type_idx, field_index: field_idx as u32 }.encode(body);
|
|
4483
|
-
}
|
|
4484
|
-
}
|
|
4485
|
-
}
|
|
4486
|
-
Instruction::StructNew(class_type_idx).encode(body);
|
|
4487
4378
|
}
|
|
4488
4379
|
ast::Expr::Attribute(attr) => {
|
|
4489
4380
|
let obj_ty = inferLocalType(&attr.object, ctx);
|
|
@@ -4497,57 +4388,48 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
|
|
|
4497
4388
|
PlumType::TStr => "Str".to_string(),
|
|
4498
4389
|
other => return Err(format!("codegen: cannot access field '{}' on non-class type {}", field_name, other)),
|
|
4499
4390
|
};
|
|
4391
|
+
// Fall back to a discriminant enum's shared params, declared
|
|
4392
|
+
// directly on the enum's SUPERTYPE (see `buildGcTypeRegistry`'s
|
|
4393
|
+
// `EnumSuper` arm) — no `ref.cast` to any particular variant
|
|
4394
|
+
// needed, since every variant has the exact same field list as
|
|
4395
|
+
// the supertype itself. This ALSO covers a record-shaped enum's
|
|
4396
|
+
// own field (the `type X = ...` replacement) via the unique-
|
|
4397
|
+
// owning-variant fallback below, since its one variant is
|
|
4398
|
+
// trivially its own unique owner of every field it declares.
|
|
4500
|
-
match ctx.
|
|
4399
|
+
match ctx.enum_params.get(&class_name) {
|
|
4501
|
-
Some(
|
|
4400
|
+
Some(params) => {
|
|
4502
|
-
let field_idx =
|
|
4401
|
+
let field_idx = params
|
|
4503
4402
|
.iter()
|
|
4504
4403
|
.position(|(n, _)| n == field_name)
|
|
4505
|
-
.ok_or_else(|| format!("codegen: no field '{}' on
|
|
4404
|
+
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
4506
|
-
let
|
|
4405
|
+
let super_type_idx = *ctx.gc_types.enum_super_type_idx.get(&class_name)
|
|
4507
|
-
.ok_or_else(|| format!("codegen:
|
|
4406
|
+
.ok_or_else(|| format!("codegen: enum '{}' missing from the GC type registry", class_name))?;
|
|
4508
4407
|
compileExpr(&attr.object, body, ctx, state)?;
|
|
4509
|
-
Instruction::StructGet { struct_type_index:
|
|
4408
|
+
Instruction::StructGet { struct_type_index: super_type_idx, field_index: field_idx as u32 }.encode(body);
|
|
4409
|
+
}
|
|
4410
|
+
// Not a discriminant enum either: `class_name` is an
|
|
4411
|
+
// ordinary enum, and `plum-checker`'s `inferExpr` already
|
|
4412
|
+
// verified exactly ONE of its variants declares a field
|
|
4413
|
+
// with this name (ambiguity across sibling variants is a
|
|
4414
|
+
// checker error, never reaches codegen). Compile `.field`
|
|
4415
|
+
// as a checked downcast to that one variant's own
|
|
4416
|
+
// concrete struct type (same `ref.cast` idiom `match`
|
|
4417
|
+
// pattern-matching already uses, e.g. `compileCasePositions`),
|
|
4418
|
+
// then read the field off it — traps at runtime if the
|
|
4419
|
+
// value turns out to be a different variant, so no static
|
|
4420
|
+
// proof the value IS that variant is required here.
|
|
4421
|
+
None => {
|
|
4422
|
+
let (variant_name, info) = ctx.enum_variants.iter()
|
|
4423
|
+
.find(|(_, info)| info.enum_name == class_name && info.field_names.iter().any(|n| n == field_name))
|
|
4424
|
+
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
4425
|
+
let field_idx = info.field_names.iter().position(|n| n == field_name)
|
|
4426
|
+
.expect("just found by this field name");
|
|
4427
|
+
let variant_type_idx = *ctx.gc_types.variant_type_idx.get(variant_name)
|
|
4428
|
+
.ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", variant_name))?;
|
|
4429
|
+
compileExpr(&attr.object, body, ctx, state)?;
|
|
4430
|
+
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx)).encode(body);
|
|
4431
|
+
Instruction::StructGet { struct_type_index: variant_type_idx, field_index: field_idx as u32 }.encode(body);
|
|
4510
4432
|
}
|
|
4511
|
-
// Not a class: fall back to a discriminant enum's shared params,
|
|
4512
|
-
// declared directly on the enum's SUPERTYPE (see
|
|
4513
|
-
// `buildGcTypeRegistry`'s `EnumSuper` arm) — no `ref.cast` to any
|
|
4514
|
-
// particular variant needed, since every variant has the exact
|
|
4515
|
-
// same field list as the supertype itself.
|
|
4516
|
-
None => match ctx.enum_params.get(&class_name) {
|
|
4517
|
-
Some(params) => {
|
|
4518
|
-
let field_idx = params
|
|
4519
|
-
.iter()
|
|
4520
|
-
.position(|(n, _)| n == field_name)
|
|
4521
|
-
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
4522
|
-
let super_type_idx = *ctx.gc_types.enum_super_type_idx.get(&class_name)
|
|
4523
|
-
.ok_or_else(|| format!("codegen: enum '{}' missing from the GC type registry", class_name))?;
|
|
4524
|
-
compileExpr(&attr.object, body, ctx, state)?;
|
|
4525
|
-
Instruction::StructGet { struct_type_index: super_type_idx, field_index: field_idx as u32 }.encode(body);
|
|
4526
|
-
}
|
|
4527
|
-
// Not a discriminant enum either: `class_name` is an
|
|
4528
|
-
// ordinary enum, and `plum-checker`'s `inferExpr` already
|
|
4529
|
-
// verified exactly ONE of its variants declares a field
|
|
4530
|
-
// with this name (ambiguity across sibling variants is a
|
|
4531
|
-
// checker error, never reaches codegen). Compile `.field`
|
|
4532
|
-
// as a checked downcast to that one variant's own
|
|
4533
|
-
// concrete struct type (same `ref.cast` idiom `match`
|
|
4534
|
-
// pattern-matching already uses, e.g. `compileCasePositions`),
|
|
4535
|
-
// then read the field off it — traps at runtime if the
|
|
4536
|
-
// value turns out to be a different variant, so no static
|
|
4537
|
-
// proof the value IS that variant is required here.
|
|
4538
|
-
None => {
|
|
4539
|
-
let (variant_name, info) = ctx.enum_variants.iter()
|
|
4540
|
-
.find(|(_, info)| info.enum_name == class_name && info.field_names.iter().any(|n| n == field_name))
|
|
4541
|
-
.ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
|
|
4542
|
-
let field_idx = info.field_names.iter().position(|n| n == field_name)
|
|
4543
|
-
.expect("just found by this field name");
|
|
4544
|
-
let variant_type_idx = *ctx.gc_types.variant_type_idx.get(variant_name)
|
|
4545
|
-
.ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", variant_name))?;
|
|
4546
|
-
compileExpr(&attr.object, body, ctx, state)?;
|
|
4547
|
-
Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx)).encode(body);
|
|
4548
|
-
Instruction::StructGet { struct_type_index: variant_type_idx, field_index: field_idx as u32 }.encode(body);
|
|
4549
|
-
}
|
|
4550
|
-
},
|
|
4551
4433
|
}
|
|
4552
4434
|
}
|
|
4553
4435
|
ast::AttrKind::Method(call) => {
|
|
@@ -4972,7 +4854,7 @@ fn compileClosureBody(
|
|
|
4972
4854
|
|
|
4973
4855
|
let mut collector = Collector {
|
|
4974
4856
|
env: base_env.clone(),
|
|
4975
|
-
cctx: checkCtxOf(&ctx.
|
|
4857
|
+
cctx: checkCtxOf(&ctx.methods, &ctx.enum_variants, &ctx.enum_params, &ctx.min_required),
|
|
4976
4858
|
named: Vec::new(),
|
|
4977
4859
|
named_set: Default::default(),
|
|
4978
4860
|
match_scratch: HashMap::new(),
|
|
@@ -5074,7 +4956,6 @@ fn compileClosureBody(
|
|
|
5074
4956
|
string_concat_func: ctx.string_concat_func,
|
|
5075
4957
|
string_eq_func: ctx.string_eq_func,
|
|
5076
4958
|
int_to_string_func: ctx.int_to_string_func,
|
|
5077
|
-
classes: &ctx.classes,
|
|
5078
4959
|
methods: &ctx.methods,
|
|
5079
4960
|
enum_variants: &ctx.enum_variants,
|
|
5080
4961
|
enum_params: &ctx.enum_params,
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -208,9 +208,8 @@ fn floatArithmeticAndNegationCompile() {
|
|
|
208
208
|
#[test]
|
|
209
209
|
fn classFieldAndMethodCompile() {
|
|
210
210
|
let src = "\
|
|
211
|
-
|
|
211
|
+
enum Cat =
|
|
212
|
-
name: Str
|
|
212
|
+
| Cat(name: Str, age: Int)
|
|
213
|
-
age: Int
|
|
214
213
|
|
|
215
214
|
fun getAge() -> Int =
|
|
216
215
|
self.age
|
|
@@ -225,13 +224,11 @@ fun makeCat() -> Int =
|
|
|
225
224
|
#[test]
|
|
226
225
|
fn nestedClassCallCompiles() {
|
|
227
226
|
let src = "\
|
|
228
|
-
|
|
227
|
+
enum Pair =
|
|
229
|
-
a: Int
|
|
228
|
+
| Pair(a: Int, b: Int)
|
|
230
|
-
b: Int
|
|
231
229
|
|
|
232
|
-
|
|
230
|
+
enum Wrapper =
|
|
233
|
-
inner: Pair
|
|
231
|
+
| Wrapper(inner: Pair, tag: Int)
|
|
234
|
-
tag: Int
|
|
235
232
|
|
|
236
233
|
fun make() -> Int =
|
|
237
234
|
w = Wrapper(inner: Pair(a: 1, b: 2), tag: 9)
|