@ball-lang/compiler 1.59.9 → 1.59.11
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/compiler.d.ts.map +1 -1
- package/dist/compiler.js +78 -2
- package/dist/compiler.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler.ts +77 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ball-lang/compiler",
|
|
3
|
-
"version": "1.59.
|
|
3
|
+
"version": "1.59.11",
|
|
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
|
@@ -3900,6 +3900,18 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
3900
3900
|
const idx = field(c, "index");
|
|
3901
3901
|
if (t && idx) return `${this.expr(t)}[${this.expr(idx)}]`;
|
|
3902
3902
|
}
|
|
3903
|
+
// `null_aware_index` in ASSIGNMENT-TARGET position must be a plain `[…]`:
|
|
3904
|
+
// `a?.[i] = v` is a SyntaxError ("the left-hand side of an assignment
|
|
3905
|
+
// expression must be a variable or a property access"), which is why the
|
|
3906
|
+
// read path emitting `?.[` alone broke the whole self-hosted engine. Dart's
|
|
3907
|
+
// `a?[i] = v` short-circuits when `a` is null; JS has no such form, so the
|
|
3908
|
+
// plain index is the closest faithful lowering for the write path.
|
|
3909
|
+
if (c && (c.function === "null_aware_index") &&
|
|
3910
|
+
(c.module === "" || c.module === "std")) {
|
|
3911
|
+
const t = field(c, "self") ?? field(c, "target");
|
|
3912
|
+
const idx = field(c, "index") ?? field(c, "key");
|
|
3913
|
+
if (t && idx) return `${this.expr(t)}[${this.expr(idx)}]`;
|
|
3914
|
+
}
|
|
3903
3915
|
return this.expr(e);
|
|
3904
3916
|
}
|
|
3905
3917
|
|
|
@@ -4835,7 +4847,13 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
4835
4847
|
if (!self_ || !idx) {
|
|
4836
4848
|
throw new Error("TS compiler: null_aware_index requires a \"self\"/\"target\" and an \"index\"/\"key\" field");
|
|
4837
4849
|
}
|
|
4838
|
-
|
|
4850
|
+
// `?.[` — NOT a plain `[`. Emitting `target[index]` threw
|
|
4851
|
+
// "Cannot read properties of null" for exactly the null receiver the
|
|
4852
|
+
// construct exists to guard against, which is the opposite of what
|
|
4853
|
+
// `a?.[i]` means. Caught by the optional-element-access round-trip
|
|
4854
|
+
// fixture added with #489 (no encoder emitted this shape before, so
|
|
4855
|
+
// nothing had ever executed it).
|
|
4856
|
+
return `${this.expr(self_)}?.[${this.expr(idx)}]`;
|
|
4839
4857
|
}
|
|
4840
4858
|
case "null_aware_access": {
|
|
4841
4859
|
const target = f.get("target");
|
|
@@ -5076,6 +5094,23 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5076
5094
|
if (v && from && to) return `${this.expr(v)}.replace(${this.expr(from)}, ${this.expr(to)})`;
|
|
5077
5095
|
return "''";
|
|
5078
5096
|
}
|
|
5097
|
+
// ── Regex (std) ───────────────────────────────────────────────────
|
|
5098
|
+
// Field shapes mirror the Dart reference engine exactly
|
|
5099
|
+
// (dart/engine/lib/engine_std.dart): match/find/find_all take
|
|
5100
|
+
// left = subject, right = pattern; replace/replace_all take
|
|
5101
|
+
// value/from/to. The pattern is always a plain string — the std regex
|
|
5102
|
+
// functions model no flags, so the TS encoder reports a dropped flag as
|
|
5103
|
+
// a behaviour-affecting warning rather than encoding it (#490).
|
|
5104
|
+
case "regex_match":
|
|
5105
|
+
return `new RegExp(${this.expr(fg("right", "pattern", "arg1")!)}).test(${this.expr(fg("left", "value", "arg0")!)})`;
|
|
5106
|
+
case "regex_find":
|
|
5107
|
+
return `(${this.expr(fg("left", "value", "arg0")!)}.match(new RegExp(${this.expr(fg("right", "pattern", "arg1")!)})) ?? [null])[0]`;
|
|
5108
|
+
case "regex_find_all":
|
|
5109
|
+
return `(${this.expr(fg("left", "value", "arg0")!)}.match(new RegExp(${this.expr(fg("right", "pattern", "arg1")!)}, 'g')) ?? [])`;
|
|
5110
|
+
case "regex_replace":
|
|
5111
|
+
return `${this.expr(f.get("value")!)}.replace(new RegExp(${this.expr(f.get("from")!)}), ${this.expr(f.get("to")!)})`;
|
|
5112
|
+
case "regex_replace_all":
|
|
5113
|
+
return `${this.expr(f.get("value")!)}.replace(new RegExp(${this.expr(f.get("from")!)}, 'g'), ${this.expr(f.get("to")!)})`;
|
|
5079
5114
|
case "string_replace_all": {
|
|
5080
5115
|
const v = f.get("value") ?? f.get("string");
|
|
5081
5116
|
const from = f.get("from") ?? f.get("pattern");
|
|
@@ -5427,6 +5462,11 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5427
5462
|
}
|
|
5428
5463
|
case "list_any": return `${this.expr(f.get("list")!)}.some(${this.expr(f.get("function") ?? f.get("callback") ?? f.get("value")!)})`;
|
|
5429
5464
|
case "list_all": return `${this.expr(f.get("list")!)}.every(${this.expr(f.get("function") ?? f.get("callback") ?? f.get("value")!)})`;
|
|
5465
|
+
// Dart's Iterable.firstWhere / JS Array.find. Declared in
|
|
5466
|
+
// dart/shared/lib/std_collections.dart and implemented by the Dart
|
|
5467
|
+
// engine, but never reachable from TS before (#489).
|
|
5468
|
+
case "list_find": return `${this.expr(f.get("list")!)}.find(${this.expr(f.get("function") ?? f.get("callback") ?? f.get("value")!)})`;
|
|
5469
|
+
case "list_flat_map": return `${this.expr(f.get("list")!)}.flatMap(${this.expr(f.get("function") ?? f.get("callback") ?? f.get("value")!)})`;
|
|
5430
5470
|
case "list_to_list": return `[...${this.expr(f.get("list")!)}]`;
|
|
5431
5471
|
case "list_foreach": return `${this.expr(f.get("list")!)}.forEach(${this.expr(f.get("function") ?? f.get("callback") ?? f.get("value")!)})`;
|
|
5432
5472
|
case "map_foreach": {
|
|
@@ -5758,6 +5798,10 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5758
5798
|
const subjectStr = this.expr(subjectExpr);
|
|
5759
5799
|
const caseExprs = casesField.literal?.listValue?.elements ?? [];
|
|
5760
5800
|
let defaultBody: Expression | undefined;
|
|
5801
|
+
// Tracked separately from `defaultBody`: a `default:` arm that carries no
|
|
5802
|
+
// body still CATCHES (it just evaluates to nothing), so it is not a
|
|
5803
|
+
// non-exhaustive switch. Only "no default arm at all" is.
|
|
5804
|
+
let hasDefault = false;
|
|
5761
5805
|
// Each branch carries its match condition, the pattern's bindings, the
|
|
5762
5806
|
// body expression and an optional `when` guard. The guard references the
|
|
5763
5807
|
// bound variables, so it must be evaluated where those bindings are in
|
|
@@ -5788,6 +5832,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5788
5832
|
// Check for is_default flag
|
|
5789
5833
|
if (isDefaultFlag) {
|
|
5790
5834
|
defaultBody = body;
|
|
5835
|
+
hasDefault = true;
|
|
5791
5836
|
continue;
|
|
5792
5837
|
}
|
|
5793
5838
|
if (!body) continue;
|
|
@@ -5799,6 +5844,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5799
5844
|
// refutable branch (the guard can still fall through).
|
|
5800
5845
|
if (result.condition === "true" && result.bindings.length === 0 && !guardField) {
|
|
5801
5846
|
defaultBody = body;
|
|
5847
|
+
hasDefault = true;
|
|
5802
5848
|
break;
|
|
5803
5849
|
}
|
|
5804
5850
|
branches.push({ cond: result.condition, bindings: result.bindings, body, guard: guardField });
|
|
@@ -5811,6 +5857,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5811
5857
|
const caseMatch = pattern ?? valueField;
|
|
5812
5858
|
if (!caseMatch) {
|
|
5813
5859
|
defaultBody = body;
|
|
5860
|
+
hasDefault = true;
|
|
5814
5861
|
continue;
|
|
5815
5862
|
}
|
|
5816
5863
|
const patText = patternLiteralText(caseMatch);
|
|
@@ -5826,13 +5873,41 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5826
5873
|
const cond = patternToTsCondition(patText, subjectStr);
|
|
5827
5874
|
if (cond === "true" && !guardField) {
|
|
5828
5875
|
defaultBody = body;
|
|
5876
|
+
hasDefault = true;
|
|
5829
5877
|
break;
|
|
5830
5878
|
}
|
|
5831
5879
|
// Check if the pattern introduces variable bindings.
|
|
5832
5880
|
const bindings = patternBindings(patText, subjectStr);
|
|
5833
5881
|
branches.push({ cond, bindings, body, guard: guardField });
|
|
5834
5882
|
}
|
|
5835
|
-
|
|
5883
|
+
// What the switch evaluates to when nothing matched.
|
|
5884
|
+
//
|
|
5885
|
+
// A defaultless switch EXPRESSION that matched nothing is non-exhaustive.
|
|
5886
|
+
// Dart's engine throws BallRuntimeError('Non-exhaustive switch expression')
|
|
5887
|
+
// (dart/engine/lib/engine_control_flow.dart), C# throws
|
|
5888
|
+
// BallRuntimeException with the same text (csharp/compiler/src/BaseCall.cs),
|
|
5889
|
+
// and Go/Rust throw it too (issue #467). TypeScript answered `undefined`
|
|
5890
|
+
// instead — a silent wrong value where every other target fails loud, and
|
|
5891
|
+
// exactly the silent-degradation shape CLAUDE.md's fail-loud rule forbids.
|
|
5892
|
+
//
|
|
5893
|
+
// Two guards keep this faithful, both mirroring the Rust port:
|
|
5894
|
+
// - only a switch EXPRESSION throws. A statement `switch` legally does
|
|
5895
|
+
// nothing when no case matches, so it still yields `undefined`.
|
|
5896
|
+
// - only "no `default` arm AT ALL" is non-exhaustive; a `default:` that
|
|
5897
|
+
// carries no body still catches.
|
|
5898
|
+
//
|
|
5899
|
+
// Engine-safety: the self-hosted engine's own oneof dispatchers
|
|
5900
|
+
// (`_evalExpression` on whichExpr, `_evalLiteral` on whichValue) are
|
|
5901
|
+
// defaultless switch expressions, but each carries an explicit `notSet`
|
|
5902
|
+
// arm, so this tail is unreachable there. Measured, not assumed —
|
|
5903
|
+
// engine_runtime.test.ts runs the whole conformance corpus through the
|
|
5904
|
+
// engine compiled by THIS compiler.
|
|
5905
|
+
const nonExhaustive = call.function === "switch_expr" && !hasDefault;
|
|
5906
|
+
const tail = defaultBody
|
|
5907
|
+
? this.expr(defaultBody)
|
|
5908
|
+
: nonExhaustive
|
|
5909
|
+
? "(() => { throw 'Non-exhaustive switch expression'; })()"
|
|
5910
|
+
: "undefined";
|
|
5836
5911
|
if (branches.length === 0) return tail;
|
|
5837
5912
|
let result = tail;
|
|
5838
5913
|
for (const { cond, bindings, body, guard } of [...branches].reverse()) {
|