gofront 1.7.0 → 1.8.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.
Files changed (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +10 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +512 -77
  34. package/src/backend/wasm/encode.js +43 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -1,6 +1,7 @@
1
1
  // src/backend/js/stdlib/json.js
2
2
  // CodeGen for Go encoding/json package with compile-time static encoders and decoders.
3
3
 
4
+ import { someChild } from "../../../lower/walk.js";
4
5
  import { typedArrayConstructorForElem } from "../../../typechecker/types.js";
5
6
 
6
7
  function safeIdent(name) {
@@ -33,6 +34,18 @@ function isRawMessageType(t) {
33
34
  );
34
35
  }
35
36
 
37
+ const NUMBER_NAMES = new Set(["json.Number", "encoding/json.Number"]);
38
+
39
+ function isNumberType(t) {
40
+ if (!t) return false;
41
+ const name = t.name ?? "";
42
+ if (NUMBER_NAMES.has(name)) return true;
43
+ if (name.startsWith("Number$$") || name.endsWith(".Number")) return true;
44
+ return (
45
+ t.kind === "named" && name === "Number" && unwrapNamed(t)?.name === "string"
46
+ );
47
+ }
48
+
36
49
  function isByteSliceType(t) {
37
50
  const base = unwrapNamed(t);
38
51
  return (
@@ -77,6 +90,27 @@ const QUOTABLE_KINDS = new Set([
77
90
  // reflect's names for GoFront's basic aliases, used in decode error messages.
78
91
  const BASIC_DISPLAY = { any: "interface {}", byte: "uint8", rune: "int32" };
79
92
 
93
+ // Unmarshal targets whose argument errors depend on the static type alone:
94
+ // pointers can be nil, interfaces are judged by their dynamic value.
95
+ // Only statically concrete non-pointer kinds are rejected up front; anything
96
+ // else (type parameters, unknown kinds) is judged by its dynamic value.
97
+ const TARGET_KIND = { pointer: "ptr" };
98
+ const VALUE_TARGET_KINDS = new Set([
99
+ "slice",
100
+ "array",
101
+ "map",
102
+ "struct",
103
+ "func",
104
+ "chan",
105
+ ]);
106
+
107
+ const COMPOSITE_KINDS = new Set(["slice", "array", "map"]);
108
+ const COMPOSITE_DECODERS = {
109
+ slice: "_getSliceDecoder",
110
+ array: "_getArrayDecoder",
111
+ map: "_getMapDecoder",
112
+ };
113
+
80
114
  // Basic kinds decoded by the typed scalar decoders.
81
115
  const SCALAR_KINDS = QUOTABLE_KINDS;
82
116
 
@@ -94,11 +128,13 @@ export const jsonMethods = {
94
128
  this._markJsonPtrRecvMarshalers();
95
129
  const args = a();
96
130
  if (fn === "Marshal") {
131
+ this._markJsonAnyEncoders();
97
132
  const argType = expr.args[0]?._type;
98
133
  const enc = this._getJsonEncoder(argType);
99
134
  return `__json_marshal(${enc}, ${args[0]})`;
100
135
  }
101
136
  if (fn === "MarshalIndent") {
137
+ this._markJsonAnyEncoders();
102
138
  const argType = expr.args[0]?._type;
103
139
  const enc = this._getJsonEncoder(argType);
104
140
  return `__json_marshal_indent(${enc}, ${args[0]}, ${args[1]}, ${args[2]})`;
@@ -106,7 +142,7 @@ export const jsonMethods = {
106
142
  if (fn === "Unmarshal") {
107
143
  const targetType = expr.args[1]?._type;
108
144
  const dec = this._getJsonDecoder(targetType);
109
- return `__json_unmarshal(${dec}, ${args[0]}, ${args[1]})`;
145
+ return `__json_unmarshal(${dec}, ${args[0]}, ${args[1]}, ${this._jsonTargetArgs(targetType)})`;
110
146
  }
111
147
  if (fn === "Valid") {
112
148
  return `__json_valid(${args[0]})`;
@@ -114,18 +150,27 @@ export const jsonMethods = {
114
150
  return undefined;
115
151
  },
116
152
 
153
+ // Name and static kind of an Unmarshal/Decode target, for the argument
154
+ // errors Go reports from the type alone ("ptr", "val" or none for interfaces).
155
+ _jsonTargetArgs(targetType) {
156
+ const base = unwrapNamed(targetType);
157
+ const key = base?.kind === "basic" ? base.name : base?.kind;
158
+ if (!targetType || key === "nil") return '"", ""';
159
+ const isValue =
160
+ base?.kind === "basic" ? key !== "any" : VALUE_TARGET_KINDS.has(key);
161
+ const kind = TARGET_KIND[key] ?? (isValue ? "val" : "");
162
+ return `${JSON.stringify(this._jsonDisplayType(targetType))}, ${JSON.stringify(kind)}`;
163
+ },
164
+
117
165
  _getJsonEncoder(type, addr = false) {
118
166
  if (!type) return "__json_enc_any";
167
+ if (isNumberType(type)) return "__json_enc_number";
119
168
  if (isRawMessageType(type)) {
120
169
  const fnName = "__json_enc_RawMessage";
121
170
  if (!this._jsonCodecs.has(fnName)) {
122
171
  this._jsonCodecs.set(
123
172
  fnName,
124
- declareCodec(
125
- fnName,
126
- "v",
127
- ` if (v === null || v === undefined) return "null";\n var s = (typeof v === "string" ? v : new TextDecoder().decode(v)).trim();\n return s.length > 0 ? s : "null";`,
128
- ),
173
+ declareCodec(fnName, "v", ` return __json_enc_raw(v);`),
129
174
  );
130
175
  }
131
176
  return fnName;
@@ -170,7 +215,7 @@ export const jsonMethods = {
170
215
  return fnName;
171
216
  }
172
217
 
173
- if (base?.kind === "slice") {
218
+ if (base?.kind === "slice" || base?.kind === "array") {
174
219
  const elemEnc = this._getJsonEncoder(base.elem, true);
175
220
  const fnName = `__json_enc_slice_${safeIdent(typeCodecName(base.elem, "elem").split(".").pop())}`;
176
221
  if (!this._jsonCodecs.has(fnName)) {
@@ -222,8 +267,95 @@ export const jsonMethods = {
222
267
  }
223
268
  },
224
269
 
270
+ // Structs reach the dynamic `any` encoder as bare instances, so each package
271
+ // struct without a MarshalJSON method gets a hidden hook that runs its static
272
+ // encoder (tags, omitempty, promoted fields) instead of listing its properties.
273
+ // A non-generic struct declared in this package that has no MarshalJSON.
274
+ _isPlainPackageStruct(name, type) {
275
+ const isStruct =
276
+ type?.kind === "named" && type.underlying?.kind === "struct";
277
+ const generic = type?.typeArgs?.length || type?._genericOf;
278
+ if (!isStruct || generic || /[.$[]/.test(name) || type.name !== name)
279
+ return false;
280
+ return !this._jsonMarshalerInfo(type) && this.structNames?.has(name);
281
+ },
282
+
283
+ _markJsonAnyEncoders() {
284
+ this._jsonAnyMarked = true;
285
+ },
286
+
287
+ _isJsonEncoderName(recvName) {
288
+ return (
289
+ recvName === "json.Encoder" ||
290
+ recvName === "encoding/json.Encoder" ||
291
+ recvName?.startsWith("Encoder$$")
292
+ );
293
+ },
294
+
295
+ // True when `node` contains a call that marks the any-encoders when
296
+ // generated (`json.Marshal`, `json.MarshalIndent`, `Encoder.Encode`).
297
+ // Decided before any body is generated, so a function-local struct can get
298
+ // its hook right after its class even when the call comes later.
299
+ _usesJsonAnyMarshal(node) {
300
+ if (Array.isArray(node))
301
+ return node.some((n) => this._usesJsonAnyMarshal(n));
302
+ if (!node || typeof node !== "object") return false;
303
+ if (node.kind === "CallExpr" && node.func?.kind === "SelectorExpr") {
304
+ const { expr, field } = node.func;
305
+ if (
306
+ expr?.kind === "Ident" &&
307
+ expr.name === "json" &&
308
+ (field === "Marshal" || field === "MarshalIndent")
309
+ )
310
+ return true;
311
+ if (
312
+ field === "Encode" &&
313
+ this._isJsonEncoderName(this._resolveRecvName(expr?._type))
314
+ )
315
+ return true;
316
+ }
317
+ return someChild(node, (n) => this._usesJsonAnyMarshal(n));
318
+ },
319
+
320
+ _jsonAnyHook(cls, enc) {
321
+ return `Object.defineProperty(${cls}.prototype, "__json_enc", { value: function() { return ${enc}(this); }, configurable: true, writable: true });`;
322
+ },
323
+
324
+ // Runs once all function bodies are generated; package-level structs get
325
+ // their hook at top level (function-local ones get it in _noteLocalTypeDecl).
326
+ _emitJsonAnyHooks() {
327
+ if (!this._jsonAnyMarked || this._jsonAnyHooked) return;
328
+ this._jsonAnyHooked = true;
329
+ const local = this._localTypeNames;
330
+ for (const [name, type] of this.checker?.types ?? []) {
331
+ if (local.has(name) || !this._isPlainPackageStruct(name, type)) continue;
332
+ const cls = safeIdent(name);
333
+ this._jsonCodecs.set(
334
+ `__json_anyenc_${cls}`,
335
+ this._jsonAnyHook(cls, this._getJsonEncoder(type, false)),
336
+ );
337
+ }
338
+ },
339
+
340
+ // A function-local struct class is only in scope inside its function, so
341
+ // its hook goes right after the class, and only when the program marshals.
342
+ _noteLocalTypeDecl(stmt) {
343
+ this._localTypeNames.add(stmt.name);
344
+ if (!this._jsonAnyUsed || stmt.type.kind !== "StructType") return;
345
+ const type = this.checker?.types.get(stmt.name);
346
+ if (!this._isPlainPackageStruct(stmt.name, type)) return;
347
+ const enc = this._getJsonEncoder(type, false);
348
+ this.line(this._jsonAnyHook(safeIdent(stmt.name), enc));
349
+ },
350
+
225
351
  _jsonTypeName(type) {
226
352
  const name = type?.name ?? "value";
353
+ const at = name.indexOf("$$");
354
+ if (at >= 0) {
355
+ const orig = name.slice(0, at);
356
+ const modPath = name.slice(at + 2).replaceAll("$", "/");
357
+ return `${modPath}.${orig}`;
358
+ }
227
359
  if (name.includes(".")) return name;
228
360
  const pkg = this.checker?.pkgName;
229
361
  return pkg ? `${pkg}.${name}` : name;
@@ -387,7 +519,7 @@ export const jsonMethods = {
387
519
  case "float32":
388
520
  return "__json_float32";
389
521
  case "float64":
390
- return "(v => { if (!isFinite(v)) throw new Error('json: unsupported value: ' + v); return String(v); })";
522
+ return "__json_float64";
391
523
  default:
392
524
  return "__json_enc_any";
393
525
  }
@@ -407,11 +539,12 @@ export const jsonMethods = {
407
539
  const keyLiteral = JSON.stringify(`${JSON.stringify(f.jsonKey)}:`);
408
540
  const encodeVal = this._genFieldEncoderExpr(f, addr);
409
541
  const isStruct = unwrapNamed(f.type)?.kind === "struct";
542
+ const isMap = unwrapNamed(f.type)?.kind === "map";
410
543
 
411
544
  this._emitFieldEncoderAccess(f, lines, (valName) => {
412
545
  const encoded = encodeVal(valName);
413
546
  if (f.omitempty) {
414
- return `if (!__json_is_zero(${valName}, ${isStruct})) parts.push(${keyLiteral} + ${encoded});`;
547
+ return `if (!__json_is_zero(${valName}, ${isStruct}, ${isMap})) parts.push(${keyLiteral} + ${encoded});`;
415
548
  }
416
549
  return `parts.push(${keyLiteral} + ${encoded});`;
417
550
  });
@@ -451,13 +584,13 @@ export const jsonMethods = {
451
584
  if (nilChecks.length > 0) {
452
585
  lines.push(` if (!(${nilChecks.join(" || ")})) {`);
453
586
  lines.push(
454
- ` var val = (v.${f.goName} !== undefined ? v.${f.goName} : (${conds.join(" && ")} ? ${leaf} : undefined));`,
587
+ ` var val = (${conds.join(" && ")} ? ${leaf} : v.${f.goName});`,
455
588
  );
456
589
  lines.push(` if (val !== undefined) { ${onVal("val")} }`);
457
590
  lines.push(` }`);
458
591
  } else {
459
592
  lines.push(
460
- ` { var val = (v.${f.goName} !== undefined ? v.${f.goName} : (${conds.join(" && ")} ? ${leaf} : undefined)); if (val !== undefined) { ${onVal("val")} } }`,
593
+ ` { var val = (${conds.join(" && ")} ? ${leaf} : v.${f.goName}); if (val !== undefined) { ${onVal("val")} } }`,
461
594
  );
462
595
  }
463
596
  },
@@ -465,9 +598,9 @@ export const jsonMethods = {
465
598
  _genFieldEncoderExpr(f, addr = false) {
466
599
  const quotedKind = this._jsonQuotedKind(f);
467
600
  if (quotedKind) return this._genQuotedFieldEncoderExpr(f, quotedKind);
601
+ if (isNumberType(f.type)) return (v) => `__json_enc_number(${v})`;
468
602
  if (isRawMessageType(f.type)) {
469
- return (v) =>
470
- `((typeof ${v} === "string" ? ${v} : new TextDecoder().decode(${v})).trim() || "null")`;
603
+ return (v) => `__json_enc_raw(${v})`;
471
604
  }
472
605
  // Marshalers and pointers go through the shared encoders.
473
606
  const special =
@@ -488,7 +621,7 @@ export const jsonMethods = {
488
621
  const enc = this._getJsonEncoder(f.type, addr);
489
622
  return (v) => `${enc}(${v})`;
490
623
  }
491
- if (base?.kind === "slice") {
624
+ if (base?.kind === "slice" || base?.kind === "array") {
492
625
  const enc = this._getJsonEncoder(base.elem, true);
493
626
  return (v) => `__json_enc_slice(${v}, ${enc})`;
494
627
  }
@@ -523,8 +656,7 @@ export const jsonMethods = {
523
656
  case "float32":
524
657
  return (v) => `__json_float32(${v})`;
525
658
  case "float64":
526
- return (v) =>
527
- `(!isFinite(${v}) ? (() => { throw new Error("json: unsupported value: " + ${v}); })() : String(${v}))`;
659
+ return (v) => `__json_float64(${v})`;
528
660
  default:
529
661
  return (v) => `__json_enc_any(${v})`;
530
662
  }
@@ -536,6 +668,10 @@ export const jsonMethods = {
536
668
  targetType.kind === "pointer" ? targetType.base : targetType;
537
669
  if (!baseType) return "__json_dec_any_target";
538
670
 
671
+ if (isNumberType(baseType)) {
672
+ return "__json_dec_number";
673
+ }
674
+
539
675
  if (this._jsonUnmarshalerInfo(baseType)) {
540
676
  return this._getUnmarshalerDecoder(baseType);
541
677
  }
@@ -584,8 +720,8 @@ export const jsonMethods = {
584
720
  return fnName;
585
721
  }
586
722
 
587
- if (base?.kind === "slice") return this._getSliceDecoder(base);
588
- if (base?.kind === "map") return this._getMapDecoder(base);
723
+ const composite = COMPOSITE_DECODERS[base?.kind];
724
+ if (composite) return this[composite](base);
589
725
  // Top-level scalar targets (`json.Unmarshal(b, &n)`) are type-checked too.
590
726
  if (base?.kind === "basic" && SCALAR_KINDS.has(base.name))
591
727
  return this._getScalarDecoder(baseType, base);
@@ -614,6 +750,37 @@ export const jsonMethods = {
614
750
  return fnName;
615
751
  },
616
752
 
753
+ // Arrays decode into a fresh array of the fixed length: surplus JSON elements
754
+ // are dropped and missing ones are zero. JSON null leaves the array alone.
755
+ _getArrayDecoder(base) {
756
+ const elemTypeName = typeCodecName(base.elem, "elem");
757
+ const fnName = `__json_dec_array${base.size}_${safeIdent(elemTypeName.split(".").pop())}`;
758
+ if (!this._jsonCodecs.has(fnName)) {
759
+ this._jsonCodecs.set(
760
+ fnName,
761
+ declareCodec(
762
+ fnName,
763
+ "parsed, target, box",
764
+ this._arrayDecoderBody(base),
765
+ ),
766
+ );
767
+ }
768
+ return fnName;
769
+ },
770
+
771
+ _arrayDecoderBody(base) {
772
+ const elemDec = this._genElemDecoder(base.elem);
773
+ const typedCtor = typedArrayConstructorForElem(base.elem);
774
+ const display = JSON.stringify(this._jsonDisplayType(base));
775
+ const init = `new ${typedCtor ?? "Array"}(${base.size})`;
776
+ // Typed arrays start zeroed; plain arrays need their zero values filled in.
777
+ const zero = this.zeroValueForType(base.elem);
778
+ const fill = typedCtor
779
+ ? ""
780
+ : `\n for (var z = parsed.length; z < ${base.size}; z++) out[z] = ${zero};`;
781
+ return ` if (parsed === null) return null;\n if (!Array.isArray(parsed)) return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${display}));\n var out = ${init};\n for (var i = 0; i < parsed.length && i < ${base.size}; i++) {\n var item = ${elemDec("parsed[i]", "__json_raw_child(parsed, i)")};\n if (item && item._msg) return item;\n out[i] = item;\n }${fill}\n if (box) box.value = out;\n return null;`;
782
+ },
783
+
617
784
  _getMapDecoder(base) {
618
785
  const valType = base.value ?? base.elem;
619
786
  const valTypeName = typeCodecName(valType, "val");
@@ -628,7 +795,7 @@ export const jsonMethods = {
628
795
  declareCodec(
629
796
  fnName,
630
797
  "parsed, target, box",
631
- ` if (parsed === null) { if (box) box.value = null; return null; }\n if (typeof parsed !== "object" || Array.isArray(parsed)) return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${display}));\n var out = Object.create(null); out.__map = true;\n for (var k in parsed) {\n if (Object.prototype.hasOwnProperty.call(parsed, k)) {\n var item = ${valDec("parsed[k]", "__json_raw_child(parsed, k)")};\n if (item && item._msg) return item;\n out[k] = item;\n }\n }\n if (box) box.value = out;\n return null;`,
798
+ ` if (parsed === null) { if (box) box.value = null; return null; }\n if (typeof parsed !== "object" || Array.isArray(parsed)) return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${display}));\n var out = __json_newmap();\n for (var k in parsed) {\n if (Object.prototype.hasOwnProperty.call(parsed, k)) {\n var item = ${valDec("parsed[k]", "__json_raw_child(parsed, k)")};\n if (item && item._msg) return item;\n out[k] = item;\n }\n }\n if (box) box.value = out;\n return null;`,
632
799
  ),
633
800
  );
634
801
  }
@@ -653,14 +820,16 @@ export const jsonMethods = {
653
820
 
654
821
  // Go-style display of a type in decode error messages (reflect's Type.String()).
655
822
  _jsonDisplayType(t) {
656
- if (!t) return "interface {}";
657
- if (t.kind === "pointer") return `*${this._jsonDisplayType(t.base)}`;
658
- if (t.kind === "named") return this._jsonTypeName(t);
659
- if (t.kind === "basic") return BASIC_DISPLAY[t.name] ?? t.name;
660
- if (t.kind === "slice") return `[]${this._jsonDisplayType(t.elem)}`;
661
- if (t.kind === "map")
662
- return `map[${this._jsonDisplayType(t.key)}]${this._jsonDisplayType(t.value ?? t.elem)}`;
663
- return "interface {}";
823
+ const show = (x) => this._jsonDisplayType(x);
824
+ const byKind = {
825
+ pointer: () => `*${show(t.base)}`,
826
+ named: () => this._jsonTypeName(t),
827
+ basic: () => BASIC_DISPLAY[t.name] ?? t.name,
828
+ slice: () => `[]${show(t.elem)}`,
829
+ array: () => `[${t.size}]${show(t.elem)}`,
830
+ map: () => `map[${show(t.key)}]${show(t.value ?? t.elem)}`,
831
+ };
832
+ return byKind[t?.kind]?.() ?? "interface {}";
664
833
  },
665
834
 
666
835
  // Decodes one parsed JSON scalar into `kind`; yields the value or an error (`_msg`).
@@ -683,6 +852,9 @@ export const jsonMethods = {
683
852
 
684
853
  _genElemDecoder(elemType) {
685
854
  if (!elemType) return (v) => `__json_dec_any(${v})`;
855
+ if (isNumberType(elemType))
856
+ return (v, rawExpr) =>
857
+ `__json_number_decode(${v}, "json.Number", ${rawExpr})`;
686
858
  const unmarshaler = this._unmarshalerElemDecoder(elemType);
687
859
  if (unmarshaler) return unmarshaler;
688
860
  if (isRawMessageType(elemType)) {
@@ -697,27 +869,39 @@ export const jsonMethods = {
697
869
  return (v, rawExpr) =>
698
870
  this._jsonScalarDecodeExpr(base.name, display, v, rawExpr);
699
871
  }
700
- if (elemType?.kind === "pointer" && elemType.base) {
701
- const innerBase = unwrapNamed(elemType.base);
702
- if (innerBase?.kind === "struct") {
703
- const dec = this._getJsonDecoder(elemType.base);
704
- const structName = elemType.base.name?.split(".").pop() ?? "Object";
705
- return (v) =>
706
- `(${v} === null ? null : (() => { var inst = new ${structName}(); var err = ${dec}(${v}, inst); return err || inst; })())`;
707
- }
708
- if (innerBase?.kind === "basic") {
709
- const basicDec = this._genElemDecoder(elemType.base);
710
- return (v, rawExpr) =>
711
- `(${v} === null ? null : (() => { var item = ${basicDec(v, rawExpr)}; return item && item._msg ? item : { value: item, __p: true }; })())`;
712
- }
713
- }
872
+ const ptrElem = this._pointerElemDecoder(elemType);
873
+ if (ptrElem) return ptrElem;
714
874
  if (base?.kind === "struct") {
715
875
  const dec = this._getJsonDecoder(elemType);
716
876
  const structName = elemType.name?.split(".").pop() ?? "Object";
717
877
  return (v) =>
718
878
  `(() => { var inst = new ${structName}(); var err = ${dec}(${v}, inst); return err || inst; })()`;
719
879
  }
720
- return (v) => `__json_dec_any(${v})`;
880
+ return this._compositeElemDecoder(elemType, base);
881
+ },
882
+
883
+ // `*Struct` and `*scalar` elements; null stays nil.
884
+ _pointerElemDecoder(elemType) {
885
+ if (elemType?.kind !== "pointer" || !elemType.base) return null;
886
+ const innerBase = unwrapNamed(elemType.base);
887
+ if (innerBase?.kind === "struct") {
888
+ const dec = this._getJsonDecoder(elemType.base);
889
+ const structName = elemType.base.name?.split(".").pop() ?? "Object";
890
+ return (v) =>
891
+ `(${v} === null ? null : (() => { var inst = new ${structName}(); var err = ${dec}(${v}, inst); return err || inst; })())`;
892
+ }
893
+ if (innerBase?.kind !== "basic") return null;
894
+ const basicDec = this._genElemDecoder(elemType.base);
895
+ return (v, rawExpr) =>
896
+ `(${v} === null ? null : (() => { var item = ${basicDec(v, rawExpr)}; return item && item._msg ? item : { value: item, __p: true }; })())`;
897
+ },
898
+
899
+ // Slices, arrays and maps decode through their own codec into a fresh box.
900
+ _compositeElemDecoder(elemType, base) {
901
+ if (!COMPOSITE_KINDS.has(base?.kind)) return (v) => `__json_dec_any(${v})`;
902
+ const dec = this._getJsonDecoder(elemType);
903
+ return (v) =>
904
+ `(() => { var b = { value: null }; var err = ${dec}(${v}, null, b); return err || b.value; })()`;
721
905
  },
722
906
 
723
907
  _buildStructDecoder(structType, fnName) {
@@ -736,6 +920,11 @@ export const jsonMethods = {
736
920
  ` return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${JSON.stringify(this._jsonDisplayType(structType))}));`,
737
921
  );
738
922
  lines.push(` }`);
923
+ const knownKeys = fields.flatMap((f) => [f.jsonKey, f.goName]);
924
+ lines.push(
925
+ ` var disallowErr = __json_check_unknown(parsed, ${JSON.stringify(knownKeys)});`,
926
+ );
927
+ lines.push(` if (disallowErr) return disallowErr;`);
739
928
  // Go reports errors as "Go struct field <innermost struct>.<json key path>".
740
929
  lines.push(` __json_structs.push(${JSON.stringify(structName)});`);
741
930
  lines.push(` try {`);
@@ -750,6 +939,9 @@ export const jsonMethods = {
750
939
  lines.push(` try {`);
751
940
  lines.push(` var val = parsed[fieldKey];`);
752
941
  lines.push(` var raw = __json_raw_child(parsed, fieldKey);`);
942
+ lines.push(
943
+ ` __json_current_offset = __json_raw_end(parsed, fieldKey) || 0;`,
944
+ );
753
945
  const quotedKind = this._jsonQuotedKind(f);
754
946
  if (quotedKind) {
755
947
  const valueType = f.type?.kind === "pointer" ? f.type.base : f.type;
@@ -809,12 +1001,24 @@ export const jsonMethods = {
809
1001
  }
810
1002
  },
811
1003
 
1004
+ _emitNumberFieldDecoder(f, lines, rawExpr) {
1005
+ lines.push(
1006
+ ` var nv = __json_number_decode(val, "json.Number", ${rawExpr}); if (nv && nv._msg) return nv;`,
1007
+ );
1008
+ const val = f.type?.kind === "pointer" ? "{ value: nv }" : "nv";
1009
+ this._emitAssign(f, val, lines);
1010
+ },
1011
+
812
1012
  _emitFieldDecoder(f, structName, lines, rawExpr) {
813
1013
  const valueType = f.type?.kind === "pointer" ? f.type.base : f.type;
814
1014
  if (this._jsonUnmarshalerInfo(valueType)) {
815
1015
  this._emitUnmarshalerField(f, lines, rawExpr);
816
1016
  return;
817
1017
  }
1018
+ if (isNumberType(valueType)) {
1019
+ this._emitNumberFieldDecoder(f, lines, rawExpr);
1020
+ return;
1021
+ }
818
1022
  if (isRawMessageType(f.type)) {
819
1023
  lines.push(
820
1024
  ` var rawBytes = __json_raw_bytes(${rawExpr}); rawBytes.__raw = true;`,
@@ -877,16 +1081,7 @@ export const jsonMethods = {
877
1081
  return;
878
1082
  }
879
1083
 
880
- if (base?.kind === "slice") {
881
- const dec = this._getJsonDecoder(f.type);
882
- lines.push(` var box = { value: null };`);
883
- lines.push(` var err = ${dec}(val, null, box);`);
884
- lines.push(` if (err) return err;`);
885
- this._emitAssign(f, "box.value", lines);
886
- return;
887
- }
888
-
889
- if (base?.kind === "map") {
1084
+ if (COMPOSITE_KINDS.has(base?.kind)) {
890
1085
  const dec = this._getJsonDecoder(f.type);
891
1086
  lines.push(` var box = { value: null };`);
892
1087
  lines.push(` var err = ${dec}(val, null, box);`);
@@ -11,14 +11,6 @@ export const mapsMethods = {
11
11
  return `__equal(${a1}, ${b1})`;
12
12
  }
13
13
  switch (fn) {
14
- case "Keys": {
15
- const [m] = a();
16
- return `Object.keys(${m})`;
17
- }
18
- case "Values": {
19
- const [m] = a();
20
- return `Object.values(${m})`;
21
- }
22
14
  case "Clone": {
23
15
  const [m] = a();
24
16
  return `({...${m}})`;
@@ -9,8 +9,10 @@ const MATH1 = {
9
9
  Sqrt: "sqrt",
10
10
  Cbrt: "cbrt",
11
11
  Log: "log",
12
+ Log1p: "log1p",
12
13
  Log2: "log2",
13
14
  Log10: "log10",
15
+ Expm1: "expm1",
14
16
  Sin: "sin",
15
17
  Cos: "cos",
16
18
  Tan: "tan",
@@ -37,7 +39,7 @@ const MATH_EXTRA = {
37
39
  Inf: ([x]) => `(${x} >= 0 ? Infinity : -Infinity)`,
38
40
  IsNaN: ([x]) => `Number.isNaN(${x})`,
39
41
  IsInf: ([x, y]) =>
40
- `(${y} > 0 ? ${x} === Infinity : ${y} < 0 ? ${x} === -Infinity : !Number.isFinite(${x}))`,
42
+ `(${y} > 0 ? ${x} === Infinity : ${y} < 0 ? ${x} === -Infinity : Math.abs(${x}) === Infinity)`,
41
43
  NaN: () => "NaN",
42
44
  Exp2: ([x]) => `Math.pow(2, ${x})`,
43
45
  Signbit: ([x]) => `(${x} < 0 || Object.is(${x}, -0))`,
@@ -46,6 +48,20 @@ const MATH_EXTRA = {
46
48
  Dim: ([x, y]) => `Math.max(${x} - ${y}, 0)`,
47
49
  Remainder: ([x, y]) =>
48
50
  `((x, y) => { if (isNaN(x) || isNaN(y) || y === 0 || !isFinite(x)) return NaN; if (!isFinite(y)) return x; const q = x / y; const r = Math.round(q); const diff = Math.abs(q - r); const n = (diff === 0.5 && (r % 2 !== 0)) ? (r < q ? r + 1 : r - 1) : r; return x - n * y; })(${x}, ${y})`,
51
+ Frexp: ([x]) =>
52
+ `((f) => { if (f === 0 || !Number.isFinite(f)) return [f, 0]; const buf = new ArrayBuffer(8); const f64 = new Float64Array(buf); const u32 = new Uint32Array(buf); f64[0] = f; let hi = u32[1]; let exp = (hi >>> 20) & 0x7ff; if (exp === 0) { f64[0] = f * Math.pow(2, 54); hi = u32[1]; exp = ((hi >>> 20) & 0x7ff) - 54; } exp -= 1022; u32[1] = (hi & 0x800fffff) | 0x3fe00000; return [f64[0], exp]; })(${x})`,
53
+ Ldexp: ([frac, exp]) =>
54
+ `((frac, exp) => { if (frac === 0 || !Number.isFinite(frac)) return frac; if (exp > 1023) return frac < 0 ? -Infinity : Infinity; if (exp < -1074) return frac < 0 ? -0 : 0; if (exp >= -1022) return frac * Math.pow(2, exp); return frac * Math.pow(2, exp + 54) * Math.pow(2, -54); })(${frac}, ${exp})`,
55
+ Modf: ([f]) =>
56
+ `((x) => { if (Number.isNaN(x)) return [NaN, NaN]; if (!Number.isFinite(x)) return [x, NaN]; const i = Math.trunc(x); const frac = x === i ? (x < 0 || Object.is(x, -0) ? -0 : 0) : x - i; return [i, frac]; })(${f})`,
57
+ FMA: ([x, y, z]) =>
58
+ `((x, y, z) => { if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(z)) return NaN; if ((x === 0 && !Number.isFinite(y)) || (y === 0 && !Number.isFinite(x))) return NaN; const p = x * y; if (!Number.isFinite(p) && !Number.isFinite(z) && ((p > 0 && z < 0) || (p < 0 && z > 0))) return NaN; return p + z; })(${x}, ${y}, ${z})`,
59
+ RoundToEven: ([x]) =>
60
+ `((x) => { if (x === 0 || !Number.isFinite(x)) return x; const t = Math.trunc(x); const f = Math.abs(x - t); if (f < 0.5) return Object.is(t, 0) && (x < 0 || Object.is(x, -0)) ? -0 : t; if (f > 0.5) return x > 0 ? t + 1 : t - 1; const res = t % 2 === 0 ? t : (x > 0 ? t + 1 : t - 1); return Object.is(res, 0) && (x < 0 || Object.is(x, -0)) ? -0 : res; })(${x})`,
61
+ Nextafter: ([x, y]) =>
62
+ `((x, y) => { if (Number.isNaN(x) || Number.isNaN(y)) return NaN; if (x === y) return x; if (x === 0) { const s = Number.MIN_VALUE; return y > 0 ? s : -s; } const buf = new ArrayBuffer(8); const f64 = new Float64Array(buf); const i64 = new BigInt64Array(buf); f64[0] = x; let b = i64[0]; if ((x > 0 && y > x) || (x < 0 && y > x)) b = b + (x > 0 ? 1n : -1n); else b = b + (x > 0 ? -1n : 1n); i64[0] = b; return f64[0]; })(${x}, ${y})`,
63
+ Nextafter32: ([x, y]) =>
64
+ `((x, y) => { if (Number.isNaN(x) || Number.isNaN(y)) return NaN; if (x === y) return x; if (x === 0) { const s = 1.401298464324817e-45; return y > 0 ? s : -s; } const buf = new ArrayBuffer(4); const f32 = new Float32Array(buf); const i32 = new Int32Array(buf); f32[0] = x; let b = i32[0]; if ((x > 0 && y > x) || (x < 0 && y > x)) b = b + (x > 0 ? 1 : -1); else b = b + (x > 0 ? -1 : 1); i32[0] = b; return f32[0]; })(${x}, ${y})`,
49
65
  Float64bits: ([x]) =>
50
66
  `((f) => { const b = new ArrayBuffer(8); new Float64Array(b)[0] = f; return Number(new BigUint64Array(b)[0]); })(${x})`,
51
67
  Float64frombits: ([b]) =>
@@ -13,10 +13,20 @@ export const simdMethods = {
13
13
  return `__simd_splat(${args[0]})`;
14
14
  case "NewF32x4":
15
15
  return `__simd_f32x4(${args[0]}, ${args[1]}, ${args[2]}, ${args[3]})`;
16
+ case "Select":
17
+ return `__simd_select(${args[0]}, ${args[1]}, ${args[2]})`;
18
+ case "Shuffle":
19
+ return `__simd_shuffle(${args.join(", ")})`;
16
20
  case "LoadF32x4":
17
21
  return `__simd_load(${args[0]}, ${args[1]})`;
18
22
  case "StoreF32x4":
19
23
  return `__simd_store(${args[0]}, ${args[1]}, ${args[2]})`;
24
+ case "LoadF32x4Slice":
25
+ return `__simd_load_slice(${args[0]}, ${args[1]})`;
26
+ case "StoreF32x4Slice":
27
+ return `__simd_store_slice(${args[0]}, ${args[1]}, ${args[2]})`;
28
+ case "Pad4":
29
+ return `(Math.floor((${args[0]} + 3) / 4) * 4)`;
20
30
  default:
21
31
  return undefined;
22
32
  }
@@ -34,26 +34,26 @@ const SLICES_DISPATCH = {
34
34
  `((s, f) => { if (!s || s.length === 0) throw new Error("slices.MinFunc: empty list"); return s.reduce((m, x) => f(x, m) < 0 ? x : m); })(${a[0]}, ${a[1]})`,
35
35
  Clone: (a) => `((s) => s ? s.slice() : null)(${a[0]})`,
36
36
  Compact: (a) =>
37
- `((s) => { if (!s || s.length === 0) return s; let k = 1; for (let i = 1; i < s.length; i++) { if (s[i] !== s[k - 1]) s[k++] = s[i]; } return ArrayBuffer.isView(s) ? s.subarray(0, k) : (s.length = k, s); })(${a[0]})`,
37
+ `((s) => { if (!s || s.length === 0) return s; let k = 1; for (let i = 1; i < s.length; i++) { if (s[i] !== s[k - 1]) s[k++] = s[i]; } return s[__sv] !== undefined ? __slice(s, 0, k) : ArrayBuffer.isView(s) ? s.subarray(0, k) : (s.length = k, s); })(${a[0]})`,
38
38
  CompactFunc: (a) =>
39
- `((s, f) => { if (!s || s.length === 0) return s; let k = 1; for (let i = 1; i < s.length; i++) { if (!f(s[i], s[k - 1])) s[k++] = s[i]; } return ArrayBuffer.isView(s) ? s.subarray(0, k) : (s.length = k, s); })(${a[0]}, ${a[1]})`,
39
+ `((s, f) => { if (!s || s.length === 0) return s; let k = 1; for (let i = 1; i < s.length; i++) { if (!f(s[i], s[k - 1])) s[k++] = s[i]; } return s[__sv] !== undefined ? __slice(s, 0, k) : ArrayBuffer.isView(s) ? s.subarray(0, k) : (s.length = k, s); })(${a[0]}, ${a[1]})`,
40
40
  Concat: (a) =>
41
41
  a.length === 0
42
42
  ? "null"
43
43
  : `((...slices) => { const nonNull = slices.filter(Boolean); if (nonNull.length === 0) return slices[0] === null ? null : []; if (ArrayBuffer.isView(nonNull[0])) { const totalLen = nonNull.reduce((sum, s) => sum + s.length, 0); const out = new nonNull[0].constructor(totalLen); let off = 0; for (const s of nonNull) { out.set(s, off); off += s.length; } return out; } return [].concat(...slices.map(s => s ? (ArrayBuffer.isView(s) ? Array.from(s) : s) : [])); })(${a.map((x) => `(${x})`).join(", ")})`,
44
44
  Delete: (a) =>
45
- `((s, i, j) => { if (!s && i === 0 && j === 0) return s; s = s || []; if (i < 0 || j < i || j > s.length) throw new Error("runtime error: slice bounds out of range"); if (ArrayBuffer.isView(s)) { s.copyWithin(i, j); return s.subarray(0, s.length - (j - i)); } s.splice(i, j - i); return s; })(${a[0]}, ${a[1]}, ${a[2]})`,
45
+ `((s, i, j) => { if (!s && i === 0 && j === 0) return s; s = s || []; if (i < 0 || j < i || j > s.length) throw new Error("runtime error: slice bounds out of range"); if (ArrayBuffer.isView(s)) { s.copyWithin(i, j); return s.subarray(0, s.length - (j - i)); } if (s[__sv] !== undefined) { s.copyWithin(i, j); return __slice(s, 0, s.length - (j - i)); } s.splice(i, j - i); return s; })(${a[0]}, ${a[1]}, ${a[2]})`,
46
46
  DeleteFunc: (a) =>
47
- `((s, f) => { if (!s || s.length === 0) return s; let k = 0; for (let i = 0; i < s.length; i++) { if (!f(s[i])) s[k++] = s[i]; } return ArrayBuffer.isView(s) ? s.subarray(0, k) : (s.length = k, s); })(${a[0]}, ${a[1]})`,
47
+ `((s, f) => { if (!s || s.length === 0) return s; let k = 0; for (let i = 0; i < s.length; i++) { if (!f(s[i])) s[k++] = s[i]; } return s[__sv] !== undefined ? __slice(s, 0, k) : ArrayBuffer.isView(s) ? s.subarray(0, k) : (s.length = k, s); })(${a[0]}, ${a[1]})`,
48
48
  Insert: (a, isTyped) => {
49
49
  const items = a.slice(2);
50
50
  const nilInit = isTyped ? "new Uint8Array(0)" : "[]";
51
- return `((s, i, ...v) => { if (!s && i === 0) s = ${nilInit}; if (!s || i < 0 || i > s.length) throw new Error("runtime error: slice bounds out of range"); if (ArrayBuffer.isView(s)) { const res = new s.constructor(s.length + v.length); res.set(s.subarray(0, i), 0); res.set(v, i); res.set(s.subarray(i), i + v.length); return res; } s.splice(i, 0, ...v); return s; })(${a[0]}, ${a[1]}${items.length ? `, ${items.join(", ")}` : ""})`;
51
+ return `((s, i, ...v) => { if (!s && i === 0) s = ${nilInit}; if (!s || i < 0 || i > s.length) throw new Error("runtime error: slice bounds out of range"); if (ArrayBuffer.isView(s)) { const res = new s.constructor(s.length + v.length); res.set(s.subarray(0, i), 0); res.set(v, i); res.set(s.subarray(i), i + v.length); return res; } if (s[__sv] !== undefined) s = Array.from(s); s.splice(i, 0, ...v); return s; })(${a[0]}, ${a[1]}${items.length ? `, ${items.join(", ")}` : ""})`;
52
52
  },
53
53
  Replace: (a, isTyped) => {
54
54
  const items = a.slice(3);
55
55
  const nilInit = isTyped ? "new Uint8Array(0)" : "[]";
56
- return `((s, i, j, ...v) => { if (!s && i === 0 && j === 0) s = ${nilInit}; if (!s || i < 0 || j < i || j > s.length) throw new Error("runtime error: slice bounds out of range"); if (ArrayBuffer.isView(s)) { const res = new s.constructor(s.length - (j - i) + v.length); res.set(s.subarray(0, i), 0); res.set(v, i); res.set(s.subarray(j), i + v.length); return res; } s.splice(i, j - i, ...v); return s; })(${a[0]}, ${a[1]}, ${a[2]}${items.length ? `, ${items.join(", ")}` : ""})`;
56
+ return `((s, i, j, ...v) => { if (!s && i === 0 && j === 0) s = ${nilInit}; if (!s || i < 0 || j < i || j > s.length) throw new Error("runtime error: slice bounds out of range"); if (ArrayBuffer.isView(s)) { const res = new s.constructor(s.length - (j - i) + v.length); res.set(s.subarray(0, i), 0); res.set(v, i); res.set(s.subarray(j), i + v.length); return res; } if (s[__sv] !== undefined) s = Array.from(s); s.splice(i, j - i, ...v); return s; })(${a[0]}, ${a[1]}, ${a[2]}${items.length ? `, ${items.join(", ")}` : ""})`;
57
57
  },
58
58
  Grow: (a, isTyped) => {
59
59
  const nilInit = isTyped ? "new Uint8Array(0)" : "[]";
@@ -62,6 +62,15 @@ const SLICES_DISPATCH = {
62
62
  Clip: (a) => `((s) => s ? s.slice() : null)(${a[0]})`,
63
63
  };
64
64
 
65
+ const VIEW_AWARE = new Set([
66
+ "Compact",
67
+ "CompactFunc",
68
+ "Delete",
69
+ "DeleteFunc",
70
+ "Insert",
71
+ "Replace",
72
+ ]);
73
+
65
74
  /** @type {ThisType<CodeGen>} */
66
75
  export const slicesMethods = {
67
76
  _genSlices(fn, a, expr) {
@@ -70,8 +79,29 @@ export const slicesMethods = {
70
79
  const [a1, b1] = a();
71
80
  return `__equal(${a1}, ${b1})`;
72
81
  }
82
+ // Struct and array elements are values: compare them field by field
83
+ // (`.includes` and `.indexOf` compare object identity).
84
+ if (
85
+ (fn === "Contains" || fn === "Index") &&
86
+ this._sliceElemIsAggregate(expr)
87
+ ) {
88
+ this.useHelper("equal");
89
+ const [s1, v1] = a();
90
+ const found = fn === "Contains" ? "true" : "i";
91
+ const missing = fn === "Contains" ? "false" : "-1";
92
+ return `((s, v) => { s = s || []; for (let i = 0; i < s.length; i++) { if (__equal(s[i], v)) return ${found}; } return ${missing}; })(${s1}, ${v1})`;
93
+ }
73
94
  const isTyped = isTypedArraySlice(expr?.args?.[0]?._type);
74
95
  const gen = SLICES_DISPATCH[fn];
96
+ // These shrink or grow the slice in place; a slice view (`__sv`) has a
97
+ // fixed length, so they reslice it or copy it to a plain array.
98
+ if (VIEW_AWARE.has(fn)) this.useHelper("slice");
75
99
  return gen ? gen(a(), isTyped) : undefined;
76
100
  },
101
+
102
+ _sliceElemIsAggregate(expr) {
103
+ let t = expr?.args?.[0]?._type;
104
+ while (t?.kind === "named") t = t.underlying;
105
+ return t?.kind === "slice" && Boolean(this._aggregateBase(t.elem));
106
+ },
77
107
  };