@ball-lang/compiler 1.7.5 → 1.7.7
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 +66 -31
- package/dist/compiler.js.map +1 -1
- package/dist/preamble.d.ts.map +1 -1
- package/dist/preamble.js +56 -9
- package/dist/preamble.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler.ts +70 -27
- package/src/preamble.ts +56 -9
package/dist/preamble.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preamble.d.ts","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"preamble.d.ts","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,QA0gD/B,CAAC"}
|
package/dist/preamble.js
CHANGED
|
@@ -247,7 +247,11 @@ function __ball_to_string(v: any): string {
|
|
|
247
247
|
}
|
|
248
248
|
if (v instanceof Map) {
|
|
249
249
|
const parts: string[] = [];
|
|
250
|
-
|
|
250
|
+
// v.entries() as a method call would hit the Dart-property-style
|
|
251
|
+
// getter of the same name (installed further down in this file) and
|
|
252
|
+
// try to invoke its return value -- an array -- as a function.
|
|
253
|
+
// _nativeMapEntries is the real, un-shadowed method (issue #259).
|
|
254
|
+
for (const [k, val] of _nativeMapEntries.call(v)) {
|
|
251
255
|
parts.push(__ball_to_string(k) + ': ' + __ball_to_string(val));
|
|
252
256
|
}
|
|
253
257
|
return '{' + parts.join(', ') + '}';
|
|
@@ -719,6 +723,21 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
719
723
|
// ── Dart \u2192 JS method-name polyfills ────────────────────────────────
|
|
720
724
|
//
|
|
721
725
|
// Idempotent: guarded so multiple preamble inclusions don't double-install.
|
|
726
|
+
|
|
727
|
+
// Native Map.prototype.entries/keys/values, captured BEFORE the
|
|
728
|
+
// installBallPolyfills IIFE below shadows them with Dart-property-style
|
|
729
|
+
// getters of the same name. Top-level (not IIFE-scoped) so every internal
|
|
730
|
+
// call site that needs the REAL iterator method -- not the property-style
|
|
731
|
+
// getter -- can reach it: the getters themselves (which must call the
|
|
732
|
+
// original to avoid recursing into themselves), __ball_to_string's Map
|
|
733
|
+
// printer, the Map-like constructor copy sites, and the map_keys/values/
|
|
734
|
+
// entries base-function helpers (issue #259 -- calling .entries()/etc.
|
|
735
|
+
// as a METHOD on a real Map after the shadow is installed throws, since
|
|
736
|
+
// the getter's return value -- an array -- isn't itself callable).
|
|
737
|
+
const _nativeMapEntries = Map.prototype.entries;
|
|
738
|
+
const _nativeMapKeys = Map.prototype.keys;
|
|
739
|
+
const _nativeMapValues = Map.prototype.values;
|
|
740
|
+
|
|
722
741
|
(function installBallPolyfills() {
|
|
723
742
|
const mp: any = Map.prototype;
|
|
724
743
|
if (!mp.containsKey) mp.containsKey = function (k: any) { return this.has(k); };
|
|
@@ -731,7 +750,9 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
731
750
|
if (!mp.addAll) {
|
|
732
751
|
mp.addAll = function (other: any) {
|
|
733
752
|
if (other instanceof Map) {
|
|
734
|
-
|
|
753
|
+
// _nativeMapEntries, not other.entries() -- see __ball_to_string's
|
|
754
|
+
// Map printer above for why (issue #259).
|
|
755
|
+
for (const [k, v] of _nativeMapEntries.call(other)) this.set(k, v);
|
|
735
756
|
} else if (other && typeof other === 'object') {
|
|
736
757
|
for (const k of Object.keys(other)) this.set(k, other[k]);
|
|
737
758
|
}
|
|
@@ -908,7 +929,8 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
908
929
|
value: function (other: any) {
|
|
909
930
|
if (this instanceof Map) {
|
|
910
931
|
if (other instanceof Map) {
|
|
911
|
-
|
|
932
|
+
// _nativeMapEntries, not other.entries() (issue #259).
|
|
933
|
+
for (const [k, v] of _nativeMapEntries.call(other)) this.set(k, v);
|
|
912
934
|
} else if (other && typeof other === 'object') {
|
|
913
935
|
for (const k of Object.keys(other)) this.set(k, other[k]);
|
|
914
936
|
}
|
|
@@ -961,9 +983,9 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
961
983
|
// JS Map has them as METHODS (need parens). The compiled engine
|
|
962
984
|
// accesses map.entries as a getter. Shadow BOTH Map.prototype AND
|
|
963
985
|
// Object.prototype so Map and plain-object dispatch tables work.
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
986
|
+
// (_nativeMapEntries/_nativeMapKeys/_nativeMapValues are captured at
|
|
987
|
+
// top level above, not here, so other call sites outside this IIFE
|
|
988
|
+
// can reach them too -- issue #259.)
|
|
967
989
|
// Shadow Map.prototype.entries with a getter (Dart uses it as a getter).
|
|
968
990
|
Object.defineProperty(Map.prototype, 'entries', {
|
|
969
991
|
configurable: true, enumerable: false,
|
|
@@ -1087,7 +1109,10 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
1087
1109
|
// Map" check, exposed as top-level helpers so compileStdCall's emitted code
|
|
1088
1110
|
// can call them (#218).
|
|
1089
1111
|
function __ball_map_keys(m: any): any {
|
|
1090
|
-
|
|
1112
|
+
// _nativeMapKeys, not m.keys() -- m.keys() would hit the Dart-property-
|
|
1113
|
+
// style getter shadowing Map.prototype.keys and try to invoke its
|
|
1114
|
+
// return value (an array) as a function (issue #259).
|
|
1115
|
+
if (m instanceof Map) return [..._nativeMapKeys.call(m)];
|
|
1091
1116
|
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1092
1117
|
m instanceof BallDouble || m instanceof Set ||
|
|
1093
1118
|
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
@@ -1096,7 +1121,8 @@ function __ball_map_keys(m: any): any {
|
|
|
1096
1121
|
return Object.keys(m);
|
|
1097
1122
|
}
|
|
1098
1123
|
function __ball_map_values(m: any): any {
|
|
1099
|
-
|
|
1124
|
+
// _nativeMapValues, not m.values() (issue #259 -- see __ball_map_keys).
|
|
1125
|
+
if (m instanceof Map) return [..._nativeMapValues.call(m)];
|
|
1100
1126
|
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1101
1127
|
m instanceof BallDouble || m instanceof Set ||
|
|
1102
1128
|
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
@@ -1105,7 +1131,8 @@ function __ball_map_values(m: any): any {
|
|
|
1105
1131
|
return Object.values(m);
|
|
1106
1132
|
}
|
|
1107
1133
|
function __ball_map_entries(m: any): any {
|
|
1108
|
-
|
|
1134
|
+
// _nativeMapEntries, not m.entries() (issue #259 -- see __ball_map_keys).
|
|
1135
|
+
if (m instanceof Map) return [..._nativeMapEntries.call(m)].map(([k, v]) => ({ key: k, value: v }));
|
|
1109
1136
|
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1110
1137
|
m instanceof BallDouble || m instanceof Set ||
|
|
1111
1138
|
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
@@ -1114,6 +1141,26 @@ function __ball_map_entries(m: any): any {
|
|
|
1114
1141
|
return Object.entries(m).map(([k, v]) => ({ key: k, value: v }));
|
|
1115
1142
|
}
|
|
1116
1143
|
|
|
1144
|
+
// Shared guard for the REMAINING map_* base-function-call cases
|
|
1145
|
+
// (map_get/map_set/map_delete/map_merge/map_length/map_is_empty/
|
|
1146
|
+
// map_contains_key/map_contains_value/map_foreach) that used to route a
|
|
1147
|
+
// bare map[key], Object.keys/values(map), or key in map straight to the
|
|
1148
|
+
// receiver with no type check at all -- silently returning undefined,
|
|
1149
|
+
// no-opping, or checking array-index membership instead of throwing on a
|
|
1150
|
+
// non-Map (issue #55's silent-degradation class, same family as #218's
|
|
1151
|
+
// map_keys/map_values/map_entries). Returns the validated Map/plain-object
|
|
1152
|
+
// itself (not a boolean) so a call site can keep using it directly, e.g.
|
|
1153
|
+
// __ball_require_map(x, 'map_get')[key].
|
|
1154
|
+
function __ball_require_map(v: any, opName: string): any {
|
|
1155
|
+
if (v instanceof Map) return v;
|
|
1156
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v) ||
|
|
1157
|
+
v instanceof BallDouble || v instanceof Set ||
|
|
1158
|
+
v instanceof Number || v instanceof String || v instanceof Boolean) {
|
|
1159
|
+
throw new Error('type \'' + __ball_to_string(v) + '\' is not a Map (' + opName + ')');
|
|
1160
|
+
}
|
|
1161
|
+
return v;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1117
1164
|
// ── Protobuf Struct/Value compatibility ─────────────────────────
|
|
1118
1165
|
//
|
|
1119
1166
|
// Dart's protobuf runtime wraps google.protobuf.Struct as a class
|
package/dist/preamble.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preamble.js","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"preamble.js","sourceRoot":"","sources":["../src/preamble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0gD5C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ball-lang/compiler",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.7",
|
|
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
|
@@ -4570,7 +4570,14 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
4570
4570
|
case "null_aware_call": {
|
|
4571
4571
|
const target = f.get("target");
|
|
4572
4572
|
const method = f.get("method");
|
|
4573
|
-
|
|
4573
|
+
// A bare `/* ... */` comment used to stand in for the missing
|
|
4574
|
+
// field — context-dependent behavior (silently becomes `undefined`
|
|
4575
|
+
// in return position, a hard-to-diagnose SyntaxError mid-expression)
|
|
4576
|
+
// instead of a clear compile-time error naming the malformed call
|
|
4577
|
+
// (#257).
|
|
4578
|
+
if (!target || !method) {
|
|
4579
|
+
throw new Error("TS compiler: null_aware_call requires a \"target\" and a \"method\" field");
|
|
4580
|
+
}
|
|
4574
4581
|
const methodName = method.literal?.stringValue ?? "";
|
|
4575
4582
|
const inputFields = call.input?.messageCreation?.fields ?? [];
|
|
4576
4583
|
const otherArgs = inputFields
|
|
@@ -4582,13 +4589,17 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
4582
4589
|
case "null_aware_index": {
|
|
4583
4590
|
const self_ = f.get("self") ?? f.get("target");
|
|
4584
4591
|
const idx = f.get("index") ?? f.get("key");
|
|
4585
|
-
if (!self_ || !idx)
|
|
4592
|
+
if (!self_ || !idx) {
|
|
4593
|
+
throw new Error("TS compiler: null_aware_index requires a \"self\"/\"target\" and an \"index\"/\"key\" field");
|
|
4594
|
+
}
|
|
4586
4595
|
return `${this.expr(self_)}[${this.expr(idx)}]`;
|
|
4587
4596
|
}
|
|
4588
4597
|
case "null_aware_access": {
|
|
4589
4598
|
const target = f.get("target");
|
|
4590
4599
|
const fieldE = f.get("field");
|
|
4591
|
-
if (!target || !fieldE)
|
|
4600
|
+
if (!target || !fieldE) {
|
|
4601
|
+
throw new Error("TS compiler: null_aware_access requires a \"target\" and a \"field\" field");
|
|
4602
|
+
}
|
|
4592
4603
|
return `${this.expr(target)}?.${fieldE.literal?.stringValue ?? ""}`;
|
|
4593
4604
|
}
|
|
4594
4605
|
case "typed_list": {
|
|
@@ -4607,13 +4618,19 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
4607
4618
|
if (entryExprs.length === 0) return "new Map()";
|
|
4608
4619
|
const pairs: string[] = [];
|
|
4609
4620
|
for (const e of entryExprs) {
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
if (k && v) pairs.push(`[${this.expr(k)}, ${this.expr(v)}]`);
|
|
4621
|
+
// A malformed entry (not a messageCreation, or missing key/value)
|
|
4622
|
+
// used to be silently dropped from the resulting Map instead of
|
|
4623
|
+
// surfaced — fail loud on the malformed IR instead (#257).
|
|
4624
|
+
if (!e.messageCreation) {
|
|
4625
|
+
throw new Error("TS compiler: typed_map entry is not a key/value messageCreation");
|
|
4616
4626
|
}
|
|
4627
|
+
const mFields = e.messageCreation.fields ?? [];
|
|
4628
|
+
const k = mFields.find((fd) => fd.name === "key")?.value;
|
|
4629
|
+
const v = mFields.find((fd) => fd.name === "value")?.value;
|
|
4630
|
+
if (!k || !v) {
|
|
4631
|
+
throw new Error("TS compiler: typed_map entry is missing \"key\" or \"value\"");
|
|
4632
|
+
}
|
|
4633
|
+
pairs.push(`[${this.expr(k)}, ${this.expr(v)}]`);
|
|
4617
4634
|
}
|
|
4618
4635
|
return `new Map([${pairs.join(", ")}])`;
|
|
4619
4636
|
}
|
|
@@ -4943,20 +4960,26 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
4943
4960
|
case "map_get": {
|
|
4944
4961
|
const map = f.get("map");
|
|
4945
4962
|
const key = f.get("key");
|
|
4946
|
-
|
|
4963
|
+
// __ball_require_map fails loud on a non-Map instead of silently
|
|
4964
|
+
// returning undefined via bare bracket access (#257, same family
|
|
4965
|
+
// as #218's map_keys/map_values/map_entries).
|
|
4966
|
+
if (map && key) return `__ball_require_map(${this.expr(map)}, 'map_get')[${this.expr(key)}]`;
|
|
4947
4967
|
return "undefined";
|
|
4948
4968
|
}
|
|
4949
4969
|
case "map_set": {
|
|
4950
4970
|
const map = f.get("map");
|
|
4951
4971
|
const key = f.get("key");
|
|
4952
4972
|
const value = f.get("value");
|
|
4953
|
-
if (map && key && value) return `(${this.expr(map)}[${this.expr(key)}] = ${this.expr(value)})`;
|
|
4973
|
+
if (map && key && value) return `(__ball_require_map(${this.expr(map)}, 'map_set')[${this.expr(key)}] = ${this.expr(value)})`;
|
|
4954
4974
|
return "undefined";
|
|
4955
4975
|
}
|
|
4956
4976
|
case "map_contains_key": {
|
|
4957
4977
|
const map = f.get("map");
|
|
4958
4978
|
const key = f.get("key");
|
|
4959
|
-
|
|
4979
|
+
// `in` throws for a primitive receiver already, but not for a List
|
|
4980
|
+
// (checks index membership instead of throwing "not a map") — the
|
|
4981
|
+
// require-map guard closes that gap (#257).
|
|
4982
|
+
if (map && key) return `(${this.expr(key)} in __ball_require_map(${this.expr(map)}, 'map_contains_key'))`;
|
|
4960
4983
|
return "false";
|
|
4961
4984
|
}
|
|
4962
4985
|
case "map_keys": {
|
|
@@ -4981,22 +5004,24 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
4981
5004
|
}
|
|
4982
5005
|
case "map_length": {
|
|
4983
5006
|
const map = f.get("map") ?? f.get("value");
|
|
4984
|
-
return map ? `Object.keys(${this.expr(map)}).length` : "0";
|
|
5007
|
+
return map ? `Object.keys(__ball_require_map(${this.expr(map)}, 'map_length')).length` : "0";
|
|
4985
5008
|
}
|
|
4986
5009
|
case "map_is_empty": {
|
|
4987
5010
|
const map = f.get("map") ?? f.get("value");
|
|
4988
|
-
return map ? `(Object.keys(${this.expr(map)}).length === 0)` : "true";
|
|
5011
|
+
return map ? `(Object.keys(__ball_require_map(${this.expr(map)}, 'map_is_empty')).length === 0)` : "true";
|
|
4989
5012
|
}
|
|
4990
5013
|
case "map_delete": case "map_remove": {
|
|
4991
5014
|
const map = f.get("map");
|
|
4992
5015
|
const key = f.get("key");
|
|
4993
|
-
if (map && key) return `(() => { const __m = ${this.expr(map)}; const __k = ${this.expr(key)}; const __v = __m[__k]; delete __m[__k]; return __v; })()`;
|
|
5016
|
+
if (map && key) return `(() => { const __m = __ball_require_map(${this.expr(map)}, 'map_delete'); const __k = ${this.expr(key)}; const __v = __m[__k]; delete __m[__k]; return __v; })()`;
|
|
4994
5017
|
return "undefined";
|
|
4995
5018
|
}
|
|
4996
5019
|
case "map_merge": {
|
|
4997
5020
|
const l = f.get("left") ?? f.get("map");
|
|
4998
5021
|
const r = f.get("right") ?? f.get("other");
|
|
4999
|
-
|
|
5022
|
+
// Spreading a non-object silently produces {} (or a partial merge)
|
|
5023
|
+
// instead of throwing — same class as the other map_* guards (#257).
|
|
5024
|
+
if (l && r) return `{...__ball_require_map(${this.expr(l)}, 'map_merge'), ...__ball_require_map(${this.expr(r)}, 'map_merge')}`;
|
|
5000
5025
|
return "{}";
|
|
5001
5026
|
}
|
|
5002
5027
|
case "map_from_entries": {
|
|
@@ -5011,13 +5036,20 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5011
5036
|
if (list) return `${this.expr(list)}.join('')`;
|
|
5012
5037
|
return "''";
|
|
5013
5038
|
}
|
|
5039
|
+
// Neither encoder ever emits these as a direct `std.<fn>` base-
|
|
5040
|
+
// function call — a Set method call (mySet.union(other), etc.)
|
|
5041
|
+
// routes through compileCall's generic "self"-field method dispatch
|
|
5042
|
+
// onto native JS Set.prototype methods instead, so this case is
|
|
5043
|
+
// never actually reached. The old fallback compiled it to a call on
|
|
5044
|
+
// a nonexistent bare identifier (a confusing runtime ReferenceError
|
|
5045
|
+
// if it WERE ever hit); fail loud at compile time instead, matching
|
|
5046
|
+
// the policy above (#257).
|
|
5014
5047
|
case "set_add": case "set_remove": case "set_contains":
|
|
5015
5048
|
case "set_union": case "set_intersection": case "set_difference":
|
|
5016
|
-
case "set_length": case "set_is_empty": case "set_to_list":
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
}
|
|
5049
|
+
case "set_length": case "set_is_empty": case "set_to_list":
|
|
5050
|
+
throw new Error(
|
|
5051
|
+
`TS compiler: std.${fn} is not implemented (compileStdCall) — Set method calls should route through native JS Set methods, not this base function`,
|
|
5052
|
+
);
|
|
5021
5053
|
// I/O
|
|
5022
5054
|
case "print_error": {
|
|
5023
5055
|
const msg = f.get("message") ?? f.get("value");
|
|
@@ -5126,7 +5158,10 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5126
5158
|
// match first and any duplicate entry here would be genuinely
|
|
5127
5159
|
// unreachable dead code. Removed (#62 Phase-2c); only cases with
|
|
5128
5160
|
// no outer-switch equivalent belong in this fallback.
|
|
5129
|
-
|
|
5161
|
+
// Same silent-degradation class as map_keys/map_values/map_entries
|
|
5162
|
+
// (#218) — the sibling case that fix missed, since it lives in
|
|
5163
|
+
// this SECOND nested switch under a different function name (#257).
|
|
5164
|
+
case "map_contains_value": return `Object.values(__ball_require_map(${this.expr(f.get("map")!)}, 'map_contains_value')).includes(${this.expr(f.get("value")!)})`;
|
|
5130
5165
|
case "list_insert": return `(${this.expr(f.get("list")!)}.splice(${this.expr(f.get("index")!)}, 0, ${this.expr(f.get("value")!)}), ${this.expr(f.get("list")!)})`;
|
|
5131
5166
|
case "list_remove_at": return `${this.expr(f.get("list")!)}.splice(${this.expr(f.get("index")!)}, 1)[0]`;
|
|
5132
5167
|
case "list_clear": return `(${this.expr(f.get("list")!)}.length = 0, ${this.expr(f.get("list")!)})`;
|
|
@@ -5154,7 +5189,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5154
5189
|
case "map_foreach": {
|
|
5155
5190
|
const map = this.expr(f.get("map") ?? f.get("value")!);
|
|
5156
5191
|
const cb = this.expr(f.get("function") ?? f.get("callback") ?? f.get("value")!);
|
|
5157
|
-
return `Object.entries(${map}).forEach(([k, v]) => ${cb}(k, v))`;
|
|
5192
|
+
return `Object.entries(__ball_require_map(${map}, 'map_foreach')).forEach(([k, v]) => ${cb}(k, v))`;
|
|
5158
5193
|
}
|
|
5159
5194
|
case "list_reversed": return `[...${this.expr(f.get("list")!)}].reverse()`;
|
|
5160
5195
|
case "compare_to": {
|
|
@@ -5225,10 +5260,18 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5225
5260
|
);
|
|
5226
5261
|
return `(() => { const __r: any[] = []; ${body} return __r; })()`;
|
|
5227
5262
|
}
|
|
5228
|
-
default:
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5263
|
+
default:
|
|
5264
|
+
// Fail loud (repo CLAUDE.md): never emit a bare/undefined
|
|
5265
|
+
// identifier for an unimplemented std function — mirrors
|
|
5266
|
+
// compileMemoryCall's rule above. The old
|
|
5267
|
+
// `/* std.${fn} */ ${sanitize(fn)}(${args})` fallback compiled
|
|
5268
|
+
// to a call on a nonexistent identifier, deferring the failure
|
|
5269
|
+
// to a confusing runtime ReferenceError instead of a clear
|
|
5270
|
+
// compile-time error naming the actual unimplemented function
|
|
5271
|
+
// (#257).
|
|
5272
|
+
throw new Error(
|
|
5273
|
+
`TS compiler: std.${fn} is not implemented (compileStdCall)`,
|
|
5274
|
+
);
|
|
5232
5275
|
}
|
|
5233
5276
|
}
|
|
5234
5277
|
}
|
package/src/preamble.ts
CHANGED
|
@@ -247,7 +247,11 @@ function __ball_to_string(v: any): string {
|
|
|
247
247
|
}
|
|
248
248
|
if (v instanceof Map) {
|
|
249
249
|
const parts: string[] = [];
|
|
250
|
-
|
|
250
|
+
// v.entries() as a method call would hit the Dart-property-style
|
|
251
|
+
// getter of the same name (installed further down in this file) and
|
|
252
|
+
// try to invoke its return value -- an array -- as a function.
|
|
253
|
+
// _nativeMapEntries is the real, un-shadowed method (issue #259).
|
|
254
|
+
for (const [k, val] of _nativeMapEntries.call(v)) {
|
|
251
255
|
parts.push(__ball_to_string(k) + ': ' + __ball_to_string(val));
|
|
252
256
|
}
|
|
253
257
|
return '{' + parts.join(', ') + '}';
|
|
@@ -719,6 +723,21 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
719
723
|
// ── Dart \u2192 JS method-name polyfills ────────────────────────────────
|
|
720
724
|
//
|
|
721
725
|
// Idempotent: guarded so multiple preamble inclusions don't double-install.
|
|
726
|
+
|
|
727
|
+
// Native Map.prototype.entries/keys/values, captured BEFORE the
|
|
728
|
+
// installBallPolyfills IIFE below shadows them with Dart-property-style
|
|
729
|
+
// getters of the same name. Top-level (not IIFE-scoped) so every internal
|
|
730
|
+
// call site that needs the REAL iterator method -- not the property-style
|
|
731
|
+
// getter -- can reach it: the getters themselves (which must call the
|
|
732
|
+
// original to avoid recursing into themselves), __ball_to_string's Map
|
|
733
|
+
// printer, the Map-like constructor copy sites, and the map_keys/values/
|
|
734
|
+
// entries base-function helpers (issue #259 -- calling .entries()/etc.
|
|
735
|
+
// as a METHOD on a real Map after the shadow is installed throws, since
|
|
736
|
+
// the getter's return value -- an array -- isn't itself callable).
|
|
737
|
+
const _nativeMapEntries = Map.prototype.entries;
|
|
738
|
+
const _nativeMapKeys = Map.prototype.keys;
|
|
739
|
+
const _nativeMapValues = Map.prototype.values;
|
|
740
|
+
|
|
722
741
|
(function installBallPolyfills() {
|
|
723
742
|
const mp: any = Map.prototype;
|
|
724
743
|
if (!mp.containsKey) mp.containsKey = function (k: any) { return this.has(k); };
|
|
@@ -731,7 +750,9 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
731
750
|
if (!mp.addAll) {
|
|
732
751
|
mp.addAll = function (other: any) {
|
|
733
752
|
if (other instanceof Map) {
|
|
734
|
-
|
|
753
|
+
// _nativeMapEntries, not other.entries() -- see __ball_to_string's
|
|
754
|
+
// Map printer above for why (issue #259).
|
|
755
|
+
for (const [k, v] of _nativeMapEntries.call(other)) this.set(k, v);
|
|
735
756
|
} else if (other && typeof other === 'object') {
|
|
736
757
|
for (const k of Object.keys(other)) this.set(k, other[k]);
|
|
737
758
|
}
|
|
@@ -908,7 +929,8 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
908
929
|
value: function (other: any) {
|
|
909
930
|
if (this instanceof Map) {
|
|
910
931
|
if (other instanceof Map) {
|
|
911
|
-
|
|
932
|
+
// _nativeMapEntries, not other.entries() (issue #259).
|
|
933
|
+
for (const [k, v] of _nativeMapEntries.call(other)) this.set(k, v);
|
|
912
934
|
} else if (other && typeof other === 'object') {
|
|
913
935
|
for (const k of Object.keys(other)) this.set(k, other[k]);
|
|
914
936
|
}
|
|
@@ -961,9 +983,9 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
961
983
|
// JS Map has them as METHODS (need parens). The compiled engine
|
|
962
984
|
// accesses map.entries as a getter. Shadow BOTH Map.prototype AND
|
|
963
985
|
// Object.prototype so Map and plain-object dispatch tables work.
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
986
|
+
// (_nativeMapEntries/_nativeMapKeys/_nativeMapValues are captured at
|
|
987
|
+
// top level above, not here, so other call sites outside this IIFE
|
|
988
|
+
// can reach them too -- issue #259.)
|
|
967
989
|
// Shadow Map.prototype.entries with a getter (Dart uses it as a getter).
|
|
968
990
|
Object.defineProperty(Map.prototype, 'entries', {
|
|
969
991
|
configurable: true, enumerable: false,
|
|
@@ -1087,7 +1109,10 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
1087
1109
|
// Map" check, exposed as top-level helpers so compileStdCall's emitted code
|
|
1088
1110
|
// can call them (#218).
|
|
1089
1111
|
function __ball_map_keys(m: any): any {
|
|
1090
|
-
|
|
1112
|
+
// _nativeMapKeys, not m.keys() -- m.keys() would hit the Dart-property-
|
|
1113
|
+
// style getter shadowing Map.prototype.keys and try to invoke its
|
|
1114
|
+
// return value (an array) as a function (issue #259).
|
|
1115
|
+
if (m instanceof Map) return [..._nativeMapKeys.call(m)];
|
|
1091
1116
|
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1092
1117
|
m instanceof BallDouble || m instanceof Set ||
|
|
1093
1118
|
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
@@ -1096,7 +1121,8 @@ function __ball_map_keys(m: any): any {
|
|
|
1096
1121
|
return Object.keys(m);
|
|
1097
1122
|
}
|
|
1098
1123
|
function __ball_map_values(m: any): any {
|
|
1099
|
-
|
|
1124
|
+
// _nativeMapValues, not m.values() (issue #259 -- see __ball_map_keys).
|
|
1125
|
+
if (m instanceof Map) return [..._nativeMapValues.call(m)];
|
|
1100
1126
|
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1101
1127
|
m instanceof BallDouble || m instanceof Set ||
|
|
1102
1128
|
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
@@ -1105,7 +1131,8 @@ function __ball_map_values(m: any): any {
|
|
|
1105
1131
|
return Object.values(m);
|
|
1106
1132
|
}
|
|
1107
1133
|
function __ball_map_entries(m: any): any {
|
|
1108
|
-
|
|
1134
|
+
// _nativeMapEntries, not m.entries() (issue #259 -- see __ball_map_keys).
|
|
1135
|
+
if (m instanceof Map) return [..._nativeMapEntries.call(m)].map(([k, v]) => ({ key: k, value: v }));
|
|
1109
1136
|
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1110
1137
|
m instanceof BallDouble || m instanceof Set ||
|
|
1111
1138
|
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
@@ -1114,6 +1141,26 @@ function __ball_map_entries(m: any): any {
|
|
|
1114
1141
|
return Object.entries(m).map(([k, v]) => ({ key: k, value: v }));
|
|
1115
1142
|
}
|
|
1116
1143
|
|
|
1144
|
+
// Shared guard for the REMAINING map_* base-function-call cases
|
|
1145
|
+
// (map_get/map_set/map_delete/map_merge/map_length/map_is_empty/
|
|
1146
|
+
// map_contains_key/map_contains_value/map_foreach) that used to route a
|
|
1147
|
+
// bare map[key], Object.keys/values(map), or key in map straight to the
|
|
1148
|
+
// receiver with no type check at all -- silently returning undefined,
|
|
1149
|
+
// no-opping, or checking array-index membership instead of throwing on a
|
|
1150
|
+
// non-Map (issue #55's silent-degradation class, same family as #218's
|
|
1151
|
+
// map_keys/map_values/map_entries). Returns the validated Map/plain-object
|
|
1152
|
+
// itself (not a boolean) so a call site can keep using it directly, e.g.
|
|
1153
|
+
// __ball_require_map(x, 'map_get')[key].
|
|
1154
|
+
function __ball_require_map(v: any, opName: string): any {
|
|
1155
|
+
if (v instanceof Map) return v;
|
|
1156
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v) ||
|
|
1157
|
+
v instanceof BallDouble || v instanceof Set ||
|
|
1158
|
+
v instanceof Number || v instanceof String || v instanceof Boolean) {
|
|
1159
|
+
throw new Error('type \'' + __ball_to_string(v) + '\' is not a Map (' + opName + ')');
|
|
1160
|
+
}
|
|
1161
|
+
return v;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1117
1164
|
// ── Protobuf Struct/Value compatibility ─────────────────────────
|
|
1118
1165
|
//
|
|
1119
1166
|
// Dart's protobuf runtime wraps google.protobuf.Struct as a class
|