gofront 1.4.0 → 1.6.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 (77) hide show
  1. package/README.md +174 -403
  2. package/package.json +16 -19
  3. package/src/{codegen → backend/js}/expressions.js +265 -77
  4. package/src/{codegen → backend/js}/index.js +120 -89
  5. package/src/{codegen → backend/js}/runtime.js +102 -0
  6. package/src/{codegen → backend/js}/source-map.js +32 -0
  7. package/src/{codegen → backend/js}/statements.js +139 -64
  8. package/src/backend/js/stdlib/bits.js +32 -0
  9. package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
  10. package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
  11. package/src/{codegen → backend/js}/stdlib/index.js +6 -0
  12. package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
  13. package/src/{codegen → backend/js}/stdlib/path.js +2 -2
  14. package/src/backend/js/stdlib/shared.js +17 -0
  15. package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
  16. package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
  17. package/src/backend/js/stdlib/strconv.js +58 -0
  18. package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
  19. package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
  20. package/src/{codegen → backend/js}/stdlib/time.js +3 -3
  21. package/src/{codegen → backend/js}/templ.js +3 -3
  22. package/src/backend/wasm/boundary.js +1798 -0
  23. package/src/backend/wasm/emit-builtins.js +1103 -0
  24. package/src/backend/wasm/emit-exprs.js +1908 -0
  25. package/src/backend/wasm/emit-maps.js +1863 -0
  26. package/src/backend/wasm/emit-shared.js +519 -0
  27. package/src/backend/wasm/emit-stdlib.js +3006 -0
  28. package/src/backend/wasm/emit-stmts.js +2472 -0
  29. package/src/backend/wasm/emit.js +1376 -0
  30. package/src/backend/wasm/encode.js +937 -0
  31. package/src/backend/wasm/glue.js +144 -0
  32. package/src/backend/wasm/index.js +2790 -0
  33. package/src/backend/wasm/monomorph.js +711 -0
  34. package/src/backend/wasm/optimize.js +227 -0
  35. package/src/backend/wasm/types.js +544 -0
  36. package/src/backend/wasm/wat.js +302 -0
  37. package/src/cli-core.js +323 -75
  38. package/src/compiler.js +447 -14
  39. package/src/dev-server.js +56 -15
  40. package/src/index.js +99 -120
  41. package/src/lexer.js +18 -0
  42. package/src/lower/boxing.js +35 -0
  43. package/src/lower/captures.js +172 -0
  44. package/src/lower/embedding.js +30 -0
  45. package/src/lower/escape.js +164 -0
  46. package/src/lower/functions.js +302 -0
  47. package/src/lower/globals.js +316 -0
  48. package/src/lower/index.js +118 -0
  49. package/src/lower/ownership.js +137 -0
  50. package/src/lower/range.js +67 -0
  51. package/src/lower/walk.js +25 -0
  52. package/src/minifier.js +2 -0
  53. package/src/parser/index.js +8 -3
  54. package/src/resolver.js +3 -1
  55. package/src/test-runner.js +94 -20
  56. package/src/typechecker/assignability.js +42 -1
  57. package/src/typechecker/expressions.js +182 -3
  58. package/src/typechecker/index.js +261 -5
  59. package/src/typechecker/resolve.js +35 -6
  60. package/src/typechecker/stdlib/core.js +34 -19
  61. package/src/typechecker/stdlib/extended.js +18 -3
  62. package/src/typechecker/stdlib/shared.js +53 -0
  63. package/src/typechecker/stdlib.js +2 -0
  64. package/src/typechecker/types.js +259 -6
  65. package/src/vendor.js +2 -2
  66. package/src/codegen/stdlib/strconv.js +0 -33
  67. /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
  68. /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
  69. /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
  70. /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
  71. /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
  72. /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
  73. /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
  74. /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
  75. /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
  76. /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
  77. /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gofront",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "author": "Luuk van Venrooij",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -11,12 +11,15 @@
11
11
  "url": "https://github.com/seriva/gofront/issues"
12
12
  },
13
13
  "homepage": "https://github.com/seriva/gofront#readme",
14
- "description": "A Go-inspired language that transpiles to JavaScript",
14
+ "description": "A Go-inspired language that compiles to JavaScript and WebAssembly",
15
15
  "keywords": [
16
16
  "go",
17
17
  "compiler",
18
18
  "transpiler",
19
19
  "javascript",
20
+ "webassembly",
21
+ "wasm",
22
+ "wasmgc",
20
23
  "frontend",
21
24
  "type-safe"
22
25
  ],
@@ -31,30 +34,24 @@
31
34
  "gofront": "./src/index.js"
32
35
  },
33
36
  "scripts": {
34
- "build:simple": "node src/index.js example/simple/src -o example/simple/app.js",
35
- "build:reactive": "node src/index.js example/reactive/src -o example/reactive/app.js",
36
- "build:gom": "node src/index.js example/gom/src -o example/gom/app.js",
37
- "build:templ": "node src/index.js example/templ/src -o example/templ/app.js",
38
- "build:webgl": "node src/index.js example/webgl/src -o example/webgl/app.js",
39
- "build:all": "npm run build:simple && npm run build:reactive && npm run build:gom && npm run build:templ && npm run build:webgl",
40
- "test": "npm run test:unit",
41
- "test:all": "npm run test:unit && npm run test:perf && npm run test:examples && npm run test:examples:dom && npm run test:e2e",
42
- "test:unit": "node test/unit/run.js",
43
- "test:perf": "node test/e2e/perf/zero-alloc.js",
44
- "test:examples": "node src/index.js test example/simple/src && node src/index.js test example/simple/src/utils && node src/index.js test example/reactive/src && node src/index.js test example/reactive/src/utils && node src/index.js test example/gom/src && node src/index.js test example/gom/src/utils && node src/index.js test example/templ/src && node src/index.js test example/templ/src/utils",
45
- "test:examples:dom": "node src/index.js test --dom example/simple/src && node src/index.js test --dom example/gom/src && node src/index.js test --dom example/templ/src",
37
+ "build": "node src/index.js example/simple/src -o example/simple/app.js && node src/index.js example/reactive/src -o example/reactive/app.js && node src/index.js example/gom/src -o example/gom/app.js && node src/index.js example/templ/src -o example/templ/app.js && node src/index.js example/webgl/src -o example/webgl/app.js && node src/index.js example/hybrid/src -o example/hybrid/app.js",
38
+ "test": "node test/unit/run.js && node test/e2e/perf/zero-alloc.js && node src/index.js test --dom example/...",
39
+ "test:e2e": "playwright test",
46
40
  "test:coverage": "c8 --include='src/**' --reporter=text --reporter=lcov node test/unit/run.js",
47
- "test:e2e": "npm run build:all && playwright test",
41
+ "bench": "node --expose-gc test/e2e/perf/raycast-bench.js",
48
42
  "format": "biome format --write .",
49
- "check": "biome check . && sentrux check && sentrux gate && node src/index.js example/simple/src --check && node src/index.js example/reactive/src --check && node src/index.js example/gom/src --check && node src/index.js example/templ/src --check && node src/index.js example/webgl/src --check",
43
+ "check": "biome check . && sentrux check && sentrux gate && node src/index.js check example/...",
50
44
  "prepare": "lefthook install"
51
45
  },
52
46
  "devDependencies": {
53
- "@biomejs/biome": "^2.5.14",
47
+ "@biomejs/biome": "^2.5.15",
54
48
  "@playwright/test": "^1.63.0",
55
49
  "c8": "^12.0.0",
56
- "jsdom": "^30.1.0",
57
- "lefthook": "^2.1.14",
50
+ "jsdom": "^30.1.2",
51
+ "lefthook": "^2.1.17",
58
52
  "serve": "^14.2.6"
53
+ },
54
+ "optionalDependencies": {
55
+ "binaryen": "^132.0.0"
59
56
  }
60
57
  }
@@ -1,12 +1,78 @@
1
+ import {
2
+ closureMutates,
3
+ fnMutates,
4
+ nodeMutatesVar,
5
+ nodeWritesVar,
6
+ rootIdentName,
7
+ } from "../../lower/index.js";
1
8
  import {
2
9
  ERROR,
3
10
  isComplex,
4
11
  isTypedArraySlice,
5
12
  typedArrayConstructorForElem,
6
- } from "../typechecker/types.js";
13
+ } from "../../typechecker/types.js";
7
14
 
8
15
  /** @typedef {import('./index.js').CodeGen} CodeGen */
9
16
 
17
+ const STRICT_NUMERIC_TYPES = new Set([
18
+ "float32",
19
+ "int8",
20
+ "int16",
21
+ "int32",
22
+ "rune",
23
+ "uint8",
24
+ "byte",
25
+ "uint16",
26
+ "uint32",
27
+ ]);
28
+
29
+ const STRICT_OPS = new Set([
30
+ "+",
31
+ "-",
32
+ "*",
33
+ "/",
34
+ "%",
35
+ "&",
36
+ "|",
37
+ "^",
38
+ "&^",
39
+ "<<",
40
+ ">>",
41
+ ]);
42
+
43
+ // Splits a generated JS lvalue at its outermost trailing access:
44
+ // `a.b[i]` → { obj: "a.b", key: "i" }, `a.b.c` → { obj: "a.b", prop: "c" }.
45
+ function splitLastAccess(js) {
46
+ if (js.endsWith("]")) {
47
+ let depth = 0;
48
+ let quote = null;
49
+ for (let i = js.length - 1; i >= 0; i--) {
50
+ const ch = js[i];
51
+ if (quote) {
52
+ if (ch === quote && js[i - 1] !== "\\") quote = null;
53
+ continue;
54
+ }
55
+ if (ch === '"' || ch === "'" || ch === "`") quote = ch;
56
+ else if (ch === "]" || ch === ")") depth++;
57
+ else if (ch === "[" || ch === "(") {
58
+ depth--;
59
+ if (depth === 0 && ch === "[" && i > 0)
60
+ return { obj: js.slice(0, i), key: js.slice(i + 1, -1) };
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+ const m = /^(.*[^.])\.([A-Za-z_$][\w$]*)$/s.exec(js);
66
+ if (!m) return null;
67
+ // Only split when the dot is at nesting depth 0, e.g. not inside `f(a.b)`.
68
+ let depth = 0;
69
+ for (const ch of m[1]) {
70
+ if (ch === "(" || ch === "[") depth++;
71
+ else if (ch === ")" || ch === "]") depth--;
72
+ }
73
+ return depth === 0 ? { obj: m[1], prop: m[2] } : null;
74
+ }
75
+
10
76
  // Namespace constants: pkg.Field → JS literal
11
77
  const NS_CONSTANTS = {
12
78
  math: {
@@ -144,7 +210,7 @@ const TYPEOF_NUMBER_NAMES = new Set([...INT_TYPE_NAMES, "float32", "float64"]);
144
210
  const BUILTIN_GEN = {
145
211
  append: (s, e) => s.genAppend(e),
146
212
  len: (s, e) => s._genBuiltinLen(e),
147
- cap: (s, e) => `${s.genExpr(e.args[0])}.length`,
213
+ cap: (s, e) => `(${s.genExpr(e.args[0])}?.length ?? 0)`,
148
214
  make: (s, e) => s.genMake(e),
149
215
  delete: (s, e) => {
150
216
  const [m, k] = e.args.map((a) => s.genExpr(a));
@@ -167,14 +233,19 @@ const BUILTIN_GEN = {
167
233
  }
168
234
  return `{ value: ${s.zeroValueForExpr(arg)} }`;
169
235
  },
170
- print: (s, e) => `console.log(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
236
+ // `print` writes without a trailing newline (Go semantics); the wasm glue
237
+ // buffers the same way, so output parity holds across backends.
238
+ print: (s, e) =>
239
+ `((__s) => (typeof process !== "undefined" && process?.stdout?.write ? process.stdout.write(__s) : console.log(__s)))([${e.args.map((a) => s.genExpr(a)).join(", ")}].map(String).join(" "))`,
171
240
  println: (s, e) =>
172
241
  `console.log(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
173
242
  panic: (s, e) => `(() => { throw new Error(${s.genExpr(e.args[0])}); })()`,
174
- recover: () =>
175
- `(typeof __panic !== "undefined" && __panic !== null ? (() => { const __r = __panic.message ?? String(__panic); __panic = null; return __r; })() : null)`,
243
+ recover: (s) => {
244
+ s.useHelper("panicRt");
245
+ return `__recover(${s._recoverOkInScope ? "__recoverOk" : "false"})`;
246
+ },
176
247
  error: (s, e) => {
177
- s._usesError = true;
248
+ s.useHelper("error");
178
249
  return `__error(${s.genExpr(e.args[0])})`;
179
250
  },
180
251
  min: (s, e) => `Math.min(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
@@ -206,7 +277,7 @@ export const expressionGenMethods = {
206
277
  if (expr._ifaceBox && !expr._ifaceBoxing) {
207
278
  expr._ifaceBoxing = true;
208
279
  try {
209
- this._usesIfaceBox = true;
280
+ this.useHelper("ifaceBox");
210
281
  const fn = expr._ifaceBox === "ptr" ? "__ifp" : "__ifv";
211
282
  return `${fn}(${this.genExpr(expr)})`;
212
283
  } finally {
@@ -299,7 +370,7 @@ export const expressionGenMethods = {
299
370
  if (base.kind === "struct") {
300
371
  if (this._structClassName(t) || this._structClassName(base))
301
372
  return `${js}.__clone()`;
302
- this._usesSClone = true;
373
+ this.useHelper("sclone");
303
374
  return `__sclone(${js})`;
304
375
  }
305
376
  const elemClone = this._cloneJs(base.elem, "__e");
@@ -335,60 +406,21 @@ export const expressionGenMethods = {
335
406
  },
336
407
 
337
408
  _rootIdentName(e) {
338
- while (e) {
339
- if (e.kind === "Ident") return e.name;
340
- if (e.kind === "SelectorExpr" || e.kind === "IndexExpr") e = e.expr;
341
- else if (e.kind === "UnaryExpr" && e.op === "*") e = e.operand;
342
- else return null;
343
- }
344
- return null;
409
+ return rootIdentName(e);
345
410
  },
346
411
 
347
412
  // True when `node` itself (not its children) writes to variable `name`.
348
413
  _nodeWritesVar(node, name) {
349
- const root = (e) => this._rootIdentName(e) === name;
350
- switch (node.kind) {
351
- case "AssignStmt":
352
- return node.lhs.some(root);
353
- case "DefineStmt":
354
- return node.lhs.some((e) => e._redecl && e.name === name);
355
- case "IncDecStmt":
356
- return root(node.expr);
357
- case "SelectorExpr":
358
- return Boolean(
359
- node._isMethodValue && node._type?._ptrRecv && root(node.expr),
360
- );
361
- default:
362
- return false;
363
- }
414
+ return nodeWritesVar(node, name);
364
415
  },
365
416
 
366
417
  // True when `node` may modify (or take the address of) the value held by variable `name`.
367
418
  _nodeMutatesVar(node, name, addrOnly = false) {
368
- if (!node || typeof node !== "object") return false;
369
- if (Array.isArray(node))
370
- return node.some((n) => this._nodeMutatesVar(n, name, addrOnly));
371
- if (
372
- node.kind === "UnaryExpr" &&
373
- node.op === "&" &&
374
- this._rootIdentName(node.operand) === name
375
- )
376
- return true;
377
- if (!addrOnly && this._nodeWritesVar(node, name)) return true;
378
- for (const key of Object.keys(node)) {
379
- if (key.startsWith("_")) continue;
380
- if (this._nodeMutatesVar(node[key], name, addrOnly)) return true;
381
- }
382
- return false;
419
+ return nodeMutatesVar(node, name, addrOnly);
383
420
  },
384
421
 
385
422
  _fnMutates(name, addrOnly = false) {
386
- const ctx = this._fnCtx;
387
- if (!ctx) return true;
388
- const key = `${addrOnly ? "&" : ""}${name}`;
389
- if (!ctx.mut.has(key))
390
- ctx.mut.set(key, this._nodeMutatesVar(ctx.body, name, addrOnly));
391
- return ctx.mut.get(key);
423
+ return fnMutates(this._fnCtx?.body, name, addrOnly, this._fnCtx?.mut);
392
424
  },
393
425
 
394
426
  _withFnCtx(body, fn) {
@@ -447,21 +479,7 @@ export const expressionGenMethods = {
447
479
  },
448
480
 
449
481
  _closureMutates(name) {
450
- const ctx = this._fnCtx;
451
- const key = `λ${name}`;
452
- if (!ctx.mut.has(key)) {
453
- const walk = (node) => {
454
- if (!node || typeof node !== "object") return false;
455
- if (Array.isArray(node)) return node.some(walk);
456
- if (node.kind === "FuncLit")
457
- return this._nodeMutatesVar(node.body, name);
458
- return Object.keys(node).some(
459
- (k) => !k.startsWith("_") && walk(node[k]),
460
- );
461
- };
462
- ctx.mut.set(key, walk(ctx.body));
463
- }
464
- return ctx.mut.get(key);
482
+ return closureMutates(this._fnCtx?.body, name, this._fnCtx?.mut);
465
483
  },
466
484
 
467
485
  _genUnaryExpr(expr) {
@@ -600,7 +618,7 @@ export const expressionGenMethods = {
600
618
  this._withFnCtx(expr.body, () =>
601
619
  this.indented(() => {
602
620
  this._emitParamCopies(expr.params);
603
- this._genBody(expr.body);
621
+ this._genBody(expr.body, expr);
604
622
  }),
605
623
  );
606
624
  const body = this.out.join("\n");
@@ -689,12 +707,20 @@ export const expressionGenMethods = {
689
707
  return this._genTimeMethodCall(expr.func.field, expr);
690
708
  if (recvName === "testing.T")
691
709
  return this._genTestingMethodCall(expr.func.field, expr);
710
+ if (recvName?.startsWith("shared.") && expr.func.field === "Subarray") {
711
+ const args = expr.args.map((a) => this.genExpr(a)).join(", ");
712
+ return `${this.genExpr(expr.func.expr)}.subarray(${args})`;
713
+ }
692
714
  return undefined;
693
715
  },
694
716
 
695
717
  _genSelectorCall(expr) {
696
- if (this._isErrorMethodCall(expr))
697
- return `${this.genExpr(expr.func.expr)}.Error()`;
718
+ if (this._isErrorMethodCall(expr)) {
719
+ const recv = this.genExpr(expr.func.expr);
720
+ if (!this.strict) return `${recv}.Error()`;
721
+ this.useHelper("nilCall");
722
+ return `__nilcall(${recv}).Error()`;
723
+ }
698
724
  if (expr.func.expr.kind === "Ident") {
699
725
  const ns = expr.func.expr.name;
700
726
  const fn = expr.func.field;
@@ -703,7 +729,25 @@ export const expressionGenMethods = {
703
729
  }
704
730
  const recvType = expr.func.expr._type;
705
731
  const recvName = this._resolveRecvName(recvType);
706
- return this._genReceiverTypeCall(recvName, expr);
732
+ const typed = this._genReceiverTypeCall(recvName, expr);
733
+ if (typed !== undefined) return typed;
734
+ if (this.strict && this._isInterfaceType(recvType)) {
735
+ // Go panics with a nil-dereference runtime error; JS would throw a
736
+ // TypeError with a different message.
737
+ this.useHelper("nilCall");
738
+ const recv = this.genExpr(expr.func.expr);
739
+ const args = expr.args
740
+ .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
741
+ .join(", ");
742
+ return `__nilcall(${recv}).${expr.func.field}(${args})`;
743
+ }
744
+ return undefined;
745
+ },
746
+
747
+ _isInterfaceType(t) {
748
+ if (!t) return false;
749
+ if (t.kind === "interface") return true;
750
+ return t.kind === "named" && t.underlying?.kind === "interface";
707
751
  },
708
752
 
709
753
  _genBuiltinLen(expr) {
@@ -714,7 +758,7 @@ export const expressionGenMethods = {
714
758
  if (wrapField) return `${this.genExpr(arg)}.${wrapField}.length`;
715
759
  const js = this.genExpr(arg);
716
760
  if (t?.kind === "map") return `Object.keys(${js}).length`;
717
- this._usesLen = true;
761
+ this.useHelper("len");
718
762
  return `__len(${js})`;
719
763
  },
720
764
 
@@ -747,7 +791,7 @@ export const expressionGenMethods = {
747
791
  a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a),
748
792
  );
749
793
  if (elems.length === 0) return this.genExpr(expr.args[0]);
750
- this._usesAppend = true;
794
+ this.useHelper("append");
751
795
  return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
752
796
  }
753
797
  const slice = this.genExpr(expr.args[0]);
@@ -755,7 +799,7 @@ export const expressionGenMethods = {
755
799
  .slice(1)
756
800
  .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a)));
757
801
  if (elems.length === 0) return slice;
758
- this._usesAppend = true;
802
+ this.useHelper("append");
759
803
  const sliceType = expr._type ?? expr.args[0]._type;
760
804
  const typedCtor = isTypedArraySlice(sliceType)
761
805
  ? typedArrayConstructorForElem(
@@ -1227,10 +1271,10 @@ export const expressionGenMethods = {
1227
1271
  case "-":
1228
1272
  return `{ re: ${l}.re - ${r}.re, im: ${l}.im - ${r}.im }`;
1229
1273
  case "*":
1230
- this._usesCmul = true;
1274
+ this.useHelper("cmul");
1231
1275
  return `__cmul(${l}, ${r})`;
1232
1276
  case "/":
1233
- this._usesCdiv = true;
1277
+ this.useHelper("cdiv");
1234
1278
  return `__cdiv(${l}, ${r})`;
1235
1279
  case "==":
1236
1280
  return `(${l}.re === ${r}.re && ${l}.im === ${r}.im)`;
@@ -1248,11 +1292,17 @@ export const expressionGenMethods = {
1248
1292
  return this._genComplexBinary(expr);
1249
1293
 
1250
1294
  if (this._isStructEquality(expr)) {
1251
- this._usesEqual = true;
1295
+ this.useHelper("equal");
1252
1296
  const cmp = `__equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
1253
1297
  return expr.op === "==" ? cmp : `!${cmp}`;
1254
1298
  }
1255
1299
 
1300
+ if (this.strict && this._needsStrictOp(expr.op, expr._type)) {
1301
+ const l = this.genExpr(expr.left);
1302
+ const r = this.genExpr(expr.right);
1303
+ return this._genStrictBinary(expr.op, l, r, expr._type);
1304
+ }
1305
+
1256
1306
  const jsOp = this._emittedBinaryOp(expr) ?? "/";
1257
1307
  const operand = (child, isRight) =>
1258
1308
  wrapForJsOp(
@@ -1327,7 +1377,7 @@ export const expressionGenMethods = {
1327
1377
  return this._genSliceTypeConversion(expr, inner, t);
1328
1378
  const target = t?.name;
1329
1379
  if (target === "error") {
1330
- this._usesError = true;
1380
+ this.useHelper("error");
1331
1381
  return `__error(${inner})`;
1332
1382
  }
1333
1383
  if (target && this.namedWrapperNames.has(target))
@@ -1422,4 +1472,142 @@ export const expressionGenMethods = {
1422
1472
  return inner;
1423
1473
  }
1424
1474
  },
1475
+
1476
+ _typeName(t) {
1477
+ if (!t) return null;
1478
+ if (t.kind === "untyped") return t.base;
1479
+ if (t.kind === "named") return this._typeName(t.underlying);
1480
+ if (t.kind === "basic") return t.name;
1481
+ return null;
1482
+ },
1483
+
1484
+ _isStrictAtomicOp(_op, typeName) {
1485
+ return STRICT_NUMERIC_TYPES.has(typeName);
1486
+ },
1487
+
1488
+ _needsStrictOp(op, type) {
1489
+ if (!STRICT_OPS.has(op)) return false;
1490
+ if (this._isStrictAtomicOp(op, this._typeName(type))) return true;
1491
+ return this.isIntType(type) && (op === "/" || op === "%");
1492
+ },
1493
+
1494
+ // Emits `lhs = strict(lhs op rhs)` for any lvalue shape; returns false when not applicable.
1495
+ // Indexed / nested targets are split into object + key temps so sub-expressions run once.
1496
+ _genStrictCompound(lhsNode, op, rhsJs, srcLine = null, lhsJs = null) {
1497
+ const type = lhsNode._type;
1498
+ if (!this._needsStrictOp(op, type)) return false;
1499
+ if (lhsNode.kind === "IndexExpr" && lhsNode.expr?._type?.kind === "map")
1500
+ return false;
1501
+ const l = lhsJs ?? this._genAssignLhsExpr(lhsNode);
1502
+ if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(l)) {
1503
+ this.line(
1504
+ `${l} = ${this._genStrictBinary(op, l, rhsJs, type)};`,
1505
+ srcLine,
1506
+ );
1507
+ return true;
1508
+ }
1509
+ const split = splitLastAccess(l);
1510
+ if (!split) return false;
1511
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
1512
+ const o = `__o${this._tmpCounter}`;
1513
+ if (split.key != null) {
1514
+ const k = `__k${this._tmpCounter}`;
1515
+ const target = `${o}[${k}]`;
1516
+ this.line(
1517
+ `{ const ${o} = ${split.obj}, ${k} = ${split.key}; ${target} = ${this._genStrictBinary(op, target, rhsJs, type)}; }`,
1518
+ srcLine,
1519
+ );
1520
+ } else {
1521
+ const target = `${o}.${split.prop}`;
1522
+ this.line(
1523
+ `{ const ${o} = ${split.obj}; ${target} = ${this._genStrictBinary(op, target, rhsJs, type)}; }`,
1524
+ srcLine,
1525
+ );
1526
+ }
1527
+ return true;
1528
+ },
1529
+
1530
+ _genStrictBinary(op, l, r, type) {
1531
+ const tName = this._typeName(type);
1532
+ if (tName === "float32") {
1533
+ return `Math.fround(${l} ${op} ${r})`;
1534
+ }
1535
+ if (tName === "int32" || tName === "rune") {
1536
+ if (op === "*") return `Math.imul(${l}, ${r})`;
1537
+ if (op === "/")
1538
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (Math.trunc((${l}) / (${r})) | 0))`;
1539
+ if (op === "%")
1540
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((${l}) % (${r})) | 0))`;
1541
+ if (op === "<<" || op === ">>") {
1542
+ return `((${r}) >= 32 ? (${op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0"}) : ((${l}) ${op} (${r}))) | 0`;
1543
+ }
1544
+ if (op === "&^") return `((${l}) & ~(${r})) | 0`;
1545
+ return `((${l} ${op} ${r}) | 0)`;
1546
+ }
1547
+ if (tName === "uint32") {
1548
+ if (op === "/")
1549
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (Math.trunc((${l}) / (${r})) >>> 0))`;
1550
+ if (op === "%")
1551
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((${l}) % (${r})) >>> 0))`;
1552
+ if (op === "<<" || op === ">>") {
1553
+ const jsOp = op === ">>" ? ">>>" : "<<";
1554
+ return `((${r}) >= 32 ? 0 : ((${l}) ${jsOp} (${r}))) >>> 0`;
1555
+ }
1556
+ if (op === "&^") return `((${l}) & ~(${r})) >>> 0`;
1557
+ return `((${l} ${op} ${r}) >>> 0)`;
1558
+ }
1559
+ if (tName === "int8") {
1560
+ if (op === "/")
1561
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : ((Math.trunc((${l}) / (${r})) << 24) >> 24))`;
1562
+ if (op === "%")
1563
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((((${l}) % (${r})) << 24) >> 24)))`;
1564
+ if (op === "<<" || op === ">>") {
1565
+ return `((${r}) >= 8 ? (${op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0"}) : (((${l}) ${op} (${r})) << 24 >> 24))`;
1566
+ }
1567
+ if (op === "&^") return `((((${l}) & ~(${r})) << 24) >> 24)`;
1568
+ return `((((${l} ${op} ${r})) << 24) >> 24)`;
1569
+ }
1570
+ if (tName === "int16") {
1571
+ if (op === "/")
1572
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : ((Math.trunc((${l}) / (${r})) << 16) >> 16))`;
1573
+ if (op === "%")
1574
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((((${l}) % (${r})) << 16) >> 16)))`;
1575
+ if (op === "<<" || op === ">>") {
1576
+ return `((${r}) >= 16 ? (${op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0"}) : (((${l}) ${op} (${r})) << 16 >> 16))`;
1577
+ }
1578
+ if (op === "&^") return `((((${l}) & ~(${r})) << 16) >> 16)`;
1579
+ return `((((${l} ${op} ${r})) << 16) >> 16)`;
1580
+ }
1581
+ if (tName === "uint8" || tName === "byte") {
1582
+ if (op === "/")
1583
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (Math.trunc((${l}) / (${r})) & 0xFF))`;
1584
+ if (op === "%")
1585
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((${l}) % (${r})) & 0xFF))`;
1586
+ if (op === "<<" || op === ">>") {
1587
+ const jsOp = op === ">>" ? ">>>" : "<<";
1588
+ return `((${r}) >= 8 ? 0 : (((${l}) ${jsOp} (${r})) & 0xFF))`;
1589
+ }
1590
+ if (op === "&^") return `(((${l}) & ~(${r})) & 0xFF)`;
1591
+ return `((${l} ${op} ${r}) & 0xFF)`;
1592
+ }
1593
+ if (tName === "uint16") {
1594
+ if (op === "/")
1595
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (Math.trunc((${l}) / (${r})) & 0xFFFF))`;
1596
+ if (op === "%")
1597
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((${l}) % (${r})) & 0xFFFF))`;
1598
+ if (op === "<<" || op === ">>") {
1599
+ const jsOp = op === ">>" ? ">>>" : "<<";
1600
+ return `((${r}) >= 16 ? 0 : (((${l}) ${jsOp} (${r})) & 0xFFFF))`;
1601
+ }
1602
+ if (op === "&^") return `(((${l}) & ~(${r})) & 0xFFFF)`;
1603
+ return `((${l} ${op} ${r}) & 0xFFFF)`;
1604
+ }
1605
+ if (this.isIntType(type)) {
1606
+ if (op === "/")
1607
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : Math.trunc((${l}) / (${r})))`;
1608
+ if (op === "%")
1609
+ return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : ((${l}) % (${r})))`;
1610
+ }
1611
+ return `${l} ${op} ${r}`;
1612
+ },
1425
1613
  };