plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
6e346ab
— Peter John
2026-10-25T16:50:16+05:30
chore: drop stale Option/Result references from comments and tests
- plum-checker/src/lib.rs +16 -16
- plum-checker/src/monomorphize.rs +81 -81
- plum-checker/tests/checker_tests.rs +60 -60
- plum-checker/tests/monomorphize_tests.rs +8 -8
- plum-core/src/ast.rs +6 -6
- plum-core/src/parser.rs +2 -2
- plum-examples/error_propagation.plum +1 -1
- plum-std/Array.plum +1 -1
- plum-std/Try.plum +1 -1
- plum-tooling/tree-sitter-plum/grammar.js +9 -10
- plum-wasm-codegen/src/lib.rs +22 -22
- plum-wasm-codegen/tests/codegen_tests.rs +6 -6
plum-checker/src/lib.rs
CHANGED
|
@@ -333,7 +333,7 @@ pub fn lookup(env: &TypeEnv, name: &str) -> Result<PlumType, String> {
|
|
|
333
333
|
pub type MethodEnv = BTreeMap<(String, String), PlumType>;
|
|
334
334
|
/// Info about one `enum` variant: which enum it belongs to, its 0-based runtime tag
|
|
335
335
|
/// (numbering is shared across all of that enum's variants), and its payload field
|
|
336
|
-
/// types (empty for a payload-free variant like `Red` or `
|
|
336
|
+
/// types (empty for a payload-free variant like `Red` or `End`).
|
|
337
337
|
#[derive(Debug, Clone, PartialEq)]
|
|
338
338
|
pub struct EnumVariantInfo {
|
|
339
339
|
pub enum_name: String,
|
|
@@ -341,7 +341,7 @@ pub struct EnumVariantInfo {
|
|
|
341
341
|
pub field_types: Vec<PlumType>,
|
|
342
342
|
/// Field names for a NAMED-payload variant (`Circle(radius: Int)`), aligned
|
|
343
343
|
/// index-for-index with `field_types` — empty for the positional/generic-payload
|
|
344
|
-
/// form (`
|
|
344
|
+
/// form (`Next(T)`). See `ast::EnumVariant::field_names`.
|
|
345
345
|
pub field_names: Vec<String>,
|
|
346
346
|
pub values: Vec<ast::Expr>,
|
|
347
347
|
}
|
|
@@ -543,9 +543,9 @@ pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, MethodEnv, EnumVaria
|
|
|
543
543
|
|
|
544
544
|
/// A `Type`'s name for comparison purposes only — deliberately ignores `.generics`
|
|
545
545
|
/// entirely (on both sides), so a trait's bare return/param type (`cause() ->
|
|
546
|
-
///
|
|
546
|
+
/// Link`, unable to name a concrete payload since a trait has no instance to
|
|
547
547
|
/// infer one from) is treated as compatible with ANY instantiation an implementer
|
|
548
|
-
/// declares (`cause(self) ->
|
|
548
|
+
/// declares (`cause(self) -> Link[JsonParseError]`) — a real but deliberately
|
|
549
549
|
/// coarse check (arity + base type names), not full structural/generic equality.
|
|
550
550
|
fn paramTypeCompareName(pt: &ast::ParamType) -> Option<&str> {
|
|
551
551
|
match pt {
|
|
@@ -571,7 +571,7 @@ fn paramTypeCompareName(pt: &ast::ParamType) -> Option<&str> {
|
|
|
571
571
|
/// a generic class's own methods (and a real trait's own declared signatures,
|
|
572
572
|
/// which are never specialized at all) read naturally here, before mangling
|
|
573
573
|
/// folds a method's concrete return/param types into new, unrelated-looking
|
|
574
|
-
/// names (`
|
|
574
|
+
/// names (`Link[JsonParseError]` -> `Link$JsonParseError`).
|
|
575
575
|
fn checkTraitConformance(source: &ast::Source) -> Vec<CheckError> {
|
|
576
576
|
let mut errors = Vec::new();
|
|
577
577
|
let traits: BTreeMap<&str, &ast::Trait> = source
|
|
@@ -668,7 +668,7 @@ pub fn checkSource(source: &ast::Source) -> CheckResult<()> {
|
|
|
668
668
|
// A discriminant enum (`enum Foo(n: Int) = ...`) requires every variant to supply
|
|
669
669
|
// exactly one value per declared param, unified against that param's type. An
|
|
670
670
|
// ordinary (param-less) enum must NOT have variants with values — most likely
|
|
671
|
-
// caused by writing `
|
|
671
|
+
// caused by writing `Next(5)` in an enum that declares no discriminant params.
|
|
672
672
|
for item in &source.items {
|
|
673
673
|
if let ast::Item::Enum(e) = item {
|
|
674
674
|
for v in &e.variants {
|
|
@@ -1425,7 +1425,7 @@ fn inferClassCallRaw(
|
|
|
1425
1425
|
}
|
|
1426
1426
|
match ctx.enum_variants.get(&call.type_name) {
|
|
1427
1427
|
// A NAMED-payload enum variant (`Circle(radius: Int)`, as opposed to the
|
|
1428
|
-
// positional/generic-payload form `
|
|
1428
|
+
// positional/generic-payload form `Next(T)`, which never sets `field_names`
|
|
1429
1429
|
// and so falls through to the permissive "unmodeled" case below) — this is
|
|
1430
1430
|
// ALSO how a record-shaped, single-variant enum (the `type X = ...`
|
|
1431
1431
|
// replacement, e.g. `enum Cat = | Cat(name: Str)`) constructs, with no
|
|
@@ -1465,7 +1465,7 @@ fn inferClassCallRaw(
|
|
|
1465
1465
|
}
|
|
1466
1466
|
Ok(PlumType::TNamed(info.enum_name.clone()))
|
|
1467
1467
|
}
|
|
1468
|
-
// Positional/generic-payload variant (`
|
|
1468
|
+
// Positional/generic-payload variant (`Next(T)`, constructed via `Expr::FnCall`
|
|
1469
1469
|
// elsewhere instead), a payload-free variant, or an unmodeled (e.g.
|
|
1470
1470
|
// builtin/std) type: allow, codegen will catch a genuine mismatch.
|
|
1471
1471
|
_ => Ok(PlumType::TNamed(call.type_name.clone())),
|
|
@@ -1487,7 +1487,7 @@ fn bareVariantWrapType(type_name: &str, ctx: &CheckCtx) -> PlumType {
|
|
|
1487
1487
|
}
|
|
1488
1488
|
|
|
1489
1489
|
/// Checks a single case pattern against the type of the subject it matches, binding any
|
|
1490
|
-
/// new names it introduces into `env`. Constructor-payload sub-patterns (`
|
|
1490
|
+
/// new names it introduces into `env`. Constructor-payload sub-patterns (`Next(x)`) bind
|
|
1491
1491
|
/// against that variant's declared field types (see `EnumVariantInfo::field_types`).
|
|
1492
1492
|
fn checkPattern(
|
|
1493
1493
|
pat: &ast::CasePattern,
|
|
@@ -1700,8 +1700,8 @@ pub(crate) fn lookupFieldType(
|
|
|
1700
1700
|
// from here: `class_name` names a real, known NAMED-payload-capable
|
|
1701
1701
|
// enum that simply has no field by this name (a hard error), or it's
|
|
1702
1702
|
// an unresolved/unmodeled type name, OR a real enum whose variants
|
|
1703
|
-
// are all positional/payload-free (`enum
|
|
1703
|
+
// are all positional/payload-free (`enum Link = | Next(Int) |
|
|
1704
|
-
//
|
|
1704
|
+
// End`, no `field_names` at all to check against — permissive,
|
|
1705
1705
|
// codegen will catch a genuine mismatch, exactly as it always has).
|
|
1706
1706
|
// Distinguish by whether any variant belonging to this enum name
|
|
1707
1707
|
// declares named fields at all.
|
|
@@ -1726,7 +1726,7 @@ pub(crate) fn lookupFieldType(
|
|
|
1726
1726
|
}
|
|
1727
1727
|
|
|
1728
1728
|
/// A mangled variant name's own unmangled base (`"Ok$Int$Str"` -> `"Ok"`,
|
|
1729
|
-
/// `"
|
|
1729
|
+
/// `"End"` -> `"End"`) — `monomorphize::mangle` always joins the original
|
|
1730
1730
|
/// variant name and its resolved type args with `$`, so the base is simply
|
|
1731
1731
|
/// everything before the first `$`.
|
|
1732
1732
|
fn variantBaseName(name: &str) -> &str {
|
|
@@ -1737,8 +1737,8 @@ fn variantBaseName(name: &str) -> &str {
|
|
|
1737
1737
|
/// belonging to the same enum as `enum_name`, for the `?` operator (`Expr::Try`)
|
|
1738
1738
|
/// — used identically by the checker (`inferExpr`) and codegen (`inferLocalType`
|
|
1739
1739
|
/// callers), which is why this lives here rather than inline in either. Returns
|
|
1740
|
-
/// `None` if `enum_name` isn't (a monomorphized specialization of) `
|
|
1740
|
+
/// `None` if `enum_name` isn't (a monomorphized specialization of) `Try`
|
|
1741
|
-
///
|
|
1741
|
+
/// — i.e. has no `Ok`/`Err` pair of variants.
|
|
1742
1742
|
pub fn tryOperatorVariants<'a>(
|
|
1743
1743
|
enum_name: &str,
|
|
1744
1744
|
ctx: &'a CheckCtx,
|
|
@@ -1836,7 +1836,7 @@ pub fn inferExpr(expr: &ast::Expr, env: &TypeEnv, ctx: &CheckCtx) -> Result<Plum
|
|
|
1836
1836
|
},
|
|
1837
1837
|
ast::Expr::Paren(inner) => inferExpr(inner, env, ctx),
|
|
1838
1838
|
// `expr?` — deliberately does NOT check that the enclosing function's
|
|
1839
|
-
// return type actually accepts the `Err`
|
|
1839
|
+
// return type actually accepts the `Err` value this can exit
|
|
1840
1840
|
// with early; that's left permissive here (codegen will catch a
|
|
1841
1841
|
// genuine mismatch as a wasm type error), consistent with this
|
|
1842
1842
|
// checker's existing "unmodeled/unvalidated, codegen catches it"
|
|
@@ -2245,7 +2245,7 @@ pub fn inferExpr(expr: &ast::Expr, env: &TypeEnv, ctx: &CheckCtx) -> Result<Plum
|
|
|
2245
2245
|
};
|
|
2246
2246
|
lookupFieldType(class_name, field_name, ctx)
|
|
2247
2247
|
}
|
|
2248
|
-
// An unresolved generic method return (e.g. `
|
|
2248
|
+
// An unresolved generic method return (e.g. `Try[T, E].unwrap()`'s
|
|
2249
2249
|
// bare `T`, which this checker's `PlumType` erases entirely — see
|
|
2250
2250
|
// `types.rs`'s `TNamed` having no type-argument slot) reaches here as
|
|
2251
2251
|
// a plain `TVar`, not a real "wrong type". Field access on it is
|
plum-checker/src/monomorphize.rs
CHANGED
|
@@ -49,7 +49,7 @@ pub fn fnGenericParams(f: &ast::Fn) -> Vec<String> {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/// The generic parameter names an `Enum` introduces — its OWN declared
|
|
52
|
-
/// generics (`enum
|
|
52
|
+
/// generics (`enum Link[T] = ...`) if it declares any, else (for an
|
|
53
53
|
/// enum that doesn't) every distinct
|
|
54
54
|
/// single-uppercase-letter variant field type name, in first-appearance
|
|
55
55
|
/// order — the older, purely-implicit inference this replaces.
|
|
@@ -90,7 +90,7 @@ enum ExtraGenericSource {
|
|
|
90
90
|
/// plain bare-letter param like `reduce`'s `acc: U` is also covered, via the
|
|
91
91
|
/// same detection `fnGenericParams` already does for non-closure params). A
|
|
92
92
|
/// closure's return type can ALSO nest the extra letter inside a container
|
|
93
|
-
/// (`
|
|
93
|
+
/// (`Link.andThen`'s `cb: fn(T) -> Link[U]`), not just be the bare
|
|
94
94
|
/// letter itself (`List.map`'s `cb: fn(T) -> U`) — `collectGenericParamNames`
|
|
95
95
|
/// recurses to find either shape; `resolveMethodOwnGenerics`'s
|
|
96
96
|
/// `ClosureReturn` handling knows how to bind a nested one back out.
|
|
@@ -382,11 +382,11 @@ fn substituteTypesInExpr(expr: &mut ast::Expr, subst: &Substitution) {
|
|
|
382
382
|
/// generic parameters with its resolved concrete type's name.
|
|
383
383
|
///
|
|
384
384
|
/// Variant names are ALSO mangled here, with the same suffix as the enum's own
|
|
385
|
-
/// name (e.g. `
|
|
385
|
+
/// name (e.g. `Next` -> `Next$Int`) — even a payload-free variant like `End`.
|
|
386
386
|
/// This is necessary because the runtime `EnumVariants` table (built by
|
|
387
387
|
/// `buildGlobalTables`) is keyed by bare variant name globally: without this,
|
|
388
388
|
/// two specializations of the same generic enum would both register a variant
|
|
389
|
-
/// literally named `
|
|
389
|
+
/// literally named `Next`, colliding in that flat table.
|
|
390
390
|
pub fn specializeEnum(e: &ast::Enum, subst: &Substitution, mangled_name: &str) -> ast::Enum {
|
|
391
391
|
let params = enumGenericParams(e);
|
|
392
392
|
let type_args: Vec<PlumType> = params
|
|
@@ -591,16 +591,16 @@ fn renameVarInArg(arg: &mut ast::Arg, old: &str, new: &str) {
|
|
|
591
591
|
|
|
592
592
|
struct Monomorphizer<'a> {
|
|
593
593
|
fns_generic: BTreeMap<String, &'a ast::Fn>,
|
|
594
|
-
/// Every method declared on a generic enum (e.g. `
|
|
594
|
+
/// Every method declared on a generic enum (e.g. `Try`'s `isOk`/`isErr`,
|
|
595
595
|
/// or a record-shaped `List`/`Map`'s own methods), keyed by the enum's own
|
|
596
596
|
/// bare name.
|
|
597
597
|
methods_generic_on_enum: BTreeMap<String, Vec<&'a ast::Fn>>,
|
|
598
|
-
/// Bare variant name (e.g. `"
|
|
598
|
+
/// Bare variant name (e.g. `"Next"`) -> the generic `Enum` it belongs to. Keyed
|
|
599
|
-
/// by variant name because a construction site (`
|
|
599
|
+
/// by variant name because a construction site (`Next(5)`) parses as a `FnCall`
|
|
600
600
|
/// whose `name` is the VARIANT, not the enum's own name.
|
|
601
601
|
enums_generic_by_variant: BTreeMap<String, &'a ast::Enum>,
|
|
602
602
|
/// Mangled enum name -> {original variant name -> mangled variant name}, e.g.
|
|
603
|
-
/// `"
|
|
603
|
+
/// `"Link$Int" -> {"Next": "Next$Int", "End": "End$Int"}`. Populated eagerly
|
|
604
604
|
/// (in `resolveEnumInstantiation`, at the moment an instantiation's concrete
|
|
605
605
|
/// type arguments become known) rather than waiting for the worklist to actually
|
|
606
606
|
/// produce that specialization — so both a construction call site and a later
|
|
@@ -608,13 +608,13 @@ struct Monomorphizer<'a> {
|
|
|
608
608
|
/// regardless of processing order.
|
|
609
609
|
enum_variant_mangling: BTreeMap<String, BTreeMap<String, String>>,
|
|
610
610
|
/// The enum's own bare name -> the generic `Enum` — used to detect a bare
|
|
611
|
-
/// generic-enum-typed function param (e.g. `o:
|
|
611
|
+
/// generic-enum-typed function param (e.g. `o: Link`), distinct from
|
|
612
612
|
/// `enums_generic_by_variant` (keyed by VARIANT name, used for construction
|
|
613
|
-
/// sites like `
|
|
613
|
+
/// sites like `Next(5)`).
|
|
614
614
|
enums_generic_by_name: BTreeMap<String, &'a ast::Enum>,
|
|
615
615
|
/// Free functions that are NOT generic by `fnGenericParams`'s lowercase-letter
|
|
616
616
|
/// convention, but whose param type(s) bare-name a generic enum (e.g.
|
|
617
|
-
/// `unwrapOr(o:
|
|
617
|
+
/// `unwrapOr(o: Link, ...)`) — such a function still needs its own
|
|
618
618
|
/// per-call-site specialization, since its receiver generic enum is
|
|
619
619
|
/// dropped from the monomorphized output and the bare name would otherwise
|
|
620
620
|
/// resolve to nothing.
|
|
@@ -655,7 +655,7 @@ struct Monomorphizer<'a> {
|
|
|
655
655
|
/// (the generic enum its declared return type names, the concrete type args
|
|
656
656
|
/// it names them with) — for every function/method whose OWN declared return
|
|
657
657
|
/// type is a fully-general instantiation of a known generic enum (e.g.
|
|
658
|
-
/// `->
|
|
658
|
+
/// `-> Try[Int, Str]`). Populated once up front (in `monomorphizeSource`,
|
|
659
659
|
/// alongside `enums_generic_by_name`) from the ORIGINAL, unmodified signatures
|
|
660
660
|
/// — unlike everywhere else in this file, this doesn't need the specialization
|
|
661
661
|
/// to have actually run yet, since the declared signature already says
|
|
@@ -808,10 +808,10 @@ impl<'a> Monomorphizer<'a> {
|
|
|
808
808
|
env.insert(p.name.clone(), TypeScheme::mono(ty));
|
|
809
809
|
}
|
|
810
810
|
// The function's own return type, resolved for THIS specialization — used
|
|
811
|
-
// below to resolve a bare payload-free variant (`
|
|
811
|
+
// below to resolve a bare payload-free variant (`End`) that's the
|
|
812
812
|
// BODY'S TAIL EXPRESSION (implicit return), the same way `Stmt::Return`
|
|
813
813
|
// already does. A tail `Stmt::Expr` never goes through `Stmt::Return`'s
|
|
814
|
-
// own handling, so without this a trailing bare `
|
|
814
|
+
// own handling, so without this a trailing bare `End` is left
|
|
815
815
|
// unmangled and later fails the real checker's return-type unification.
|
|
816
816
|
let expected_ret = self
|
|
817
817
|
.current_return_type
|
|
@@ -837,16 +837,16 @@ impl<'a> Monomorphizer<'a> {
|
|
|
837
837
|
self.rewriteBlock(block, &mut env)?;
|
|
838
838
|
// `inferConcrete` (not plain `infer`) — the tail's type feeds
|
|
839
839
|
// `maybeRewriteReturn` below, which needs the call's REAL
|
|
840
|
-
// specialized return type (e.g. `
|
|
840
|
+
// specialized return type (e.g. `Link$Str`) when the tail is
|
|
841
841
|
// a call to another function whose OWN declared return type
|
|
842
842
|
// instantiates a generic enum. Plain `infer` only consults
|
|
843
843
|
// `self.global_env`, built once from the ORIGINAL source
|
|
844
844
|
// before any mangling — so a tail like `return otherFn()`
|
|
845
|
-
// where `otherFn() ->
|
|
845
|
+
// where `otherFn() -> Link[Str]` would infer the bare,
|
|
846
|
-
// unmangled `
|
|
846
|
+
// unmangled `Link` here, and `maybeRewriteReturn` would then
|
|
847
847
|
// permanently freeze this function's OWN return annotation at
|
|
848
848
|
// that same bare, ungeneric name instead of the concrete
|
|
849
|
-
// `
|
|
849
|
+
// `Link$Str` the body actually produces.
|
|
850
850
|
match block.stmts.last() {
|
|
851
851
|
Some(ast::Stmt::Expr(e, ..)) => Some(self.inferConcrete(e, &env)),
|
|
852
852
|
Some(ast::Stmt::Return(Some(e))) => Some(self.inferConcrete(e, &env)),
|
|
@@ -880,7 +880,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
880
880
|
/// is `true` or `false`; the specialization path differs only in that its tail
|
|
881
881
|
/// is inferred against a resolved substitution, so a generic-parameter-letter
|
|
882
882
|
/// return resolves to the specialization's concrete bound type (which the
|
|
883
|
-
/// ordinary path cannot do). The unparseable-`
|
|
883
|
+
/// ordinary path cannot do). The unparseable-`End` fill-in is gated on
|
|
884
884
|
/// `resolve_return` so an ordinary void function (`returns: None` meaning "no
|
|
885
885
|
/// declared return", not "a generic return the grammar dropped") is never given
|
|
886
886
|
/// a fabricated return type. Never fabricates a return from an un-inferrable
|
|
@@ -893,7 +893,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
893
893
|
None => resolve_return,
|
|
894
894
|
Some(rt) => {
|
|
895
895
|
// A generic enum named WITH concrete type args already
|
|
896
|
-
// (`-> List[Str]`, `->
|
|
896
|
+
// (`-> List[Str]`, `-> Try[Int, Str]`) is already fully
|
|
897
897
|
// resolved — only a genuinely BARE reference (`-> List`,
|
|
898
898
|
// `-> Box`, no `[...]` at all) is the "unresolved" shape this
|
|
899
899
|
// is meant to catch. Without the `rt.generics.is_empty()`
|
|
@@ -923,10 +923,10 @@ impl<'a> Monomorphizer<'a> {
|
|
|
923
923
|
}
|
|
924
924
|
|
|
925
925
|
/// Rewrites `pat` (bare-name/constructor pattern) in place: an uppercase variant
|
|
926
|
-
/// name gets mangled to its specialized form (`
|
|
926
|
+
/// name gets mangled to its specialized form (`Next` -> `Next$Int`) if `mangling`
|
|
927
927
|
/// says this position's subject is a specialized generic enum; a plain binding
|
|
928
928
|
/// name is inserted into `case_env` at `ty`. Recurses into a constructor
|
|
929
|
-
/// pattern's own fields (`
|
|
929
|
+
/// pattern's own fields (`Next(Next(v))`), looking up *that* field's own
|
|
930
930
|
/// mangling table from `self.enum_variant_mangling` — a nested sub-pattern can
|
|
931
931
|
/// be a specialization independent of its enclosing pattern's.
|
|
932
932
|
fn manglePattern(
|
|
@@ -988,7 +988,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
988
988
|
match stmt {
|
|
989
989
|
ast::Stmt::Assign(a) => {
|
|
990
990
|
for (target, value) in a.targets.iter_mut().zip(a.values.iter_mut()) {
|
|
991
|
-
// For a field target (`self.head =
|
|
991
|
+
// For a field target (`self.head = End`), resolve the
|
|
992
992
|
// value against the field's OWN declared type BEFORE the
|
|
993
993
|
// generic rewrite/inference below — same reasoning as
|
|
994
994
|
// `ClassCall`'s field values: a bare payload-free variant
|
|
@@ -1026,7 +1026,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1026
1026
|
ast::Stmt::Return(Some(e)) => {
|
|
1027
1027
|
// Same idea as the field-target case above, but against the
|
|
1028
1028
|
// enclosing function's own declared return type (`return
|
|
1029
|
-
//
|
|
1029
|
+
// End` inside a method returning `Link[Int]`).
|
|
1030
1030
|
if let Some(rt) = self.current_return_type.clone() {
|
|
1031
1031
|
let resolved = self.resolveFieldType(&rt);
|
|
1032
1032
|
let expected = crate::plumTypeFromAst(&resolved);
|
|
@@ -1094,7 +1094,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1094
1094
|
.collect();
|
|
1095
1095
|
// If a subject's concrete type is a specialized generic enum, its
|
|
1096
1096
|
// variant-name mangling table lets us rewrite that position's patterns
|
|
1097
|
-
// (`
|
|
1097
|
+
// (`Next`/`End` -> `Next$Int`/`End$Int`) to reference the correct
|
|
1098
1098
|
// specialization, so the checker/codegen's unmodified, bare-name-keyed
|
|
1099
1099
|
// `EnumVariants` lookup still resolves each pattern correctly.
|
|
1100
1100
|
let variant_manglings: Vec<Option<BTreeMap<String, String>>> = subject_types
|
|
@@ -1130,8 +1130,8 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1130
1130
|
// A case body's own LAST statement, when it's a bare
|
|
1131
1131
|
// `Stmt::Expr`, is itself an implicit-return position
|
|
1132
1132
|
// whenever this whole `match` is the function's tail
|
|
1133
|
-
// expression (`first`/`last`'s `
|
|
1133
|
+
// expression (`first`/`last`'s `End =>` arm ending in a
|
|
1134
|
-
// bare `
|
|
1134
|
+
// bare `End`, for instance) — same reasoning as
|
|
1135
1135
|
// `rewriteFnBody`'s own tail-expression handling, just one
|
|
1136
1136
|
// level down through the match. Resolving against the
|
|
1137
1137
|
// function's OWN declared return type is a no-op unless
|
|
@@ -1180,7 +1180,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1180
1180
|
if let Some(idx) = variant.fields.iter().position(|t| t.name == *gp) {
|
|
1181
1181
|
let field_name = &variant.field_names[idx];
|
|
1182
1182
|
if let Some(fa) = call.fields.iter().find(|fa| &fa.name == field_name) {
|
|
1183
|
-
// A bare payload-free variant reference (`
|
|
1183
|
+
// A bare payload-free variant reference (`End`) carries
|
|
1184
1184
|
// no type of its own to bind a generic param FROM — skip
|
|
1185
1185
|
// it here; it gets resolved AGAINST the binding (once
|
|
1186
1186
|
// known) below instead, same as every other field.
|
|
@@ -1192,9 +1192,9 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1192
1192
|
}
|
|
1193
1193
|
}
|
|
1194
1194
|
// No field is EVER directly typed as a bare generic param for a type
|
|
1195
|
-
// like `List[T]` (its fields are `
|
|
1195
|
+
// like `List[T]` (its fields are `Link[Node[T]]`/`Int`, never a bare
|
|
1196
1196
|
// `T`) — and even where one exists, constructing with a payload-free
|
|
1197
|
-
// value (`List(head:
|
|
1197
|
+
// value (`List(head: End, ...)`) gives no VALUE to infer a type from
|
|
1198
1198
|
// regardless. Fall back to an explicit `List[Int](...)` annotation at
|
|
1199
1199
|
// the call site when field-value inference alone isn't enough.
|
|
1200
1200
|
if bindings.len() != params.len() && call.generics.len() == params.len() {
|
|
@@ -1219,9 +1219,9 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1219
1219
|
// declared return type, exactly like `resolveEnumInstantiation` already
|
|
1220
1220
|
// does for a generic-enum construction site that can't see every
|
|
1221
1221
|
// parameter from its own arguments. This is what makes `fun
|
|
1222
|
-
// makeList(values: ...T) -> List[T] = List(head:
|
|
1222
|
+
// makeList(values: ...T) -> List[T] = List(head: End, ...).add(values)`
|
|
1223
1223
|
// work: the bare `List(...)` construction gives no type witness at all
|
|
1224
|
-
// (every field value is a payload-free `
|
|
1224
|
+
// (every field value is a payload-free `End`/`0`), but by the time
|
|
1225
1225
|
// THIS function is specialized for a concrete `T`, `current_return_type`
|
|
1226
1226
|
// has already been substituted to e.g. `List[Int]` by `specializeFn`.
|
|
1227
1227
|
if bindings.len() != params.len() {
|
|
@@ -1249,10 +1249,10 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1249
1249
|
let mangled = self.ensureEnumSpecialized(e, ¶ms, bindings);
|
|
1250
1250
|
|
|
1251
1251
|
// Now that every generic param is bound, resolve any bare
|
|
1252
|
-
// payload-free-variant field values (`
|
|
1252
|
+
// payload-free-variant field values (`End`) against THIS
|
|
1253
1253
|
// specialization's own field types — codegen only ever sees the
|
|
1254
1254
|
// mangled specialization (the generic template is dropped), so a
|
|
1255
|
-
// still-bare `
|
|
1255
|
+
// still-bare `End` would be an unresolvable reference by the time it
|
|
1256
1256
|
// gets there.
|
|
1257
1257
|
if let Some(info) = self.enum_variants.get(&mangled).cloned() {
|
|
1258
1258
|
for fa in &mut call.fields {
|
|
@@ -1285,7 +1285,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1285
1285
|
/// `Attribute` arm — that generic walk seeds a closure's params as
|
|
1286
1286
|
/// unconstrained `TVar`s (it has no way to know a method's declared
|
|
1287
1287
|
/// closure-param types), and once a construction inside a closure body
|
|
1288
|
-
/// (e.g. `andThen`'s `|n|
|
|
1288
|
+
/// (e.g. `andThen`'s `|n| Next(n / 2)`) gets mangled against a WRONG
|
|
1289
1289
|
/// (`TVar`-derived) binding, nothing downstream can fix it —
|
|
1290
1290
|
/// `resolveEnumInstantiation` only fires for a bare, still-unmangled
|
|
1291
1291
|
/// variant name, so a premature wrong rewrite sticks permanently. Called
|
|
@@ -1437,7 +1437,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1437
1437
|
}
|
|
1438
1438
|
ExtraGenericSource::ClosureReturn(i) => {
|
|
1439
1439
|
// The closure's body was already rewritten (any generic
|
|
1440
|
-
// construction inside it, e.g. `andThen`'s `
|
|
1440
|
+
// construction inside it, e.g. `andThen`'s `Next(n / 2)`,
|
|
1441
1441
|
// already mangled to its real specialization) by
|
|
1442
1442
|
// `rewriteMethodClosureArgs`, called before this — just
|
|
1443
1443
|
// re-seed the same closure-param env to infer its
|
|
@@ -1475,10 +1475,10 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1475
1475
|
};
|
|
1476
1476
|
// A bare closure return (`cb: fn(T) -> U`, e.g. `List.map`)
|
|
1477
1477
|
// binds `letter` directly to the closure's whole inferred
|
|
1478
|
-
// type. A NESTED one (`cb: fn(T) ->
|
|
1478
|
+
// type. A NESTED one (`cb: fn(T) -> Link[U]`, e.g.
|
|
1479
|
-
// `
|
|
1479
|
+
// `Link.andThen`) instead needs `bound` (the closure's
|
|
1480
|
-
// ACTUAL concrete return, e.g. `
|
|
1480
|
+
// ACTUAL concrete return, e.g. `Link$Int`) matched back
|
|
1481
|
-
// against the DECLARED shape `
|
|
1481
|
+
// against the DECLARED shape `Link[U]` to recover what
|
|
1482
1482
|
// `U` itself was — `bindGenericArg` (shared with
|
|
1483
1483
|
// `resolveFnInstantiation`'s free-function argument
|
|
1484
1484
|
// binding) does exactly that structural extraction.
|
|
@@ -1511,7 +1511,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1511
1511
|
// generic-FREE-function machinery this reuses) are FLAT sets keyed by
|
|
1512
1512
|
// this bare string alone, globally across the whole program — without
|
|
1513
1513
|
// the receiver folded in, two unrelated receivers whose OWN method
|
|
1514
|
-
// happens to mangle to the same suffix (e.g. `
|
|
1514
|
+
// happens to mangle to the same suffix (e.g. `Link[Int].map` and
|
|
1515
1515
|
// `List[Int].map` both producing `"map$Str"`) would collide: the
|
|
1516
1516
|
// second one popped would find the first's bare name already in
|
|
1517
1517
|
// `specialized` and silently skip producing its own body.
|
|
@@ -1575,7 +1575,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1575
1575
|
}
|
|
1576
1576
|
|
|
1577
1577
|
/// Without this, a method call chained directly onto a FRESH
|
|
1578
|
-
/// enum construction in the same statement (`
|
|
1578
|
+
/// enum construction in the same statement (`Next(4).filter(...)`, or
|
|
1579
1579
|
/// any `SomeVariant(...).method(...)` one-liner for a generic enum) could
|
|
1580
1580
|
/// never resolve: the receiver's specialization is only ENQUEUED here,
|
|
1581
1581
|
/// not fully produced until the worklist drains (well after the current
|
|
@@ -1675,9 +1675,9 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1675
1675
|
}
|
|
1676
1676
|
|
|
1677
1677
|
/// If `expr` is a bare reference to a payload-free variant of a GENERIC
|
|
1678
|
-
/// enum (`
|
|
1678
|
+
/// enum (`End`), and `expected` names a SPECIFIC specialization of that
|
|
1679
|
-
/// same enum (`
|
|
1679
|
+
/// same enum (`Link$Node$Int`), rewrites `expr`'s name to that
|
|
1680
|
-
/// specialization's own mangled variant name (`
|
|
1680
|
+
/// specialization's own mangled variant name (`End$Node$Int`) and
|
|
1681
1681
|
/// ensures that specialization is registered — codegen only ever knows
|
|
1682
1682
|
/// about specializations (the generic template enum is dropped entirely),
|
|
1683
1683
|
/// so an un-rewritten bare reference would be unresolvable by the time it
|
|
@@ -1869,14 +1869,14 @@ impl<'a> Monomorphizer<'a> {
|
|
|
1869
1869
|
/// `List[T]`), given `actual` — the ACTUAL concrete `PlumType` an argument
|
|
1870
1870
|
/// declared as `declared` was inferred to have. The bare case (`declared`
|
|
1871
1871
|
/// IS the letter, e.g. `a: T`) binds it directly to `actual`. The
|
|
1872
|
-
/// container case (`declared` is `List[T]`/`
|
|
1872
|
+
/// container case (`declared` is `List[T]`/`Link[T]`/`Try[T, E]`,
|
|
1873
1873
|
/// `actual` is that container's own mangled specialization like
|
|
1874
1874
|
/// `List$Int`) looks up `actual`'s recorded `class_specialization_info`
|
|
1875
1875
|
/// (the SAME table `resolveMethodOwnGenerics` already trusts for a
|
|
1876
1876
|
/// method's own receiver) to recover the concrete type EACH of the
|
|
1877
1877
|
/// container's own generic params was bound to, then recurses — so
|
|
1878
1878
|
/// `List[T]` against `List$Int` binds `T -> Int`, and (one level deeper)
|
|
1879
|
-
/// `List[
|
|
1879
|
+
/// `List[Link[T]]` against `List$Link$Int` binds `T -> Int` too. A
|
|
1880
1880
|
/// mismatch (wrong container, or `actual` isn't even a named/specialized
|
|
1881
1881
|
/// type) silently binds nothing for that param, same as before this
|
|
1882
1882
|
/// existed — `resolveFnInstantiation`'s caller already rejects an
|
|
@@ -2006,8 +2006,8 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2006
2006
|
}
|
|
2007
2007
|
|
|
2008
2008
|
/// The bare names of any generic class or enum referenced directly (not via a
|
|
2009
|
-
/// lowercase-letter generic parameter) in `f`'s param types — e.g. `"
|
|
2009
|
+
/// lowercase-letter generic parameter) in `f`'s param types — e.g. `"Link"` for
|
|
2010
|
-
/// `unwrapOr(o:
|
|
2010
|
+
/// `unwrapOr(o: Link, default: Int) -> Int`. See `fns_bare_generic`'s doc
|
|
2011
2011
|
/// comment for why such a function needs its own specialization.
|
|
2012
2012
|
fn fnBareGenericRefs(&self, f: &ast::Fn) -> Vec<String> {
|
|
2013
2013
|
let mut names: Vec<String> = Vec::new();
|
|
@@ -2098,17 +2098,17 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2098
2098
|
Ok(())
|
|
2099
2099
|
}
|
|
2100
2100
|
|
|
2101
|
-
/// Resolves a construction of a generic enum's variant (e.g. `
|
|
2101
|
+
/// Resolves a construction of a generic enum's variant (e.g. `Next(5)` for
|
|
2102
|
-
/// `enum
|
|
2102
|
+
/// `enum Link = | Next(a) | End`), rewriting `call.name` from the bare
|
|
2103
|
-
/// variant name (`
|
|
2103
|
+
/// variant name (`Next`) to its mangled form (`Next$Int`) once the enum's own
|
|
2104
2104
|
/// concrete instantiation is known. Mangling is eager and deterministic — it
|
|
2105
2105
|
/// doesn't wait for the worklist to actually produce the specialized `ast::Enum`
|
|
2106
2106
|
/// (see `enum_variant_mangling`'s doc comment).
|
|
2107
2107
|
///
|
|
2108
|
-
/// A variant that carries no generic fields (e.g. `
|
|
2108
|
+
/// A variant that carries no generic fields (e.g. `End`) can't pin down the
|
|
2109
2109
|
/// enum's type parameters on its own, so such a construction site is left alone
|
|
2110
|
-
/// here — some other construction site (e.g. `
|
|
2110
|
+
/// here — some other construction site (e.g. `Next(5)`) is what drives the
|
|
2111
|
-
/// specialization. (A bare `
|
|
2111
|
+
/// specialization. (A bare `End` used as a *value*, not a call, is
|
|
2112
2112
|
/// `ast::Expr::TypeName` and doesn't go through this function at all — see the
|
|
2113
2113
|
/// plan's Global Constraints for that narrower, documented residual limitation.)
|
|
2114
2114
|
fn resolveEnumInstantiation(
|
|
@@ -2167,11 +2167,11 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2167
2167
|
}
|
|
2168
2168
|
}
|
|
2169
2169
|
// This single construction site couldn't pin down every generic parameter
|
|
2170
|
-
// by itself — e.g. `Ok(5)` only ever supplies `
|
|
2170
|
+
// by itself — e.g. `Ok(5)` only ever supplies `Try`'s `T`, never its `E`
|
|
2171
2171
|
// (no `Ok` call site can, since `Err`'s payload is a disjoint field). Fall
|
|
2172
2172
|
// back to the enclosing function's declared return type, if it names this
|
|
2173
2173
|
// same enum with an explicit, fully-general `[...]` instantiation (e.g.
|
|
2174
|
-
// `->
|
|
2174
|
+
// `-> Try[Int, Str]`) — that's the one other place a type this
|
|
2175
2175
|
// construction site can't see on its own is written down on purpose.
|
|
2176
2176
|
if bindings.len() != params.len() {
|
|
2177
2177
|
if let Some(rt) = self.current_return_type.clone() {
|
|
@@ -2206,7 +2206,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2206
2206
|
// `resolveClassInstantiation` — `gt` needs
|
|
2207
2207
|
// `resolveFieldType`'s recursive resolution
|
|
2208
2208
|
// before `plumTypeFromAst`, or a generic-with-args
|
|
2209
|
-
// type argument (e.g. `
|
|
2209
|
+
// type argument (e.g. `Try[Link[Int], Str]`)
|
|
2210
2210
|
// would have its own nested generics silently
|
|
2211
2211
|
// dropped.
|
|
2212
2212
|
let resolved = self.resolveFieldType(gt);
|
|
@@ -2322,7 +2322,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2322
2322
|
if !self.enum_variant_mangling.contains_key(&mangled) {
|
|
2323
2323
|
// Insert a placeholder table BEFORE recursively resolving any variant's
|
|
2324
2324
|
// own field types below — a self-referential generic enum (e.g. a
|
|
2325
|
-
// linked-list `Node[T]`'s own `next:
|
|
2325
|
+
// linked-list `Node[T]`'s own `next: Link[Node[T]]` field) would
|
|
2326
2326
|
// otherwise recurse right back into specializing itself here before
|
|
2327
2327
|
// this specialization is even marked as in-progress, infinitely. The
|
|
2328
2328
|
// recursive re-entry only ever needs this specialization's MANGLED
|
|
@@ -2360,14 +2360,14 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2360
2360
|
// Eagerly registers this specialization's ORDINARY methods'
|
|
2361
2361
|
// signatures right now (see `registerEnumMethodSignatures`'s doc
|
|
2362
2362
|
// comment) — a method called on this value in the SAME statement
|
|
2363
|
-
// that constructs it (`
|
|
2363
|
+
// that constructs it (`Next(4).filter(...)`) needs
|
|
2364
2364
|
// `self.methods` to already know about it, well before the
|
|
2365
2365
|
// worklist entry below is ever popped. MUST run only inside this
|
|
2366
2366
|
// `if` (guarded by the `enum_variant_mangling` insert just
|
|
2367
2367
|
// above, which marks this exact specialization as already being
|
|
2368
2368
|
// set up) — a method whose own return type re-mentions this SAME
|
|
2369
|
-
// specialization (`
|
|
2369
|
+
// specialization (`Link[T]`'s `filter` returning `Link[T]`)
|
|
2370
|
-
// recurses right back into `ensureEnumSpecialized("
|
|
2370
|
+
// recurses right back into `ensureEnumSpecialized("Link$Int",
|
|
2371
2371
|
// ...)`; running this unconditionally on every call (rather than
|
|
2372
2372
|
// only the first) recurses infinitely and stack-overflows.
|
|
2373
2373
|
self.registerEnumMethodSignatures(e, &mangled, &bindings);
|
|
@@ -2386,12 +2386,12 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2386
2386
|
/// Fully resolves a FIELD's declared type into something that will actually
|
|
2387
2387
|
/// exist after monomorphization. `specializeEnum`'s own field substitution
|
|
2388
2388
|
/// only replaces a bare generic-param NAME (`T` -> `Int`) — a field declared
|
|
2389
|
-
/// `
|
|
2389
|
+
/// `Link[Node[T]]` becomes `Link[Node[Int]]` this way, which is now
|
|
2390
|
-
/// fully CONCRETE but still names the generic TEMPLATES `
|
|
2390
|
+
/// fully CONCRETE but still names the generic TEMPLATES `Link`/`Node`
|
|
2391
2391
|
/// directly, both of which monomorphization deletes from the output (only
|
|
2392
2392
|
/// their mangled specializations, e.g. `Node$Int`, survive). Recursively
|
|
2393
2393
|
/// resolves any nested generic arguments first (so `Node[Int]` inside
|
|
2394
|
-
/// `
|
|
2394
|
+
/// `Link[Node[Int]]` becomes `Node$Int` before `Link[...]` itself is
|
|
2395
2395
|
/// resolved), then — if the type names a known generic enum applied to
|
|
2396
2396
|
/// arguments — mangles it to that specialization's real name and enqueues
|
|
2397
2397
|
/// the specialization if it hasn't been already, via the same
|
|
@@ -2471,8 +2471,8 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2471
2471
|
/// originally declared, generics and all) to drive
|
|
2472
2472
|
/// `resolveEnumInstantiation`'s fallback for construction sites that
|
|
2473
2473
|
/// can't infer every generic param from their own arguments alone (e.g.
|
|
2474
|
-
/// `Err("...")` needs `
|
|
2474
|
+
/// `Err("...")` needs `Try`'s OTHER param, `T`, from the function's
|
|
2475
|
-
/// own `->
|
|
2475
|
+
/// own `-> Try[Int, Str]` declaration) — flattening the return type
|
|
2476
2476
|
/// up front would replace `rt.name` with an already-mangled name that
|
|
2477
2477
|
/// fallback's own bare-template-name comparison can never match again.
|
|
2478
2478
|
fn resolveFnParamTypes(&mut self, f: &mut ast::Fn) {
|
|
@@ -2482,11 +2482,11 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2482
2482
|
ast::ParamType::Variadic(t) => *t = self.resolveFieldType(t),
|
|
2483
2483
|
// A closure-typed param's own param/return types can equally
|
|
2484
2484
|
// name a concrete generic instantiation after substitution
|
|
2485
|
-
// (e.g. `andThen`'s `cb: fn(T) ->
|
|
2485
|
+
// (e.g. `andThen`'s `cb: fn(T) -> Link[U]` becomes `fn(Int)
|
|
2486
|
-
// ->
|
|
2486
|
+
// -> Link[Int]` once `T`/`U` are bound) — leaving them
|
|
2487
2487
|
// unresolved meant this param's type, once turned into a
|
|
2488
2488
|
// `PlumType` via `plumTypeFromAst` for the body's own type
|
|
2489
|
-
// env, silently flattened to a bare, unmangled `
|
|
2489
|
+
// env, silently flattened to a bare, unmangled `Link`
|
|
2490
2490
|
// (`plumTypeFromAst` drops every type's `.generics`
|
|
2491
2491
|
// regardless of nesting depth), so a call through the
|
|
2492
2492
|
// closure param (`cb(v)`) inferred the wrong, un-mangled
|
|
@@ -2515,12 +2515,12 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2515
2515
|
/// PRODUCED — it says nothing about code further downstream that CONSUMES an
|
|
2516
2516
|
/// already-specialized generic-enum value returned from calling some other
|
|
2517
2517
|
/// (already fully concrete, non-generic) function or method, e.g.
|
|
2518
|
-
/// `match parseIt() { Ok(v) => ... }` where `fun parseIt() ->
|
|
2518
|
+
/// `match parseIt() { Ok(v) => ... }` where `fun parseIt() -> Try[Int, Str]`.
|
|
2519
2519
|
/// `self.infer` can't help there either: it's built from `buildGlobalTables`,
|
|
2520
2520
|
/// which (like every other `PlumType` site) drops a declared type's generic
|
|
2521
|
-
/// args entirely (`plumTypeFromAst` maps `
|
|
2521
|
+
/// args entirely (`plumTypeFromAst` maps `Try[Int, Str]` to the bare
|
|
2522
|
-
/// `TNamed("
|
|
2522
|
+
/// `TNamed("Try")`) — so it has no way to know this call's result is the
|
|
2523
|
-
/// SPECIALIZED `
|
|
2523
|
+
/// SPECIALIZED `Try$Int$Str`, not the generic template.
|
|
2524
2524
|
///
|
|
2525
2525
|
/// This resolves that one specific, common shape directly from the callee's own
|
|
2526
2526
|
/// declaration (recorded in `fn_return_generic_enum` during classification) —
|
|
@@ -2580,7 +2580,7 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2580
2580
|
}
|
|
2581
2581
|
// Runs next (before the generic per-field rewrite below): a
|
|
2582
2582
|
// bare payload-free variant field value (`Node(..., next:
|
|
2583
|
-
//
|
|
2583
|
+
// End)`) carries no type of its own to infer a generic
|
|
2584
2584
|
// param from, and needs the enum's OWN (about-to-be-
|
|
2585
2585
|
// specialized) field type to resolve which specialization it
|
|
2586
2586
|
// actually means — `resolveClassInstantiation` handles that
|
|
@@ -2760,8 +2760,8 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2760
2760
|
ast::Expr::Compare(c) => {
|
|
2761
2761
|
self.rewriteExpr(&mut c.left, env)?;
|
|
2762
2762
|
self.rewriteExpr(&mut c.right, env)?;
|
|
2763
|
-
// `current !=
|
|
2763
|
+
// `current != End`: `current`'s side may already be a SPECIFIC
|
|
2764
|
-
// enum specialization (e.g. `
|
|
2764
|
+
// enum specialization (e.g. `Link$Node$Int`) while `End`
|
|
2765
2765
|
// itself is still bare (it carries no type of its own) —
|
|
2766
2766
|
// resolve each side against the OTHER's type; a no-op unless
|
|
2767
2767
|
// that side is actually a bare generic-enum variant.
|
|
@@ -2799,9 +2799,9 @@ impl<'a> Monomorphizer<'a> {
|
|
|
2799
2799
|
| ast::Expr::TypeName(_) => {}
|
|
2800
2800
|
ast::Expr::Closure(cl) => {
|
|
2801
2801
|
// A closure literal's body can itself contain a generic
|
|
2802
|
-
// construction site (`|v|
|
|
2802
|
+
// construction site (`|v| Next(v)`) that needs rewriting to
|
|
2803
2803
|
// its mangled variant/class name, same as anywhere else —
|
|
2804
|
-
// without this, codegen later traps with "variant '
|
|
2804
|
+
// without this, codegen later traps with "variant 'Next'
|
|
2805
2805
|
// missing from the GC type registry" the first time such a
|
|
2806
2806
|
// closure is actually compiled. The closure's OWN params
|
|
2807
2807
|
// aren't bound to any concrete type YET at this point in the
|
|
@@ -2876,7 +2876,7 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2876
2876
|
// `fn_return_generic_enum` loop below, which calls `resolveFieldType` and
|
|
2877
2877
|
// can therefore trigger `registerEnumMethodSignatures` for a generic enum
|
|
2878
2878
|
// as a SIDE EFFECT (e.g. resolving a nested `List[Str]` inside some other
|
|
2879
|
-
// function's `->
|
|
2879
|
+
// function's `-> Try[List[Str], Str]` return type).
|
|
2880
2880
|
// `registerEnumMethodSignatures` looks up `methods_generic_on_enum` and,
|
|
2881
2881
|
// finding it not yet populated, silently registers ZERO methods for that
|
|
2882
2882
|
// specialization (`Some(methods) = ... else { return }`) — and because the
|
|
@@ -2924,7 +2924,7 @@ pub fn monomorphizeSource(source: &ast::Source) -> Result<ast::Source, String> {
|
|
|
2924
2924
|
// before `plumTypeFromAst`, same as every other site
|
|
2925
2925
|
// that turns a declared `ast::Type` generic argument
|
|
2926
2926
|
// into a `PlumType` — otherwise a nested generic type
|
|
2927
|
-
// argument (e.g. this method's own `
|
|
2927
|
+
// argument (e.g. this method's own `Link[List[Json]]`
|
|
2928
2928
|
// return type) has its `List[Json]` silently flattened
|
|
2929
2929
|
// to bare `List`, and every match on this method's
|
|
2930
2930
|
// return value ends up destructuring against the
|
plum-checker/tests/checker_tests.rs
CHANGED
|
@@ -281,15 +281,15 @@ fn matchIntPatternAgainstStrSubjectIsError() {
|
|
|
281
281
|
|
|
282
282
|
#[test]
|
|
283
283
|
fn bareEnumTagUnifiesWithOwningEnumType() {
|
|
284
|
-
// Regression: a bare non-Bool tag like `
|
|
284
|
+
// Regression: a bare non-Bool tag like `Nothing` used to type as `TNamed("Nothing")`
|
|
285
|
-
// (itself, not its enum), so comparing it against
|
|
285
|
+
// (itself, not its enum), so comparing it against a `Maybe` value would wrongly
|
|
286
286
|
// fail with a type mismatch.
|
|
287
287
|
let src = "\
|
|
288
|
-
enum
|
|
288
|
+
enum Maybe =
|
|
289
|
-
|
|
|
289
|
+
| Just(Int)
|
|
290
|
-
|
|
|
290
|
+
| Nothing
|
|
291
291
|
|
|
292
|
-
fun isNone(o:
|
|
292
|
+
fun isNone(o: Maybe) -> Bool = o == Nothing
|
|
293
293
|
";
|
|
294
294
|
let source = parse(src);
|
|
295
295
|
let result = checkSource(&source);
|
|
@@ -299,12 +299,12 @@ fun isNone(o: Option) -> Bool = o == None
|
|
|
299
299
|
#[test]
|
|
300
300
|
fn variantConstructionChecksArgCountAndTypes() {
|
|
301
301
|
let src = "\
|
|
302
|
-
enum
|
|
302
|
+
enum Maybe =
|
|
303
|
-
|
|
|
303
|
+
| Just(Int)
|
|
304
|
-
|
|
|
304
|
+
| Nothing
|
|
305
305
|
|
|
306
|
-
fun
|
|
306
|
+
fun makeJust(v: Int) -> Maybe =
|
|
307
|
-
|
|
307
|
+
Just(v)
|
|
308
308
|
";
|
|
309
309
|
let source = parse(src);
|
|
310
310
|
let result = checkSource(&source);
|
|
@@ -314,12 +314,12 @@ fun makeSome(v: Int) -> Option =
|
|
|
314
314
|
#[test]
|
|
315
315
|
fn variantConstructionWrongArgTypeIsError() {
|
|
316
316
|
let src = "\
|
|
317
|
-
enum
|
|
317
|
+
enum Maybe =
|
|
318
|
-
|
|
|
318
|
+
| Just(Int)
|
|
319
|
-
|
|
|
319
|
+
| Nothing
|
|
320
320
|
|
|
321
|
-
fun bad() ->
|
|
321
|
+
fun bad() -> Maybe =
|
|
322
|
-
|
|
322
|
+
Just(\"x\")
|
|
323
323
|
";
|
|
324
324
|
let source = parse(src);
|
|
325
325
|
let result = checkSource(&source);
|
|
@@ -380,9 +380,9 @@ enum Step(n: Int) =
|
|
|
380
380
|
#[test]
|
|
381
381
|
fn enumDiscriminantValueOnParamLessEnumIsError() {
|
|
382
382
|
let src = "\
|
|
383
|
-
enum
|
|
383
|
+
enum Maybe =
|
|
384
|
-
|
|
|
384
|
+
| Just(5)
|
|
385
|
-
|
|
|
385
|
+
| Nothing
|
|
386
386
|
";
|
|
387
387
|
let source = parse(src);
|
|
388
388
|
let result = checkSource(&source);
|
|
@@ -412,9 +412,9 @@ fn fieldAccessOnOrdinaryEnumReceiverIsUnaffectedByEnumParams() {
|
|
|
412
412
|
// not the checker, is what would catch a genuinely bad field access here) —
|
|
413
413
|
// exactly as it did before discriminant enums existed.
|
|
414
414
|
let src = "\
|
|
415
|
-
enum
|
|
415
|
+
enum Maybe =
|
|
416
|
-
|
|
|
416
|
+
| Just(Int)
|
|
417
|
-
|
|
|
417
|
+
| Nothing
|
|
418
418
|
|
|
419
419
|
fun bad(self) -> Int =
|
|
420
420
|
self.n
|
|
@@ -570,15 +570,15 @@ fun useIt() -> Str =
|
|
|
570
570
|
|
|
571
571
|
#[test]
|
|
572
572
|
fn genericEnumSingleInstantiationTypeChecks() {
|
|
573
|
-
// A generic
|
|
573
|
+
// A generic Maybe-shaped enum, constructed at one concrete type (`Just(5)`),
|
|
574
574
|
// matched, must resolve end-to-end via checkSource.
|
|
575
575
|
let src = "\
|
|
576
|
-
enum
|
|
576
|
+
enum Maybe[T] =
|
|
577
|
-
|
|
|
577
|
+
| Just(T)
|
|
578
|
-
|
|
|
578
|
+
| Nothing
|
|
579
579
|
|
|
580
580
|
fun get() -> Int =
|
|
581
|
-
o =
|
|
581
|
+
o = Just(5)
|
|
582
582
|
match o
|
|
583
583
|
Some(v) =>
|
|
584
584
|
v
|
|
@@ -590,36 +590,36 @@ fun get() -> Int =
|
|
|
590
590
|
assert!(result.is_ok(), "expected Ok, got {:?}", result.err());
|
|
591
591
|
|
|
592
592
|
// Directly prove resolution happened: the monomorphized output must contain a
|
|
593
|
-
// concrete `
|
|
593
|
+
// concrete `Maybe$Int` enum whose `Just$Int` variant carries an `Int` field
|
|
594
|
-
// (not the generic `a`), and must NOT retain the generic `
|
|
594
|
+
// (not the generic `a`), and must NOT retain the generic `Maybe` template.
|
|
595
|
-
// The variant name is ALSO mangled (`
|
|
595
|
+
// The variant name is ALSO mangled (`Just` -> `Just$Int`), the same suffix as
|
|
596
596
|
// the enum's own name.
|
|
597
597
|
let mono = plum_checker::monomorphize::monomorphizeSource(&source)
|
|
598
598
|
.expect("monomorphize should succeed");
|
|
599
599
|
let opt = mono.items.iter().find_map(|it| match it {
|
|
600
|
-
Item::Enum(e) if e.name == "
|
|
600
|
+
Item::Enum(e) if e.name == "Maybe$Int" => Some(e),
|
|
601
601
|
_ => None,
|
|
602
602
|
});
|
|
603
|
-
let opt = opt.expect("expected a specialized `
|
|
603
|
+
let opt = opt.expect("expected a specialized `Maybe$Int` enum in the output");
|
|
604
604
|
let some = opt
|
|
605
605
|
.variants
|
|
606
606
|
.iter()
|
|
607
|
-
.find(|v| v.name == "
|
|
607
|
+
.find(|v| v.name == "Just$Int")
|
|
608
|
-
.expect("expected `
|
|
608
|
+
.expect("expected `Just$Int` (mangled) variant on `Maybe$Int`");
|
|
609
609
|
assert_eq!(
|
|
610
610
|
some.fields,
|
|
611
611
|
vec![Type {
|
|
612
612
|
name: "Int".to_string(),
|
|
613
613
|
generics: vec![]
|
|
614
614
|
}],
|
|
615
|
-
"
|
|
615
|
+
"Just's field should be concrete Int"
|
|
616
616
|
);
|
|
617
617
|
assert!(
|
|
618
618
|
!mono
|
|
619
619
|
.items
|
|
620
620
|
.iter()
|
|
621
|
-
.any(|it| matches!(it, Item::Enum(e) if e.name == "
|
|
621
|
+
.any(|it| matches!(it, Item::Enum(e) if e.name == "Maybe")),
|
|
622
|
-
"the generic `
|
|
622
|
+
"the generic `Maybe` template must be dropped from the output"
|
|
623
623
|
);
|
|
624
624
|
}
|
|
625
625
|
|
|
@@ -629,12 +629,12 @@ fn genericEnumMultipleInstantiationsCoexistAndTypeCheck() {
|
|
|
629
629
|
// program must now type-check correctly for BOTH instantiations — this is the
|
|
630
630
|
// behavior this task adds (previously this was a documented, rejected limitation).
|
|
631
631
|
let src = "\
|
|
632
|
-
enum
|
|
632
|
+
enum Maybe[T] =
|
|
633
|
-
|
|
|
633
|
+
| Just(T)
|
|
634
|
-
|
|
|
634
|
+
| Nothing
|
|
635
635
|
|
|
636
636
|
fun useInt() -> Int =
|
|
637
|
-
o =
|
|
637
|
+
o = Just(5)
|
|
638
638
|
match o
|
|
639
639
|
Some(v) =>
|
|
640
640
|
v
|
|
@@ -642,7 +642,7 @@ fun useInt() -> Int =
|
|
|
642
642
|
0
|
|
643
643
|
|
|
644
644
|
fun useStr() -> Str =
|
|
645
|
-
o =
|
|
645
|
+
o = Just(\"x\")
|
|
646
646
|
match o
|
|
647
647
|
Some(v) =>
|
|
648
648
|
v
|
|
@@ -664,12 +664,12 @@ fun useStr() -> Str =
|
|
|
664
664
|
})
|
|
665
665
|
};
|
|
666
666
|
assert!(
|
|
667
|
-
has_enum_with_variant("
|
|
667
|
+
has_enum_with_variant("Maybe$Int", "Just$Int"),
|
|
668
|
-
"expected
|
|
668
|
+
"expected Maybe$Int with Just$Int"
|
|
669
669
|
);
|
|
670
670
|
assert!(
|
|
671
|
-
has_enum_with_variant("
|
|
671
|
+
has_enum_with_variant("Maybe$Str", "Just$Str"),
|
|
672
|
-
"expected
|
|
672
|
+
"expected Maybe$Str with Just$Str"
|
|
673
673
|
);
|
|
674
674
|
}
|
|
675
675
|
|
|
@@ -724,11 +724,11 @@ fn ordinaryFunctionWithBareGenericEnumParamTypeChecks() {
|
|
|
724
724
|
// The shape that broke the pre-existing codegen test: an otherwise-ordinary
|
|
725
725
|
// function taking a bare generic-enum-typed parameter.
|
|
726
726
|
let src = "\
|
|
727
|
-
enum
|
|
727
|
+
enum Maybe[T] =
|
|
728
|
-
|
|
|
728
|
+
| Just(T)
|
|
729
|
-
|
|
|
729
|
+
| Nothing
|
|
730
730
|
|
|
731
|
-
fun unwrapOr(o:
|
|
731
|
+
fun unwrapOr(o: Maybe, default: Int) -> Int =
|
|
732
732
|
match o
|
|
733
733
|
Some(v) =>
|
|
734
734
|
v
|
|
@@ -736,7 +736,7 @@ fun unwrapOr(o: Option, default: Int) -> Int =
|
|
|
736
736
|
default
|
|
737
737
|
|
|
738
738
|
fun use() -> Int =
|
|
739
|
-
unwrapOr(
|
|
739
|
+
unwrapOr(Just(5), 0)
|
|
740
740
|
";
|
|
741
741
|
let source = parse(src);
|
|
742
742
|
let result = checkSource(&source);
|
|
@@ -1134,18 +1134,18 @@ fn classImplementingTraitWithGenericReturnTypeMismatchIsPermitted() {
|
|
|
1134
1134
|
// same generic type with a concrete arg is still a match (base type name
|
|
1135
1135
|
// only; see `checkTraitConformance`'s doc comment).
|
|
1136
1136
|
let src = "\
|
|
1137
|
-
enum
|
|
1137
|
+
enum Maybe[T] =
|
|
1138
|
-
|
|
|
1138
|
+
| Just(T)
|
|
1139
|
-
|
|
|
1139
|
+
| Nothing
|
|
1140
1140
|
|
|
1141
1141
|
trait Err =
|
|
1142
|
-
cause() ->
|
|
1142
|
+
cause() -> Maybe
|
|
1143
1143
|
|
|
1144
1144
|
enum MyErr(Err) =
|
|
1145
1145
|
| MyErr(pos: Int)
|
|
1146
1146
|
|
|
1147
|
-
fun cause(self) ->
|
|
1147
|
+
fun cause(self) -> Maybe[MyErr] =
|
|
1148
|
-
|
|
1148
|
+
Nothing
|
|
1149
1149
|
";
|
|
1150
1150
|
let source = parse(src);
|
|
1151
1151
|
assert!(
|
|
@@ -1403,9 +1403,9 @@ fn nested_union_in_payload() {
|
|
|
1403
1403
|
#[test]
|
|
1404
1404
|
fn tryOperatorVariantsOnlyOkErr() {
|
|
1405
1405
|
let src = "\
|
|
1406
|
-
enum
|
|
1406
|
+
enum Maybe =
|
|
1407
|
-
|
|
|
1407
|
+
| Just(Int)
|
|
1408
|
-
|
|
|
1408
|
+
| Nothing
|
|
1409
1409
|
";
|
|
1410
1410
|
let source = parseClean(src);
|
|
1411
1411
|
let (_, methods, variants, params) = plum_checker::buildGlobalTables(&source);
|
plum-checker/tests/monomorphize_tests.rs
CHANGED
|
@@ -53,13 +53,13 @@ fn enumGenericParamsDetectsImplicitUppercaseLetterVariantFields() {
|
|
|
53
53
|
let e = ast::Enum {
|
|
54
54
|
line: 0,
|
|
55
55
|
column: 0,
|
|
56
|
-
name: "
|
|
56
|
+
name: "Maybe".to_string(),
|
|
57
57
|
generics: vec![],
|
|
58
58
|
implements: vec![],
|
|
59
59
|
params: vec![],
|
|
60
60
|
variants: vec![
|
|
61
61
|
ast::EnumVariant {
|
|
62
|
-
name: "
|
|
62
|
+
name: "Just".to_string(),
|
|
63
63
|
fields: vec![ast::Type {
|
|
64
64
|
name: "T".to_string(),
|
|
65
65
|
generics: vec![],
|
|
@@ -68,7 +68,7 @@ fn enumGenericParamsDetectsImplicitUppercaseLetterVariantFields() {
|
|
|
68
68
|
values: vec![],
|
|
69
69
|
},
|
|
70
70
|
ast::EnumVariant {
|
|
71
|
-
name: "
|
|
71
|
+
name: "Nothing".to_string(),
|
|
72
72
|
fields: vec![],
|
|
73
73
|
field_names: vec![],
|
|
74
74
|
values: vec![],
|
|
@@ -154,13 +154,13 @@ fn specializeEnumSubstitutesGenericVariantFieldNames() {
|
|
|
154
154
|
let e = ast::Enum {
|
|
155
155
|
line: 0,
|
|
156
156
|
column: 0,
|
|
157
|
-
name: "
|
|
157
|
+
name: "Maybe".to_string(),
|
|
158
158
|
generics: vec![],
|
|
159
159
|
implements: vec![],
|
|
160
160
|
params: vec![],
|
|
161
161
|
variants: vec![
|
|
162
162
|
ast::EnumVariant {
|
|
163
|
-
name: "
|
|
163
|
+
name: "Just".to_string(),
|
|
164
164
|
fields: vec![ast::Type {
|
|
165
165
|
name: "T".to_string(),
|
|
166
166
|
generics: vec![],
|
|
@@ -169,7 +169,7 @@ fn specializeEnumSubstitutesGenericVariantFieldNames() {
|
|
|
169
169
|
values: vec![],
|
|
170
170
|
},
|
|
171
171
|
ast::EnumVariant {
|
|
172
|
-
name: "
|
|
172
|
+
name: "Nothing".to_string(),
|
|
173
173
|
fields: vec![],
|
|
174
174
|
field_names: vec![],
|
|
175
175
|
values: vec![],
|
|
@@ -178,8 +178,8 @@ fn specializeEnumSubstitutesGenericVariantFieldNames() {
|
|
|
178
178
|
};
|
|
179
179
|
let mut bindings = std::collections::BTreeMap::new();
|
|
180
180
|
bindings.insert("T".to_string(), PlumType::TInt);
|
|
181
|
-
let specialized = specializeEnum(&e, &Substitution(bindings), "
|
|
181
|
+
let specialized = specializeEnum(&e, &Substitution(bindings), "Maybe$Int");
|
|
182
|
-
assert_eq!(specialized.name, "
|
|
182
|
+
assert_eq!(specialized.name, "Maybe$Int");
|
|
183
183
|
assert_eq!(
|
|
184
184
|
specialized.variants[0].fields,
|
|
185
185
|
vec![ast::Type {
|
plum-core/src/ast.rs
CHANGED
|
@@ -102,7 +102,7 @@ pub struct Enum {
|
|
|
102
102
|
/// rather than parsed).
|
|
103
103
|
pub line: usize,
|
|
104
104
|
pub column: usize,
|
|
105
|
-
/// The enum's OWN declared generic type parameters (`enum
|
|
105
|
+
/// The enum's OWN declared generic type parameters (`enum Link[T] =
|
|
106
106
|
/// ...`). Empty for a non-generic enum.
|
|
107
107
|
pub generics: Vec<GenericParam>,
|
|
108
108
|
/// Trait names this enum claims to implement (`enum Cat(ToStr) = ...`).
|
|
@@ -126,7 +126,7 @@ pub struct EnumVariant {
|
|
|
126
126
|
pub fields: Vec<Type>,
|
|
127
127
|
/// Field names for a NAMED-payload variant (`Circle(radius: Int)`), aligned
|
|
128
128
|
/// index-for-index with `fields` — empty for the older, still-supported
|
|
129
|
-
/// positional/generic-payload form (`
|
|
129
|
+
/// positional/generic-payload form (`Next(T)`), NOT one empty string per
|
|
130
130
|
/// field. Lets a construction site use named args (`Circle(radius: 5)`,
|
|
131
131
|
/// parsed as an ordinary `ClassCall` — see `plum-checker`/`plum-wasm-codegen`'s
|
|
132
132
|
/// `ClassCall` handling) instead of only positional ones (`Circle(5)`, still
|
|
@@ -361,11 +361,11 @@ pub enum Expr {
|
|
|
361
361
|
ClassCall(ClassCall),
|
|
362
362
|
/// `expr.field` or `expr.method(…)`
|
|
363
363
|
Attribute(Box<AttributeExpr>),
|
|
364
|
-
/// `expr?` — unwraps a `
|
|
364
|
+
/// `expr?` — unwraps a `Try`'s `Ok`, or returns the
|
|
365
|
-
/// `Err`
|
|
365
|
+
/// `Err` value from the enclosing function as-is otherwise.
|
|
366
366
|
Try(Box<Expr>),
|
|
367
|
-
/// `left ?
|
|
367
|
+
/// `left ?? right` — if `left` is `Ok(v)`, the value is `v`; if
|
|
368
|
-
/// it's `Err(_)`
|
|
368
|
+
/// it's `Err(_)`, the value is `right`. Unlike `Try`, a plain
|
|
369
369
|
/// expression with no early return.
|
|
370
370
|
Elvis(Box<ElvisExpr>),
|
|
371
371
|
/// `{expr}` — grouped/parenthesized expression
|
plum-core/src/parser.rs
CHANGED
|
@@ -1089,7 +1089,7 @@ impl<'a> AstParser<'a> {
|
|
|
1089
1089
|
}
|
|
1090
1090
|
|
|
1091
1091
|
fn parseElvis(&self, node: Node) -> Expr {
|
|
1092
|
-
// elvis_expression: expression "?
|
|
1092
|
+
// elvis_expression: expression "??" expression
|
|
1093
1093
|
let named: Vec<Node> = self.namedChildren(node);
|
|
1094
1094
|
let left = named
|
|
1095
1095
|
.first()
|
|
@@ -1161,7 +1161,7 @@ impl<'a> AstParser<'a> {
|
|
|
1161
1161
|
/// safe_attribute: primary_expression "?." fn_identifier fn_argument_list?
|
|
1162
1162
|
///
|
|
1163
1163
|
/// Desugars entirely here into `object.map(|v| v.member(...))` — reusing
|
|
1164
|
-
///
|
|
1164
|
+
/// Try's existing generic `.map` method for the actual
|
|
1165
1165
|
/// unwrap/transform/rewrap, so nothing past the parser (checker,
|
|
1166
1166
|
/// monomorphizer, codegen, formatters) needs to know `?.` exists at all.
|
|
1167
1167
|
/// The synthesized param name can't collide with a real variable in scope:
|
plum-examples/error_propagation.plum
CHANGED
|
@@ -7,7 +7,7 @@ import std/List
|
|
|
7
7
|
# `Try[T, E]` is either `Ok(T)` or `Err(E)`. `E` is normally a tag union such
|
|
8
8
|
# as `[NegativeNum, BadNumStr]`: a set of bare tags (optionally carrying a
|
|
9
9
|
# payload, e.g. `InvalidJson(Str)`) that needs no declaration. There is no
|
|
10
|
-
#
|
|
10
|
+
# A missing value is just an error tag like `[OutOfBounds]`.
|
|
11
11
|
#
|
|
12
12
|
# `expr?` unwraps an `Ok`, or exits the enclosing function early with the
|
|
13
13
|
# `Err` value. The error unions of different calls merge automatically into
|
plum-std/Array.plum
CHANGED
|
@@ -29,7 +29,7 @@ import std/Number
|
|
|
29
29
|
# fixed-length allocation once made), so it allocates a new, one-longer array
|
|
30
30
|
# and copies, same amortized-growth idea as `Buffer`'s own `write`.
|
|
31
31
|
#
|
|
32
|
-
# New elements start out
|
|
32
|
+
# New elements start out null-shaped — really a null `anyref` under the
|
|
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).
|
plum-std/Try.plum
CHANGED
|
@@ -10,7 +10,7 @@ import std/Map
|
|
|
10
10
|
# carrying an `E`). `E` is normally a structural tag union such as
|
|
11
11
|
# `[NotFound, BadNum(Str)]`; `?` merges the error unions of different calls
|
|
12
12
|
# into the enclosing function's declared union. A missing value is just an
|
|
13
|
-
# error tag (`[OutOfBounds]`, `[KeyNotFound]`, ...),
|
|
13
|
+
# error tag (`[OutOfBounds]`, `[KeyNotFound]`, ...), with no separate optional type.
|
|
14
14
|
enum Try[T, E] =
|
|
15
15
|
| Ok(T)
|
|
16
16
|
| Err(E)
|
plum-tooling/tree-sitter-plum/grammar.js
CHANGED
|
@@ -202,7 +202,7 @@ module.exports = grammar({
|
|
|
202
202
|
field(
|
|
203
203
|
"parameters",
|
|
204
204
|
optional(choice(
|
|
205
|
-
// Positional payload, e.g. `
|
|
205
|
+
// Positional payload, e.g. `Next(Int)`/`Next(T)` — brackets are
|
|
206
206
|
// reserved for generics (a type/trait/enum's OWN declared
|
|
207
207
|
// params, and instantiating one, e.g. `List[Int]`); a variant's
|
|
208
208
|
// payload always uses parens, same as a class field's type.
|
|
@@ -533,9 +533,8 @@ module.exports = grammar({
|
|
|
533
533
|
),
|
|
534
534
|
),
|
|
535
535
|
|
|
536
|
-
// Rust-style error-propagation postfix: `expr?` — unwraps a `
|
|
536
|
+
// Rust-style error-propagation postfix: `expr?` — unwraps a `Try`'s `Ok`, or exits the enclosing function early with the
|
|
537
|
-
// or an `Option`'s `Some`, or exits the enclosing function early with the
|
|
538
|
-
// `Err`
|
|
537
|
+
// `Err` value as-is otherwise. Same postfix shape/precedence as
|
|
539
538
|
// `attribute` (`.`) just above.
|
|
540
539
|
try_expression: ($) =>
|
|
541
540
|
choice(
|
|
@@ -553,12 +552,12 @@ module.exports = grammar({
|
|
|
553
552
|
),
|
|
554
553
|
|
|
555
554
|
// Groovy/Kotlin-style safe navigation: `obj?.member` / `obj?.method(args)`
|
|
556
|
-
// on a `
|
|
555
|
+
// on a `Try` value — same shape as `attribute` just above, but
|
|
557
556
|
// with a `?.` token in place of `.`. `?.` is its own literal token, so the
|
|
558
557
|
// lexer's usual longest-match rule already picks it over a bare `?` (the
|
|
559
558
|
// `try_expression` token) with no extra grammar conflict needed, the same
|
|
560
|
-
// way `elvis_expression`'s `?
|
|
559
|
+
// way `elvis_expression`'s `??` needed none. Desugars entirely in
|
|
561
|
-
// `parser.rs` (into `.map(|v| v.member(...))`, reusing
|
|
560
|
+
// `parser.rs` (into `.map(|v| v.member(...))`, reusing Try's
|
|
562
561
|
// existing generic `.map`) — nothing past the parser ever sees this node.
|
|
563
562
|
safe_attribute: ($) =>
|
|
564
563
|
prec(
|
|
@@ -641,9 +640,9 @@ module.exports = grammar({
|
|
|
641
640
|
seq($.expression, "?", $.expression, ":", $.expression),
|
|
642
641
|
),
|
|
643
642
|
|
|
644
|
-
// Elvis / null-coalescing: `opt ?
|
|
643
|
+
// Elvis / null-coalescing: `opt ?? default` — if `left` is `Ok(v)`,
|
|
645
|
-
// the value is `v`; if it's `Err(_)`
|
|
644
|
+
// the value is `v`; if it's `Err(_)`, the value is `right`. A single
|
|
646
|
-
// atomic `"?
|
|
645
|
+
// atomic `"??"` token (maximal munch out-munches the bare `"?"` ternary/
|
|
647
646
|
// try-operator tokens at the same position, so — unlike `try_expression`,
|
|
648
647
|
// which genuinely shares ternary's leading `?` and needs the `conflicts`
|
|
649
648
|
// entry above — this needs no special conflict handling).
|
plum-wasm-codegen/src/lib.rs
CHANGED
|
@@ -383,7 +383,7 @@ pub struct CompileCtx<'a> {
|
|
|
383
383
|
/// wasm-gc type-section indices for this program's enums/Str/closures —
|
|
384
384
|
/// every value's real representation (see `docs/superpowers/plans/2026-07-25-wasm-gc-migration.md`).
|
|
385
385
|
pub gc_types: GcTypeRegistry,
|
|
386
|
-
/// Payload-free variant name (True/False/
|
|
386
|
+
/// Payload-free variant name (True/False/End/...) -> the global index holding
|
|
387
387
|
/// its one pre-allocated instance (see this migration plan's Decision 2).
|
|
388
388
|
pub singleton_globals: HashMap<String, u32>,
|
|
389
389
|
}
|
|
@@ -404,7 +404,7 @@ struct LocalCtx<'a> {
|
|
|
404
404
|
/// `Match` stmt identity (pointer address) -> scratch slot offset.
|
|
405
405
|
match_scratch_index: HashMap<usize, u32>,
|
|
406
406
|
/// First local index reserved for nested-constructor-pattern scratch temporaries
|
|
407
|
-
/// (`
|
|
407
|
+
/// (`Next(Next(v))`'s inner `Next(v)`); the outermost pattern uses a
|
|
408
408
|
/// `match_scratch` slot instead, so this only covers depth >= 1.
|
|
409
409
|
nested_class_scratch_base: u32,
|
|
410
410
|
/// `CasePattern::Class` identity (pointer address) -> scratch slot offset.
|
|
@@ -702,7 +702,7 @@ fn classOrVariantTypeIdx(r: &GcTypeRegistry, name: &str) -> u32 {
|
|
|
702
702
|
.or_else(|| r.class_type_idx.get(name))
|
|
703
703
|
// A scratch slot narrowed to a MULTI-variant enum's own wide supertype
|
|
704
704
|
// (not any one variant) — the `?` operator's scratch local holds the
|
|
705
|
-
// subject at its natural (possibly multi-variant, e.g. `
|
|
705
|
+
// subject at its natural (possibly multi-variant, e.g. `Try`)
|
|
706
706
|
// type, never narrower, since it ref.casts to whichever of the two
|
|
707
707
|
// variants applies only at the point of use, not up front.
|
|
708
708
|
.or_else(|| r.enum_super_type_idx.get(name))
|
|
@@ -917,11 +917,11 @@ fn plumTypeToGcValtype(t: &PlumType, registry: &GcTypeRegistry) -> ValType {
|
|
|
917
917
|
nullable: true,
|
|
918
918
|
heap_type: HeapType::Concrete(*idx),
|
|
919
919
|
}),
|
|
920
|
-
// A field typed as a generic enum (e.g. `Node.next:
|
|
920
|
+
// A field typed as a generic enum (e.g. `Node.next: Link[Node]`)
|
|
921
921
|
// resolves here to the BARE generic name — `plumTypeFromAst`
|
|
922
922
|
// has no representation for type arguments, so it can't know this means
|
|
923
|
-
// `
|
|
923
|
+
// `Link$Int` once monomorphization specializes (and removes the
|
|
924
|
-
// unspecialized) `
|
|
924
|
+
// unspecialized) `Link` — same permissive `anyref` fallback as
|
|
925
925
|
// `plumTypeToValtype` above, for the same "genuinely unmodeled" reason:
|
|
926
926
|
// `struct.get`/`ref.test`/`ref.cast` all work against `anyref` operands
|
|
927
927
|
// fine, so a field merely being STORED as `anyref` instead of the exact
|
|
@@ -943,7 +943,7 @@ fn plumTypeToGcValtype(t: &PlumType, registry: &GcTypeRegistry) -> ValType {
|
|
|
943
943
|
/// ONE `rec` group via `module.addGcTypes` — a single group sidesteps every ordering
|
|
944
944
|
/// question about mutual/self-references (a record-shaped enum's field naming
|
|
945
945
|
/// another such enum's type, an enum variant field referencing its own enum,
|
|
946
|
-
/// `Node.next:
|
|
946
|
+
/// `Node.next: Link[Node]`, etc.), since within one `rec` group members may
|
|
947
947
|
/// reference each other regardless of declaration order.
|
|
948
948
|
fn buildGcTypeRegistry(
|
|
949
949
|
module: &mut WasmModule,
|
|
@@ -1611,7 +1611,7 @@ fn compileSourceInner(
|
|
|
1611
1611
|
}
|
|
1612
1612
|
}
|
|
1613
1613
|
|
|
1614
|
-
// Pre-allocate one instance of every payload-free variant (True/False/
|
|
1614
|
+
// Pre-allocate one instance of every payload-free variant (True/False/End/...)
|
|
1615
1615
|
// as a global, populated once by a `start` function rather than reconstructed on
|
|
1616
1616
|
// every reference — see this migration plan's Decision 2. `struct.new` isn't
|
|
1617
1617
|
// allowed inside a global's own const-expr initializer (confirmed empirically in
|
|
@@ -2087,7 +2087,7 @@ fn registerStringConcatHelper(module: &mut WasmModule, byte_array_type_idx: u32)
|
|
|
2087
2087
|
/// Registers `__string_eq(a: ref Str, b: ref Str) -> i32` (1/0), a hand-written
|
|
2088
2088
|
/// runtime helper backing `Str`'s `==`/`!=` operator. `Str` is a wasm-gc `ref` —
|
|
2089
2089
|
/// the general `Compare` codegen's default for reference-typed operands is
|
|
2090
|
-
/// `ref.eq` (identity), correct for a payload-free singleton (`
|
|
2090
|
+
/// `ref.eq` (identity), correct for a payload-free singleton (`End`/`True`) or
|
|
2091
2091
|
/// class-instance identity, but WRONG for `Str`: two independently-heap-allocated
|
|
2092
2092
|
/// `array<i8>` values holding identical bytes (e.g. two `"a"` literals, or one
|
|
2093
2093
|
/// literal and a runtime-built string) are different objects and would compare
|
|
@@ -3352,7 +3352,7 @@ struct Collector<'a> {
|
|
|
3352
3352
|
/// One scratch-local type per subject (usually one, more for `match a, b, ...`).
|
|
3353
3353
|
match_scratch: HashMap<usize, Vec<PlumType>>,
|
|
3354
3354
|
/// `CasePattern::Class` identity -> its scratch local. Covers EVERY constructor
|
|
3355
|
-
/// pattern, including top-level ones — under wasm-gc, matching `
|
|
3355
|
+
/// pattern, including top-level ones — under wasm-gc, matching `Next(v)` needs a
|
|
3356
3356
|
/// `ref.cast` from the subject's static supertype down to the concrete variant
|
|
3357
3357
|
/// type before any `struct.get` on it validates, and that narrowed value needs
|
|
3358
3358
|
/// its OWN local (declared with the concrete variant's ref type) distinct from
|
|
@@ -3550,12 +3550,12 @@ impl<'a> Collector<'a> {
|
|
|
3550
3550
|
}
|
|
3551
3551
|
ast::Expr::Not(inner) => self.walkExpr(inner),
|
|
3552
3552
|
// `expr?` needs a scratch local to hold `inner`'s value across the
|
|
3553
|
-
// `ref.test` that decides Ok
|
|
3553
|
+
// `ref.test` that decides Ok vs Err (which consumes it)
|
|
3554
3554
|
// and the `ref.cast` that reads it again inside whichever branch —
|
|
3555
3555
|
// reuses the same pointer-identity-keyed scratch-slot mechanism
|
|
3556
3556
|
// constructor patterns already use (`nested_class_scratch`), keyed
|
|
3557
3557
|
// by this `Expr::Try` node itself. Declared with `inner`'s own
|
|
3558
|
-
// (possibly multi-variant, e.g. `
|
|
3558
|
+
// (possibly multi-variant, e.g. `Try`) enum type —
|
|
3559
3559
|
// `classOrVariantTypeIdx` falls back to `enum_super_type_idx` for
|
|
3560
3560
|
// exactly this case — since narrowing happens later via `ref.cast`
|
|
3561
3561
|
// in each branch, not by declaring this local pre-narrowed.
|
|
@@ -4383,7 +4383,7 @@ fn exprHasResult(expr: &ast::Expr, ctx: &LocalCtx) -> bool {
|
|
|
4383
4383
|
/// patterns" fallback in `compile_match_arms` is provably unreachable code, not a real
|
|
4384
4384
|
/// gap. `compileMatch` uses this to append a synthetic trap-and-never-fall-through
|
|
4385
4385
|
/// wildcard arm (rather than let the arms recursion hit its non-exhaustive-match error)
|
|
4386
|
-
/// so previously-working exhaustive enum matches (e.g. `
|
|
4386
|
+
/// so previously-working exhaustive enum matches (e.g. `Next`/`End`, `True`/`False`)
|
|
4387
4387
|
/// keep compiling even without a trailing wildcard, while a genuinely non-exhaustive
|
|
4388
4388
|
/// match (an `Int` match, or an enum match missing a variant) still gets a clear error.
|
|
4389
4389
|
/// True if `cases` already covers every combination of enum variants across all
|
|
@@ -5017,7 +5017,7 @@ fn compileVariantConstructorArm(
|
|
|
5017
5017
|
}
|
|
5018
5018
|
|
|
5019
5019
|
/// Checks `fields[fpos..]` (a constructor pattern's own sub-patterns, e.g. the `v` in
|
|
5020
|
-
/// `
|
|
5020
|
+
/// `Next(v)`, or — recursively — the `Next(v)` in `Wrap(Next(v))`) against the
|
|
5021
5021
|
/// already-loaded value in `container_local`, one field at a time. Once every field
|
|
5022
5022
|
/// has matched, calls `on_match` (typically: proceed to the next top-level subject
|
|
5023
5023
|
/// position). A mismatch at any field — at any nesting depth — falls through to
|
|
@@ -5148,7 +5148,7 @@ fn compileFieldPatterns(
|
|
|
5148
5148
|
}
|
|
5149
5149
|
ast::CasePattern::Name(n) => {
|
|
5150
5150
|
// An uppercase, payload-free variant name used as a field pattern (e.g.
|
|
5151
|
-
// matching a nested `
|
|
5151
|
+
// matching a nested `End` rather than binding a name to it).
|
|
5152
5152
|
ctx.enum_variants
|
|
5153
5153
|
.get(n)
|
|
5154
5154
|
.ok_or_else(|| format!("codegen: unknown enum variant '{}'", n))?;
|
|
@@ -5383,8 +5383,8 @@ fn compileExpr(
|
|
|
5383
5383
|
ast::Expr::Paren(inner) => {
|
|
5384
5384
|
compileExpr(inner, body, ctx, state)?;
|
|
5385
5385
|
}
|
|
5386
|
-
// `expr?` — `ref.test` decides Ok
|
|
5386
|
+
// `expr?` — `ref.test` decides Ok (fall through, extract the
|
|
5387
|
-
// field) vs Err
|
|
5387
|
+
// field) vs Err (return the already-constructed value AS-IS,
|
|
5388
5388
|
// no reconstruction needed). The `then`/`else` branches don't need to
|
|
5389
5389
|
// agree in wasm's usual sense: after `return`, the rest of that
|
|
5390
5390
|
// branch is unreachable, so the validator accepts the `if`'s single
|
|
@@ -5499,7 +5499,7 @@ fn compileExpr(
|
|
|
5499
5499
|
}
|
|
5500
5500
|
Instruction::End.encode(body);
|
|
5501
5501
|
}
|
|
5502
|
-
// `left ?
|
|
5502
|
+
// `left ?? right` — same `ref.test`/scratch-local narrowing as `Try`
|
|
5503
5503
|
// above, but both branches produce an ordinary value (no `return`):
|
|
5504
5504
|
// the failure branch compiles `right` (already unified against the
|
|
5505
5505
|
// success field's type by the checker), the success branch extracts
|
|
@@ -5723,7 +5723,7 @@ fn compileExpr(
|
|
|
5723
5723
|
},
|
|
5724
5724
|
// Bool/class/enum values are wasm-gc refs — `==`/`!=` compares
|
|
5725
5725
|
// reference identity via `ref.eq`. That's exactly right for a
|
|
5726
|
-
// payload-free singleton (`
|
|
5726
|
+
// payload-free singleton (`End`/`True`/`False`, this migration
|
|
5727
5727
|
// plan's Decision 2) and for class-instance identity.
|
|
5728
5728
|
// Ordering a ref type has no meaning and was never valid.
|
|
5729
5729
|
_ => match &c.op {
|
|
@@ -6002,7 +6002,7 @@ fn compileExpr(
|
|
|
6002
6002
|
compileExpr(&synthetic, body, ctx, state)?;
|
|
6003
6003
|
}
|
|
6004
6004
|
// A NAMED-payload enum variant construction (`Circle(radius: 5)`, as
|
|
6005
|
-
// opposed to the positional/generic-payload form `
|
|
6005
|
+
// opposed to the positional/generic-payload form `Next(T)` — constructed
|
|
6006
6006
|
// via `Expr::FnCall` instead, elsewhere) — mirrors `plum-checker`'s own
|
|
6007
6007
|
// `inferClassCallRaw` handling of the same call shape. Against the
|
|
6008
6008
|
// variant's own GC struct type (`gc_types.variant_type_idx`), matched by
|
|
@@ -6342,8 +6342,8 @@ fn compileExpr(
|
|
|
6342
6342
|
Ok(())
|
|
6343
6343
|
}
|
|
6344
6344
|
|
|
6345
|
-
/// Compiles a variant-construction call. A payload-free variant (`
|
|
6345
|
+
/// Compiles a variant-construction call. A payload-free variant (`End`, called as
|
|
6346
|
-
/// `
|
|
6346
|
+
/// `End()` rather than used bare) is its pre-allocated singleton global. A payload
|
|
6347
6347
|
/// variant pushes its field values in order and does `struct.new` into its own
|
|
6348
6348
|
/// concrete variant type.
|
|
6349
6349
|
/// Compiles the missing TRAILING arguments of a call to `func_key` (a plain
|
plum-wasm-codegen/tests/codegen_tests.rs
CHANGED
|
@@ -545,7 +545,7 @@ fn wasmtimeGcConfigSupportsSubtypingRefTestAndNullableSelfReferentialFields() {
|
|
|
545
545
|
// Deeper empirical check: enum-variant subtyping (abstract supertype + concrete
|
|
546
546
|
// subtypes in one `rec` group), ref.test-based dispatch, ref.cast to narrow to a
|
|
547
547
|
// subtype, and a nullable field that references the struct's OWN type (the
|
|
548
|
-
// Node.next:
|
|
548
|
+
// Node.next: Link[Node] shape List needs) — all in one hand-encoded module,
|
|
549
549
|
// bypassing plum entirely.
|
|
550
550
|
use wasm_encoder::*;
|
|
551
551
|
|
|
@@ -553,8 +553,8 @@ fn wasmtimeGcConfigSupportsSubtypingRefTestAndNullableSelfReferentialFields() {
|
|
|
553
553
|
let mut types = TypeSection::new();
|
|
554
554
|
|
|
555
555
|
// rec group: type 0 = abstract enum supertype (empty struct, non-final so it can
|
|
556
|
-
// be subtyped); type 1 = concrete "
|
|
556
|
+
// be subtyped); type 1 = concrete "Next"-like subtype with one i32 payload field;
|
|
557
|
-
// type 2 = concrete "
|
|
557
|
+
// type 2 = concrete "End"-like subtype (empty, no payload).
|
|
558
558
|
types.ty().rec(vec![
|
|
559
559
|
SubType {
|
|
560
560
|
is_final: false,
|
|
@@ -619,7 +619,7 @@ fn wasmtimeGcConfigSupportsSubtypingRefTestAndNullableSelfReferentialFields() {
|
|
|
619
619
|
},
|
|
620
620
|
}]);
|
|
621
621
|
|
|
622
|
-
// type 4: () -> i32 — constructs a "
|
|
622
|
+
// type 4: () -> i32 — constructs a "Next"-like subtype (type 1) holding 42,
|
|
623
623
|
// stores it as the supertype (type 0), ref.tests it against type 1, then
|
|
624
624
|
// ref.casts and struct.gets the payload back out. Also builds a 2-node linked
|
|
625
625
|
// list (type 3) and confirms unlinking (overwriting `next` with ref.null) and
|
|
@@ -653,7 +653,7 @@ fn wasmtimeGcConfigSupportsSubtypingRefTestAndNullableSelfReferentialFields() {
|
|
|
653
653
|
let locals_super = 0u32;
|
|
654
654
|
let locals_node = 1u32;
|
|
655
655
|
|
|
656
|
-
// local_super =
|
|
656
|
+
// local_super = Next(42) (as the supertype)
|
|
657
657
|
f.instruction(&Instruction::I32Const(42));
|
|
658
658
|
f.instruction(&Instruction::StructNew(1));
|
|
659
659
|
f.instruction(&Instruction::LocalSet(locals_super));
|
|
@@ -722,7 +722,7 @@ fn wasmtimeGcConfigSupportsSubtypingRefTestAndNullableSelfReferentialFields() {
|
|
|
722
722
|
#[test]
|
|
723
723
|
fn wasmtimeGcConfigAllowsStructNewInGlobalConstExpr() {
|
|
724
724
|
// Decision 2 of the wasm-gc migration plan pre-allocates payload-free enum
|
|
725
|
-
// variants (True/False/
|
|
725
|
+
// variants (True/False/End/...) once as globals. Confirm a global's
|
|
726
726
|
// initializer expression can directly use struct.new (not just i32.const/
|
|
727
727
|
// ref.null), or the plan needs a `start` function fallback instead.
|
|
728
728
|
use wasm_encoder::*;
|