@ball-lang/compiler 1.68.7 → 1.69.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ball-lang/compiler",
3
- "version": "1.68.7",
3
+ "version": "1.69.0",
4
4
  "description": "Ball → TypeScript compiler. Consumes a Ball protobuf Program and emits idiomatic TypeScript via ts-morph. The canonical TS compiler for Ball — lives in TS land so TS syntax knowledge doesn't leak into other languages.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/compiler.ts CHANGED
@@ -5509,20 +5509,65 @@ function __isUnknownFnError(e: any): boolean {
5509
5509
  if (list) return `${this.expr(list)}.join('')`;
5510
5510
  return "''";
5511
5511
  }
5512
- // Neither encoder ever emits these as a direct `std.<fn>` base-
5513
- // function call a Set method call (mySet.union(other), etc.)
5514
- // routes through compileCall's generic "self"-field method dispatch
5515
- // onto native JS Set.prototype methods instead, so this case is
5516
- // never actually reached. The old fallback compiled it to a call on
5517
- // a nonexistent bare identifier (a confusing runtime ReferenceError
5518
- // if it WERE ever hit); fail loud at compile time instead, matching
5519
- // the policy above (#257).
5520
- case "set_add": case "set_remove": case "set_contains":
5521
- case "set_union": case "set_intersection": case "set_difference":
5522
- case "set_length": case "set_is_empty": case "set_to_list":
5523
- throw new Error(
5524
- `TS compiler: std.${fn} is not implemented (compileStdCall) — Set method calls should route through native JS Set methods, not this base function`,
5525
- );
5512
+ // The `std_collections` set family, on native JS `Set`s — the same value
5513
+ // `set_create` above compiles to (`new Set([...])`).
5514
+ //
5515
+ // These used to throw "not implemented", on the reasoning that neither
5516
+ // encoder emits a direct `std_collections.set_*` call (a Set METHOD call
5517
+ // routes through compileCall's generic "self"-field dispatch instead). But
5518
+ // "no encoder emits it" is not "no program contains it": a hand-authored
5519
+ // or tool-generated Ball program can, and every other compiler in the repo
5520
+ // (Dart/C++/Rust/C#/Go/Python) implements the family. Conformance fixture
5521
+ // 459_set_add_remove_bool is exactly such a program, and it made this
5522
+ // throw reachable so implement the family rather than carve it out.
5523
+ //
5524
+ // `set_add`/`set_remove` mutate in place and answer BOOL (issue #545) —
5525
+ // `Set.add` returns the set in JS, so it is wrapped; `Set.delete` already
5526
+ // reports presence.
5527
+ //
5528
+ // A member whose operand field is MISSING throws (`__setOperand`) rather
5529
+ // than emitting a placeholder like `false` / `0` / `new Set()`: a
5530
+ // `std_collections` set call without its operand is a malformed program,
5531
+ // and quietly compiling it to a constant is precisely the silent
5532
+ // degradation that hid issue #55. Nothing regresses by throwing — until
5533
+ // #545 this whole family threw unconditionally, so any input that reaches
5534
+ // the throw was rejected by the old code too.
5535
+ case "set_add": {
5536
+ const set = __setOperand(fn, "set", f.get("set") ?? f.get("collection"));
5537
+ const value = __setOperand(fn, "value", f.get("value") ?? f.get("element"));
5538
+ return `(() => { const __s = ${this.expr(set)}; const __v = ${this.expr(value)}; if (__s.has(__v)) return false; __s.add(__v); return true; })()`;
5539
+ }
5540
+ case "set_remove": {
5541
+ const set = __setOperand(fn, "set", f.get("set") ?? f.get("collection"));
5542
+ const value = __setOperand(fn, "value", f.get("value") ?? f.get("element"));
5543
+ return `${this.expr(set)}.delete(${this.expr(value)})`;
5544
+ }
5545
+ case "set_contains": {
5546
+ const set = __setOperand(fn, "set", f.get("set") ?? f.get("collection"));
5547
+ const value = __setOperand(fn, "value", f.get("value") ?? f.get("element"));
5548
+ return `${this.expr(set)}.has(${this.expr(value)})`;
5549
+ }
5550
+ case "set_length":
5551
+ return `${this.expr(__setOperand(fn, "set", f.get("set") ?? f.get("collection")))}.size`;
5552
+ case "set_is_empty":
5553
+ return `(${this.expr(__setOperand(fn, "set", f.get("set") ?? f.get("collection")))}.size === 0)`;
5554
+ case "set_to_list":
5555
+ return `[...${this.expr(__setOperand(fn, "set", f.get("set") ?? f.get("collection")))}]`;
5556
+ case "set_union": {
5557
+ const l = __setOperand(fn, "left", f.get("left") ?? f.get("set"));
5558
+ const r = __setOperand(fn, "right", f.get("right") ?? f.get("other"));
5559
+ return `new Set([...${this.expr(l)}, ...${this.expr(r)}])`;
5560
+ }
5561
+ case "set_intersection": {
5562
+ const l = __setOperand(fn, "left", f.get("left") ?? f.get("set"));
5563
+ const r = __setOperand(fn, "right", f.get("right") ?? f.get("other"));
5564
+ return `(() => { const __b = ${this.expr(r)}; return new Set([...${this.expr(l)}].filter((__x: any) => __b.has(__x))); })()`;
5565
+ }
5566
+ case "set_difference": {
5567
+ const l = __setOperand(fn, "left", f.get("left") ?? f.get("set"));
5568
+ const r = __setOperand(fn, "right", f.get("right") ?? f.get("other"));
5569
+ return `(() => { const __b = ${this.expr(r)}; return new Set([...${this.expr(l)}].filter((__x: any) => !__b.has(__x))); })()`;
5570
+ }
5526
5571
  // I/O
5527
5572
  case "print_error": {
5528
5573
  const msg = f.get("message") ?? f.get("value");
@@ -6400,6 +6445,29 @@ function fieldMap(fields: FieldValuePair[]): Map<string, Expression> {
6400
6445
  return m;
6401
6446
  }
6402
6447
 
6448
+ /**
6449
+ * Assert that a `std_collections` set operation was handed the operand it needs.
6450
+ *
6451
+ * The set family (issue #545) FAILS LOUD on a missing field rather than
6452
+ * emitting a placeholder (`false` / `0` / `new Set()`). A set call without its
6453
+ * operand is a malformed program, and compiling it to a constant is the silent
6454
+ * degradation CLAUDE.md forbids and issue #55 was made of. Throwing costs
6455
+ * nothing in compatibility: before #545 every member of the family threw
6456
+ * unconditionally, so anything that reaches here was already rejected.
6457
+ */
6458
+ function __setOperand(
6459
+ fn: string,
6460
+ field: string,
6461
+ value: Expression | undefined,
6462
+ ): Expression {
6463
+ if (value === undefined) {
6464
+ throw new Error(
6465
+ `TS compiler: std_collections.${fn} is missing its "${field}" field`,
6466
+ );
6467
+ }
6468
+ return value;
6469
+ }
6470
+
6403
6471
  function memberShortName(qualified: string): string {
6404
6472
  const dot = qualified.lastIndexOf(".");
6405
6473
  return sanitize(dot < 0 ? qualified : qualified.slice(dot + 1));