@kumikijs/compiler 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -286,6 +286,14 @@ type Expr = {
286
286
  base: Expr;
287
287
  field: string;
288
288
  pos: Pos;
289
+ /**
290
+ * Dispatch decision filled in by the type checker (ADR-002): `"field"`
291
+ * means the receiver is a record with this field, so codegen lowers a
292
+ * field read instead of a method shortcut (kills the #23 shadow). Absent /
293
+ * `"shortcut"` keeps the name-based shortcut dispatch (back-compat — also
294
+ * the case when codegen runs without `check()`).
295
+ */
296
+ accessKind?: "field" | "shortcut";
289
297
  } | {
290
298
  kind: "Index";
291
299
  base: Expr;
@@ -463,6 +471,28 @@ type CodegenOptions = {
463
471
  includeTests?: boolean;
464
472
  };
465
473
  declare function codegen(program: Program, opts: CodegenOptions): string;
474
+ /**
475
+ * Methods the code generator actually implements (the `methodCallJs` switch
476
+ * cases below). This is the single source of truth for what `obj.method(...)`
477
+ * calls are runnable; the typechecker uses it to flag unimplemented methods
478
+ * (E0801) at `check` time instead of letting them throw or misbehave at runtime.
479
+ * Keep this in exact sync with the `switch (method)` cases.
480
+ */
481
+ declare const KNOWN_METHODS: ReadonlySet<string>;
482
+ /**
483
+ * The method names codegen lowers in the parenthesis-free `recv.m` (FieldAccess)
484
+ * form — kept in sync with the `if (e.field === …)` chain in jsOfExpr's
485
+ * FieldAccess case. A subset of KNOWN_METHODS (enforced by a test): every
486
+ * no-paren shortcut must also accept the `recv.m()` shape.
487
+ */
488
+ declare const FIELD_ACCESS_SHORTCUTS: ReadonlySet<string>;
489
+ /**
490
+ * Every member name the runtime understands on a stdlib receiver — the union of
491
+ * the method-call methods and the no-paren shortcuts. Used by the type checker
492
+ * (ADR-002) to decide whether `recv.m` on a *known* receiver type is a real
493
+ * member (→ shortcut) or an unknown one (→ E0108). Flat, not per-type.
494
+ */
495
+ declare const KNOWN_MEMBERS: ReadonlySet<string>;
466
496
  declare const RUNTIME_HELPERS = "\nfunction _setPath(obj, path, value) {\n if (path.length === 0) return value;\n const [head, ...rest] = path;\n const cur = obj ?? {};\n return { ...cur, [head]: _setPath(cur[head], rest, value) };\n}\nfunction _children(...xs) {\n const out = [];\n for (const x of xs) {\n if (x === null || x === undefined) continue;\n if (Array.isArray(x)) {\n for (const y of x) if (y !== null && y !== undefined) out.push(y);\n } else {\n out.push(x);\n }\n }\n return out;\n}\nfunction _attachProps(node, props) {\n if (!node || !props) return node;\n return { ...node, props: { ...(node.props || {}), ...props } };\n}\n";
467
497
  //#endregion
468
498
  //#region src/typecheck.d.ts
@@ -519,4 +549,4 @@ declare class ParseError extends Error {
519
549
  }
520
550
  declare function parse(tokens: Token[]): Program;
521
551
  //#endregion
522
- export { type AppDef, type BinOp, type CapabilityManifest, type CodegenOptions, type CompileFail, type CompileOk, type CompileResult, type Def, type EffectDef, type EventPattern, type Expr, type ExtendedCodegenOptions, type FnDef, type KumikiError, type Lvalue, type ManifestResult, type MatchArm, type MotionDef, ParseError, type Pattern, type PolicyExpr, type Pos, type Program, RUNTIME_HELPERS, type ReducerDef, type Refinement, type RetryExpr, STANDARD_CAPABILITIES, type SlotDef, type Statement, type TestDef, type ThemeDef, type ThemeValue, type TileArg, type TileDef, type TileExpr, type TileMatchArm, type TileProp, type Token, type TypeDef, type TypeExpr, type UiEventKind, check, codegen, compile, inlineRuntime, lex, parse, parseCapabilityManifest };
552
+ export { type AppDef, type BinOp, type CapabilityManifest, type CodegenOptions, type CompileFail, type CompileOk, type CompileResult, type Def, type EffectDef, type EventPattern, type Expr, type ExtendedCodegenOptions, FIELD_ACCESS_SHORTCUTS, type FnDef, KNOWN_MEMBERS, KNOWN_METHODS, type KumikiError, type Lvalue, type ManifestResult, type MatchArm, type MotionDef, ParseError, type Pattern, type PolicyExpr, type Pos, type Program, RUNTIME_HELPERS, type ReducerDef, type Refinement, type RetryExpr, STANDARD_CAPABILITIES, type SlotDef, type Statement, type TestDef, type ThemeDef, type ThemeValue, type TileArg, type TileDef, type TileExpr, type TileMatchArm, type TileProp, type Token, type TypeDef, type TypeExpr, type UiEventKind, check, codegen, compile, inlineRuntime, lex, parse, parseCapabilityManifest };
package/dist/index.js CHANGED
@@ -373,6 +373,7 @@ function jsOfExpr(e, ctx) {
373
373
  case "UnaryOp": return `(${e.op === "!" ? "!" : "-"}${jsOfExpr(e.rhs, ctx)})`;
374
374
  case "FieldAccess": {
375
375
  const baseJs = jsOfExpr(e.base, ctx);
376
+ if (e.accessKind === "field") return `(${baseJs})[${JSON.stringify(e.field)}]`;
376
377
  if (e.field === "get") return `_s.unwrap(${baseJs})`;
377
378
  if (e.field === "is-some") return `(_s.variantIs(${baseJs}, "Some"))`;
378
379
  if (e.field === "is-none") return `(_s.variantIs(${baseJs}, "None"))`;
@@ -432,6 +433,7 @@ function jsOfExpr(e, ctx) {
432
433
  const args = e.args.map((a) => jsOfExpr(a, ctx));
433
434
  return `_s.fmt ? _s.fmt(${args.join(", ")}) : ${args[0] ?? "\"\""}`;
434
435
  }
436
+ if (cn === "panic") return `_s.panic(${e.args[0] ? jsOfExpr(e.args[0], ctx) : "\"\""})`;
435
437
  const args = e.args.map((a) => jsOfExpr(a, ctx)).join(", ");
436
438
  return `${jsName(cn)}(${args})`;
437
439
  }
@@ -518,8 +520,63 @@ const KNOWN_METHODS = new Set([
518
520
  "abs",
519
521
  "neg",
520
522
  "to-float",
523
+ "to-int",
524
+ "is-ok",
525
+ "is-err",
526
+ "values",
527
+ "entries",
528
+ "lower",
529
+ "upper",
530
+ "sort",
531
+ "ms"
532
+ ]);
533
+ /**
534
+ * The method names codegen lowers in the parenthesis-free `recv.m` (FieldAccess)
535
+ * form — kept in sync with the `if (e.field === …)` chain in jsOfExpr's
536
+ * FieldAccess case. A subset of KNOWN_METHODS (enforced by a test): every
537
+ * no-paren shortcut must also accept the `recv.m()` shape.
538
+ */
539
+ const FIELD_ACCESS_SHORTCUTS = new Set([
540
+ "get",
541
+ "is-some",
542
+ "is-none",
543
+ "is-ok",
544
+ "is-err",
545
+ "keys",
546
+ "values",
547
+ "entries",
548
+ "size",
549
+ "to-ms",
550
+ "ms",
551
+ "show",
552
+ "length",
553
+ "is-empty",
554
+ "lower",
555
+ "upper",
556
+ "trim",
557
+ "unique",
558
+ "reverse",
559
+ "sort",
560
+ "head",
561
+ "tail",
562
+ "last",
563
+ "to-list",
564
+ "get-err",
565
+ "to-option",
566
+ "parse-int",
567
+ "parse-float",
568
+ "abs",
569
+ "neg",
570
+ "to-float",
521
571
  "to-int"
522
572
  ]);
573
+ /**
574
+ * Every member name the runtime understands on a stdlib receiver — the union of
575
+ * the method-call methods and the no-paren shortcuts. Used by the type checker
576
+ * (ADR-002) to decide whether `recv.m` on a *known* receiver type is a real
577
+ * member (→ shortcut) or an unknown one (→ E0108). Flat, not per-type.
578
+ */
579
+ const KNOWN_MEMBERS = new Set([...KNOWN_METHODS, ...FIELD_ACCESS_SHORTCUTS]);
523
580
  function methodCallJs(recv, method, args, ctx) {
524
581
  const inner = makeEvalCtx(ctx.gen, ctx.localBinds);
525
582
  inner.localBinds.add("$1");
@@ -3310,9 +3367,13 @@ function checkSlot(slot, sym, errors) {
3310
3367
  function checkTile(tile, sym, errors) {
3311
3368
  const ctx = {
3312
3369
  kind: "tile",
3313
- localBinds: /* @__PURE__ */ new Set()
3370
+ localBinds: /* @__PURE__ */ new Set(),
3371
+ localTypes: /* @__PURE__ */ new Map()
3314
3372
  };
3315
- if (tile.in) ctx.localBinds.add("$1");
3373
+ if (tile.in) {
3374
+ ctx.localBinds.add("$1");
3375
+ ctx.localTypes?.set("$1", tile.in);
3376
+ }
3316
3377
  checkTileExpr(tile.body, sym, errors, ctx);
3317
3378
  }
3318
3379
  function checkA11y(t, errors) {
@@ -3351,7 +3412,8 @@ function checkTileExpr(t, sym, errors, ctx) {
3351
3412
  checkExpr(t.iter, sym, errors, ctx);
3352
3413
  const inner = {
3353
3414
  ...ctx,
3354
- localBinds: new Set(ctx.localBinds)
3415
+ localBinds: new Set(ctx.localBinds),
3416
+ localTypes: new Map(ctx.localTypes ?? [])
3355
3417
  };
3356
3418
  inner.localBinds.add(t.bind);
3357
3419
  checkTileExpr(t.body, sym, errors, inner);
@@ -3371,7 +3433,8 @@ function checkTileExpr(t, sym, errors, ctx) {
3371
3433
  for (const arm of t.arms) {
3372
3434
  const inner = {
3373
3435
  ...ctx,
3374
- localBinds: new Set(ctx.localBinds)
3436
+ localBinds: new Set(ctx.localBinds),
3437
+ localTypes: new Map(ctx.localTypes ?? [])
3375
3438
  };
3376
3439
  if (arm.pattern.kind === "PVariant") for (const b of arm.pattern.binds) inner.localBinds.add(b);
3377
3440
  if (arm.pattern.kind === "PBind") inner.localBinds.add(arm.pattern.name);
@@ -3469,7 +3532,8 @@ function checkStmt(s, sym, errors, ctx, writtenRoots) {
3469
3532
  checkExpr(s.iter, sym, errors, ctx);
3470
3533
  const inner = {
3471
3534
  ...ctx,
3472
- localBinds: new Set(ctx.localBinds)
3535
+ localBinds: new Set(ctx.localBinds),
3536
+ localTypes: new Map(ctx.localTypes ?? [])
3473
3537
  };
3474
3538
  inner.localBinds.add(s.bind);
3475
3539
  const bodyWrites = new Set(writtenRoots);
@@ -3493,7 +3557,8 @@ function checkStmt(s, sym, errors, ctx, writtenRoots) {
3493
3557
  for (const arm of s.arms) {
3494
3558
  const inner = {
3495
3559
  ...ctx,
3496
- localBinds: new Set(ctx.localBinds)
3560
+ localBinds: new Set(ctx.localBinds),
3561
+ localTypes: new Map(ctx.localTypes ?? [])
3497
3562
  };
3498
3563
  if (arm.pattern.kind === "PVariant") {
3499
3564
  for (const b of arm.pattern.binds) if (b !== "_") inner.localBinds.add(b);
@@ -3510,6 +3575,11 @@ function checkStmt(s, sym, errors, ctx, writtenRoots) {
3510
3575
  if (s.kind === "LetStmt") {
3511
3576
  checkExpr(s.rhs, sym, errors, ctx);
3512
3577
  ctx.localBinds.add(s.name);
3578
+ const rt = inferType(s.rhs, sym, ctx);
3579
+ if (rt) {
3580
+ if (!ctx.localTypes) ctx.localTypes = /* @__PURE__ */ new Map();
3581
+ ctx.localTypes.set(s.name, rt);
3582
+ }
3513
3583
  return;
3514
3584
  }
3515
3585
  if (s.kind === "Emit") {
@@ -3615,6 +3685,7 @@ function checkExpr(e, sym, errors, ctx) {
3615
3685
  return;
3616
3686
  case "FieldAccess":
3617
3687
  checkExpr(e.base, sym, errors, ctx);
3688
+ classifyFieldAccess(e, sym, errors, ctx);
3618
3689
  return;
3619
3690
  case "Index":
3620
3691
  checkExpr(e.base, sym, errors, ctx);
@@ -3634,7 +3705,8 @@ function checkExpr(e, sym, errors, ctx) {
3634
3705
  for (const a of e.args) {
3635
3706
  const inner = {
3636
3707
  ...ctx,
3637
- localBinds: new Set(ctx.localBinds)
3708
+ localBinds: new Set(ctx.localBinds),
3709
+ localTypes: new Map(ctx.localTypes ?? [])
3638
3710
  };
3639
3711
  inner.localBinds.add("$1");
3640
3712
  inner.localBinds.add("$2");
@@ -3658,7 +3730,8 @@ function checkExpr(e, sym, errors, ctx) {
3658
3730
  for (const arm of e.arms) {
3659
3731
  const inner = {
3660
3732
  ...ctx,
3661
- localBinds: new Set(ctx.localBinds)
3733
+ localBinds: new Set(ctx.localBinds),
3734
+ localTypes: new Map(ctx.localTypes ?? [])
3662
3735
  };
3663
3736
  if (arm.pattern.kind === "PVariant") {
3664
3737
  for (const b of arm.pattern.binds) if (b !== "_") inner.localBinds.add(b);
@@ -3676,21 +3749,180 @@ function checkExpr(e, sym, errors, ctx) {
3676
3749
  checkExpr(e.value, sym, errors, ctx);
3677
3750
  const inner = {
3678
3751
  ...ctx,
3679
- localBinds: new Set(ctx.localBinds)
3752
+ localBinds: new Set(ctx.localBinds),
3753
+ localTypes: new Map(ctx.localTypes ?? [])
3680
3754
  };
3681
3755
  inner.localBinds.add(e.name);
3756
+ const vt = inferType(e.value, sym, inner);
3757
+ if (vt) inner.localTypes?.set(e.name, vt);
3682
3758
  checkExpr(e.body, sym, errors, inner);
3683
3759
  return;
3684
3760
  }
3685
3761
  }
3686
3762
  }
3763
+ const SCALAR_PRIMS = new Set([
3764
+ "Int",
3765
+ "Float",
3766
+ "Text",
3767
+ "Bool",
3768
+ "Time",
3769
+ "Bytes"
3770
+ ]);
3771
+ const STDLIB_CONTAINERS = new Set([
3772
+ "List",
3773
+ "Map",
3774
+ "Set",
3775
+ "Option",
3776
+ "Result"
3777
+ ]);
3778
+ /** Unwrap type aliases (`TypeRef` → its `TypeDef` body) and nominal/refinement wrappers. */
3779
+ function unaliasType(t, sym, seen = /* @__PURE__ */ new Set()) {
3780
+ if (!t) return null;
3781
+ if (t.kind === "TypeRef") {
3782
+ if (seen.has(t.name)) return null;
3783
+ const def = sym.types.get(t.name);
3784
+ if (!def) return t;
3785
+ seen.add(t.name);
3786
+ return unaliasType(def.body, sym, seen);
3787
+ }
3788
+ if (t.kind === "TypeNominal" || t.kind === "TypeRefinement") return unaliasType(t.inner, sym, seen);
3789
+ return t;
3790
+ }
3791
+ function recordFieldType(rec, name) {
3792
+ return rec.fields.find((f) => f.name === name)?.type ?? null;
3793
+ }
3794
+ const unitType = (pos) => ({
3795
+ kind: "TypePrim",
3796
+ name: "Unit",
3797
+ pos
3798
+ });
3799
+ /** Best-effort static type of an expression; `null` = undecidable / dynamic. */
3800
+ function inferType(e, sym, ctx) {
3801
+ switch (e.kind) {
3802
+ case "Num": return {
3803
+ kind: "TypePrim",
3804
+ name: "Int",
3805
+ pos: e.pos
3806
+ };
3807
+ case "Str": return {
3808
+ kind: "TypePrim",
3809
+ name: "Text",
3810
+ pos: e.pos
3811
+ };
3812
+ case "Bool": return {
3813
+ kind: "TypePrim",
3814
+ name: "Bool",
3815
+ pos: e.pos
3816
+ };
3817
+ case "Ref": {
3818
+ const bound = ctx.localTypes?.get(e.name);
3819
+ if (bound) return bound;
3820
+ return sym.slots.get(e.name)?.type ?? null;
3821
+ }
3822
+ case "FieldAccess": {
3823
+ const base = unaliasType(inferType(e.base, sym, ctx), sym);
3824
+ if (!base) return null;
3825
+ if (base.kind === "TypeRecord") return recordFieldType(base, e.field);
3826
+ if (e.field === "get" && base.kind === "TypeApp" && (base.name === "Option" || base.name === "Result")) return base.args[0] ?? null;
3827
+ return null;
3828
+ }
3829
+ case "Index": {
3830
+ const base = unaliasType(inferType(e.base, sym, ctx), sym);
3831
+ if (base?.kind === "TypeApp") {
3832
+ if (base.name === "List" || base.name === "Set") return base.args[0] ?? null;
3833
+ if (base.name === "Map") return base.args[1] ?? null;
3834
+ }
3835
+ return null;
3836
+ }
3837
+ case "MethodCall":
3838
+ if (e.method === "get") {
3839
+ const recv = unaliasType(inferType(e.receiver, sym, ctx), sym);
3840
+ if (recv?.kind === "TypeApp") {
3841
+ if (recv.name === "Map") return recv.args[1] ? {
3842
+ kind: "TypeApp",
3843
+ name: "Option",
3844
+ args: [recv.args[1]],
3845
+ pos: e.pos
3846
+ } : null;
3847
+ if (recv.name === "Option" || recv.name === "Result") return recv.args[0] ?? null;
3848
+ }
3849
+ }
3850
+ return null;
3851
+ case "RecordLit": return {
3852
+ kind: "TypeRecord",
3853
+ fields: e.fields.map((f) => ({
3854
+ name: f.name,
3855
+ type: inferType(f.value, sym, ctx) ?? unitType(f.value.pos)
3856
+ })),
3857
+ pos: e.pos
3858
+ };
3859
+ case "Variant": {
3860
+ const inner = e.payload[0] ? inferType(e.payload[0], sym, ctx) ?? unitType(e.pos) : unitType(e.pos);
3861
+ if (e.name === "Some" || e.name === "None") return {
3862
+ kind: "TypeApp",
3863
+ name: "Option",
3864
+ args: [inner],
3865
+ pos: e.pos
3866
+ };
3867
+ if (e.name === "Ok") return {
3868
+ kind: "TypeApp",
3869
+ name: "Result",
3870
+ args: [inner],
3871
+ pos: e.pos
3872
+ };
3873
+ return null;
3874
+ }
3875
+ default: return null;
3876
+ }
3877
+ }
3878
+ /**
3879
+ * Decide whether `recv.field` is a record field read or a method shortcut, and
3880
+ * annotate the node so codegen lowers the right thing (ADR-002). Emits E0108
3881
+ * when the receiver type is KNOWN and `field` is neither a member nor a record
3882
+ * field; stays silent (shortcut) when the type is undecidable.
3883
+ */
3884
+ function classifyFieldAccess(e, sym, errors, ctx) {
3885
+ const t = unaliasType(inferType(e.base, sym, ctx), sym);
3886
+ if (!t) return;
3887
+ if (t.kind === "TypeRecord") {
3888
+ if (recordFieldType(t, e.field)) {
3889
+ e.accessKind = "field";
3890
+ return;
3891
+ }
3892
+ if (e.field === "show") {
3893
+ e.accessKind = "shortcut";
3894
+ return;
3895
+ }
3896
+ errors.push({
3897
+ code: "E0108",
3898
+ kind: "undef-member",
3899
+ message: `Record type has no field or method ".${e.field}"`,
3900
+ pos: e.pos
3901
+ });
3902
+ return;
3903
+ }
3904
+ if (t.kind === "TypePrim" && SCALAR_PRIMS.has(t.name) || t.kind === "TypeApp" && STDLIB_CONTAINERS.has(t.name)) {
3905
+ if (KNOWN_MEMBERS.has(e.field)) {
3906
+ e.accessKind = "shortcut";
3907
+ return;
3908
+ }
3909
+ const tn = t.kind === "TypeApp" ? t.name : t.name;
3910
+ errors.push({
3911
+ code: "E0108",
3912
+ kind: "undef-member",
3913
+ message: `Type "${tn}" has no member ".${e.field}"`,
3914
+ pos: e.pos
3915
+ });
3916
+ }
3917
+ }
3687
3918
  function currentFnName(ctx) {
3688
3919
  return ctx.fnName ?? "<fn>";
3689
3920
  }
3690
3921
  function checkFn(fn, sym, errors) {
3691
3922
  const ctx = {
3692
3923
  kind: "fn",
3693
- localBinds: /* @__PURE__ */ new Set()
3924
+ localBinds: /* @__PURE__ */ new Set(),
3925
+ localTypes: new Map(fn.params.map((p) => [p.name, p.type]))
3694
3926
  };
3695
3927
  ctx.fnName = fn.name;
3696
3928
  for (const p of fn.params) ctx.localBinds.add(p.name);
@@ -3805,4 +4037,4 @@ function compile(source, opts) {
3805
4037
  };
3806
4038
  }
3807
4039
  //#endregion
3808
- export { ParseError, RUNTIME_HELPERS, STANDARD_CAPABILITIES, check, codegen, compile, inlineRuntime, lex, parse, parseCapabilityManifest };
4040
+ export { FIELD_ACCESS_SHORTCUTS, KNOWN_MEMBERS, KNOWN_METHODS, ParseError, RUNTIME_HELPERS, STANDARD_CAPABILITIES, check, codegen, compile, inlineRuntime, lex, parse, parseCapabilityManifest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kumikijs/compiler",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Kumiki compiler — lexer, parser, typechecker, and code generator.",
6
6
  "license": "Apache-2.0",
@@ -41,7 +41,7 @@
41
41
  "provenance": true
42
42
  },
43
43
  "dependencies": {
44
- "@kumikijs/runtime": "0.2.1"
44
+ "@kumikijs/runtime": "0.3.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^25.9.1",