@ball-lang/compiler 1.7.4 → 1.7.6
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 +77 -34
- package/dist/compiler.js.map +1 -1
- package/dist/preamble.d.ts.map +1 -1
- package/dist/preamble.js +66 -8
- package/dist/preamble.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler.ts +81 -30
- package/src/preamble.ts +66 -8
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,QA++C/B,CAAC"}
|
package/dist/preamble.js
CHANGED
|
@@ -994,14 +994,11 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
994
994
|
},
|
|
995
995
|
});
|
|
996
996
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
// .keys/.values on a non-Map must FAIL LOUD (throw a catchable error), not
|
|
1003
|
-
// silently return [] — the silent-degradation class of bug that hid issue
|
|
1004
|
-
// #55 (mirrors the fix already applied to the Dart/C++ compilers).
|
|
997
|
+
// .entries/.keys/.values on a non-Map must FAIL LOUD (throw a catchable
|
|
998
|
+
// error), not silently return [] — the silent-degradation class of bug
|
|
999
|
+
// that hid issue #55 (mirrors the fix already applied to the Dart/C++
|
|
1000
|
+
// compilers). .entries used to be the odd one out here, silently
|
|
1001
|
+
// returning [] instead of throwing — same bug family as #218.
|
|
1005
1002
|
//
|
|
1006
1003
|
// A getter installed on Object.prototype is invoked in "sloppy" (non-strict)
|
|
1007
1004
|
// script contexts with this auto-boxed to a Number/String/Boolean WRAPPER
|
|
@@ -1014,6 +1011,13 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
1014
1011
|
typeof v === 'object' && v !== null && !Array.isArray(v) &&
|
|
1015
1012
|
!(v instanceof BallDouble) && !(v instanceof Set) &&
|
|
1016
1013
|
!(v instanceof Number) && !(v instanceof String) && !(v instanceof Boolean);
|
|
1014
|
+
defDartGetter('entries', function (this: any) {
|
|
1015
|
+
if (this instanceof Map) return [..._nativeMapEntries.call(this)].map(([k, v]: any) => ({ key: k, value: v }));
|
|
1016
|
+
if (!__isGenuineMap(this)) {
|
|
1017
|
+
throw new Error('type \'' + __ball_to_string(this) + '\' has no .entries getter (not a Map)');
|
|
1018
|
+
}
|
|
1019
|
+
return Object.entries(this).map(([k, v]: any) => ({ key: k, value: v }));
|
|
1020
|
+
});
|
|
1017
1021
|
defDartGetter('keys', function (this: any) {
|
|
1018
1022
|
if (this instanceof Map) return [..._nativeMapKeys.call(this)];
|
|
1019
1023
|
if (!__isGenuineMap(this)) {
|
|
@@ -1076,6 +1080,60 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
1076
1080
|
});
|
|
1077
1081
|
})();
|
|
1078
1082
|
|
|
1083
|
+
// std.map_keys/std.map_values/std.map_entries (the base-function-call form,
|
|
1084
|
+
// as opposed to the .keys/.values/.entries DART-GETTER-STYLE property
|
|
1085
|
+
// access the defDartGetter block above already guards) must ALSO fail loud
|
|
1086
|
+
// on a non-Map receiver instead of silently returning [] — same "genuine
|
|
1087
|
+
// Map" check, exposed as top-level helpers so compileStdCall's emitted code
|
|
1088
|
+
// can call them (#218).
|
|
1089
|
+
function __ball_map_keys(m: any): any {
|
|
1090
|
+
if (m instanceof Map) return [...m.keys()];
|
|
1091
|
+
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1092
|
+
m instanceof BallDouble || m instanceof Set ||
|
|
1093
|
+
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
1094
|
+
throw new Error('type \'' + __ball_to_string(m) + '\' has no .keys getter (not a Map)');
|
|
1095
|
+
}
|
|
1096
|
+
return Object.keys(m);
|
|
1097
|
+
}
|
|
1098
|
+
function __ball_map_values(m: any): any {
|
|
1099
|
+
if (m instanceof Map) return [...m.values()];
|
|
1100
|
+
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1101
|
+
m instanceof BallDouble || m instanceof Set ||
|
|
1102
|
+
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
1103
|
+
throw new Error('type \'' + __ball_to_string(m) + '\' has no .values getter (not a Map)');
|
|
1104
|
+
}
|
|
1105
|
+
return Object.values(m);
|
|
1106
|
+
}
|
|
1107
|
+
function __ball_map_entries(m: any): any {
|
|
1108
|
+
if (m instanceof Map) return [...m.entries()].map(([k, v]) => ({ key: k, value: v }));
|
|
1109
|
+
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1110
|
+
m instanceof BallDouble || m instanceof Set ||
|
|
1111
|
+
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
1112
|
+
throw new Error('type \'' + __ball_to_string(m) + '\' has no .entries getter (not a Map)');
|
|
1113
|
+
}
|
|
1114
|
+
return Object.entries(m).map(([k, v]) => ({ key: k, value: v }));
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// Shared guard for the REMAINING map_* base-function-call cases
|
|
1118
|
+
// (map_get/map_set/map_delete/map_merge/map_length/map_is_empty/
|
|
1119
|
+
// map_contains_key/map_contains_value/map_foreach) that used to route a
|
|
1120
|
+
// bare map[key], Object.keys/values(map), or key in map straight to the
|
|
1121
|
+
// receiver with no type check at all -- silently returning undefined,
|
|
1122
|
+
// no-opping, or checking array-index membership instead of throwing on a
|
|
1123
|
+
// non-Map (issue #55's silent-degradation class, same family as #218's
|
|
1124
|
+
// map_keys/map_values/map_entries). Returns the validated Map/plain-object
|
|
1125
|
+
// itself (not a boolean) so a call site can keep using it directly, e.g.
|
|
1126
|
+
// __ball_require_map(x, 'map_get')[key].
|
|
1127
|
+
function __ball_require_map(v: any, opName: string): any {
|
|
1128
|
+
if (v instanceof Map) return v;
|
|
1129
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v) ||
|
|
1130
|
+
v instanceof BallDouble || v instanceof Set ||
|
|
1131
|
+
v instanceof Number || v instanceof String || v instanceof Boolean) {
|
|
1132
|
+
throw new Error('type \'' + __ball_to_string(v) + '\' is not a Map (' + opName + ')');
|
|
1133
|
+
}
|
|
1134
|
+
return v;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1079
1137
|
// ── Protobuf Struct/Value compatibility ─────────────────────────
|
|
1080
1138
|
//
|
|
1081
1139
|
// 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA++C5C,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.6",
|
|
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,52 +4960,68 @@ 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": {
|
|
4963
4986
|
const map = f.get("map") ?? f.get("value");
|
|
4964
|
-
|
|
4987
|
+
// Fail loud on a non-Map receiver instead of silently returning [] —
|
|
4988
|
+
// that silent degradation is the class of bug that hid issue #55
|
|
4989
|
+
// (mirrors the .keys DART-GETTER guard already installed in
|
|
4990
|
+
// preamble.ts's defDartGetter block, and the engine/C++-compiler
|
|
4991
|
+
// guards for the same base function, #218).
|
|
4992
|
+
return map ? `__ball_map_keys(${this.expr(map)})` : "[]";
|
|
4965
4993
|
}
|
|
4966
4994
|
case "map_values": {
|
|
4967
4995
|
const map = f.get("map") ?? f.get("value");
|
|
4968
|
-
return map ? `
|
|
4996
|
+
return map ? `__ball_map_values(${this.expr(map)})` : "[]";
|
|
4969
4997
|
}
|
|
4970
4998
|
case "map_entries": {
|
|
4971
4999
|
const map = f.get("map") ?? f.get("value");
|
|
4972
|
-
|
|
5000
|
+
// Same silent-degradation class as map_keys/map_values (#218): a
|
|
5001
|
+
// bare Object.entries(...) on a non-Map silently returns garbage
|
|
5002
|
+
// instead of failing loud.
|
|
5003
|
+
return map ? `__ball_map_entries(${this.expr(map)})` : "[]";
|
|
4973
5004
|
}
|
|
4974
5005
|
case "map_length": {
|
|
4975
5006
|
const map = f.get("map") ?? f.get("value");
|
|
4976
|
-
return map ? `Object.keys(${this.expr(map)}).length` : "0";
|
|
5007
|
+
return map ? `Object.keys(__ball_require_map(${this.expr(map)}, 'map_length')).length` : "0";
|
|
4977
5008
|
}
|
|
4978
5009
|
case "map_is_empty": {
|
|
4979
5010
|
const map = f.get("map") ?? f.get("value");
|
|
4980
|
-
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";
|
|
4981
5012
|
}
|
|
4982
5013
|
case "map_delete": case "map_remove": {
|
|
4983
5014
|
const map = f.get("map");
|
|
4984
5015
|
const key = f.get("key");
|
|
4985
|
-
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; })()`;
|
|
4986
5017
|
return "undefined";
|
|
4987
5018
|
}
|
|
4988
5019
|
case "map_merge": {
|
|
4989
5020
|
const l = f.get("left") ?? f.get("map");
|
|
4990
5021
|
const r = f.get("right") ?? f.get("other");
|
|
4991
|
-
|
|
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')}`;
|
|
4992
5025
|
return "{}";
|
|
4993
5026
|
}
|
|
4994
5027
|
case "map_from_entries": {
|
|
@@ -5003,13 +5036,20 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5003
5036
|
if (list) return `${this.expr(list)}.join('')`;
|
|
5004
5037
|
return "''";
|
|
5005
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).
|
|
5006
5047
|
case "set_add": case "set_remove": case "set_contains":
|
|
5007
5048
|
case "set_union": case "set_intersection": case "set_difference":
|
|
5008
|
-
case "set_length": case "set_is_empty": case "set_to_list":
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
}
|
|
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
|
+
);
|
|
5013
5053
|
// I/O
|
|
5014
5054
|
case "print_error": {
|
|
5015
5055
|
const msg = f.get("message") ?? f.get("value");
|
|
@@ -5118,7 +5158,10 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5118
5158
|
// match first and any duplicate entry here would be genuinely
|
|
5119
5159
|
// unreachable dead code. Removed (#62 Phase-2c); only cases with
|
|
5120
5160
|
// no outer-switch equivalent belong in this fallback.
|
|
5121
|
-
|
|
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")!)})`;
|
|
5122
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")!)})`;
|
|
5123
5166
|
case "list_remove_at": return `${this.expr(f.get("list")!)}.splice(${this.expr(f.get("index")!)}, 1)[0]`;
|
|
5124
5167
|
case "list_clear": return `(${this.expr(f.get("list")!)}.length = 0, ${this.expr(f.get("list")!)})`;
|
|
@@ -5146,7 +5189,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5146
5189
|
case "map_foreach": {
|
|
5147
5190
|
const map = this.expr(f.get("map") ?? f.get("value")!);
|
|
5148
5191
|
const cb = this.expr(f.get("function") ?? f.get("callback") ?? f.get("value")!);
|
|
5149
|
-
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))`;
|
|
5150
5193
|
}
|
|
5151
5194
|
case "list_reversed": return `[...${this.expr(f.get("list")!)}].reverse()`;
|
|
5152
5195
|
case "compare_to": {
|
|
@@ -5217,10 +5260,18 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5217
5260
|
);
|
|
5218
5261
|
return `(() => { const __r: any[] = []; ${body} return __r; })()`;
|
|
5219
5262
|
}
|
|
5220
|
-
default:
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
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
|
+
);
|
|
5224
5275
|
}
|
|
5225
5276
|
}
|
|
5226
5277
|
}
|
package/src/preamble.ts
CHANGED
|
@@ -994,14 +994,11 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
994
994
|
},
|
|
995
995
|
});
|
|
996
996
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
// .keys/.values on a non-Map must FAIL LOUD (throw a catchable error), not
|
|
1003
|
-
// silently return [] — the silent-degradation class of bug that hid issue
|
|
1004
|
-
// #55 (mirrors the fix already applied to the Dart/C++ compilers).
|
|
997
|
+
// .entries/.keys/.values on a non-Map must FAIL LOUD (throw a catchable
|
|
998
|
+
// error), not silently return [] — the silent-degradation class of bug
|
|
999
|
+
// that hid issue #55 (mirrors the fix already applied to the Dart/C++
|
|
1000
|
+
// compilers). .entries used to be the odd one out here, silently
|
|
1001
|
+
// returning [] instead of throwing — same bug family as #218.
|
|
1005
1002
|
//
|
|
1006
1003
|
// A getter installed on Object.prototype is invoked in "sloppy" (non-strict)
|
|
1007
1004
|
// script contexts with this auto-boxed to a Number/String/Boolean WRAPPER
|
|
@@ -1014,6 +1011,13 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
1014
1011
|
typeof v === 'object' && v !== null && !Array.isArray(v) &&
|
|
1015
1012
|
!(v instanceof BallDouble) && !(v instanceof Set) &&
|
|
1016
1013
|
!(v instanceof Number) && !(v instanceof String) && !(v instanceof Boolean);
|
|
1014
|
+
defDartGetter('entries', function (this: any) {
|
|
1015
|
+
if (this instanceof Map) return [..._nativeMapEntries.call(this)].map(([k, v]: any) => ({ key: k, value: v }));
|
|
1016
|
+
if (!__isGenuineMap(this)) {
|
|
1017
|
+
throw new Error('type \'' + __ball_to_string(this) + '\' has no .entries getter (not a Map)');
|
|
1018
|
+
}
|
|
1019
|
+
return Object.entries(this).map(([k, v]: any) => ({ key: k, value: v }));
|
|
1020
|
+
});
|
|
1017
1021
|
defDartGetter('keys', function (this: any) {
|
|
1018
1022
|
if (this instanceof Map) return [..._nativeMapKeys.call(this)];
|
|
1019
1023
|
if (!__isGenuineMap(this)) {
|
|
@@ -1076,6 +1080,60 @@ function __ball_cascade(target: any, ops: any[]): any {
|
|
|
1076
1080
|
});
|
|
1077
1081
|
})();
|
|
1078
1082
|
|
|
1083
|
+
// std.map_keys/std.map_values/std.map_entries (the base-function-call form,
|
|
1084
|
+
// as opposed to the .keys/.values/.entries DART-GETTER-STYLE property
|
|
1085
|
+
// access the defDartGetter block above already guards) must ALSO fail loud
|
|
1086
|
+
// on a non-Map receiver instead of silently returning [] — same "genuine
|
|
1087
|
+
// Map" check, exposed as top-level helpers so compileStdCall's emitted code
|
|
1088
|
+
// can call them (#218).
|
|
1089
|
+
function __ball_map_keys(m: any): any {
|
|
1090
|
+
if (m instanceof Map) return [...m.keys()];
|
|
1091
|
+
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1092
|
+
m instanceof BallDouble || m instanceof Set ||
|
|
1093
|
+
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
1094
|
+
throw new Error('type \'' + __ball_to_string(m) + '\' has no .keys getter (not a Map)');
|
|
1095
|
+
}
|
|
1096
|
+
return Object.keys(m);
|
|
1097
|
+
}
|
|
1098
|
+
function __ball_map_values(m: any): any {
|
|
1099
|
+
if (m instanceof Map) return [...m.values()];
|
|
1100
|
+
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1101
|
+
m instanceof BallDouble || m instanceof Set ||
|
|
1102
|
+
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
1103
|
+
throw new Error('type \'' + __ball_to_string(m) + '\' has no .values getter (not a Map)');
|
|
1104
|
+
}
|
|
1105
|
+
return Object.values(m);
|
|
1106
|
+
}
|
|
1107
|
+
function __ball_map_entries(m: any): any {
|
|
1108
|
+
if (m instanceof Map) return [...m.entries()].map(([k, v]) => ({ key: k, value: v }));
|
|
1109
|
+
if (typeof m !== 'object' || m === null || Array.isArray(m) ||
|
|
1110
|
+
m instanceof BallDouble || m instanceof Set ||
|
|
1111
|
+
m instanceof Number || m instanceof String || m instanceof Boolean) {
|
|
1112
|
+
throw new Error('type \'' + __ball_to_string(m) + '\' has no .entries getter (not a Map)');
|
|
1113
|
+
}
|
|
1114
|
+
return Object.entries(m).map(([k, v]) => ({ key: k, value: v }));
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// Shared guard for the REMAINING map_* base-function-call cases
|
|
1118
|
+
// (map_get/map_set/map_delete/map_merge/map_length/map_is_empty/
|
|
1119
|
+
// map_contains_key/map_contains_value/map_foreach) that used to route a
|
|
1120
|
+
// bare map[key], Object.keys/values(map), or key in map straight to the
|
|
1121
|
+
// receiver with no type check at all -- silently returning undefined,
|
|
1122
|
+
// no-opping, or checking array-index membership instead of throwing on a
|
|
1123
|
+
// non-Map (issue #55's silent-degradation class, same family as #218's
|
|
1124
|
+
// map_keys/map_values/map_entries). Returns the validated Map/plain-object
|
|
1125
|
+
// itself (not a boolean) so a call site can keep using it directly, e.g.
|
|
1126
|
+
// __ball_require_map(x, 'map_get')[key].
|
|
1127
|
+
function __ball_require_map(v: any, opName: string): any {
|
|
1128
|
+
if (v instanceof Map) return v;
|
|
1129
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v) ||
|
|
1130
|
+
v instanceof BallDouble || v instanceof Set ||
|
|
1131
|
+
v instanceof Number || v instanceof String || v instanceof Boolean) {
|
|
1132
|
+
throw new Error('type \'' + __ball_to_string(v) + '\' is not a Map (' + opName + ')');
|
|
1133
|
+
}
|
|
1134
|
+
return v;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1079
1137
|
// ── Protobuf Struct/Value compatibility ─────────────────────────
|
|
1080
1138
|
//
|
|
1081
1139
|
// Dart's protobuf runtime wraps google.protobuf.Struct as a class
|