gofront 1.3.5 → 1.3.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.
@@ -26,9 +26,11 @@ import {
26
26
  HELPER_EQUAL,
27
27
  HELPER_ERROR,
28
28
  HELPER_ERROR_IS,
29
+ HELPER_IFACE_BOX,
29
30
  HELPER_LEN,
30
31
  HELPER_PATH_CLEAN,
31
32
  HELPER_S,
33
+ HELPER_SCLONE,
32
34
  HELPER_SPRINTF,
33
35
  HELPER_TESTING,
34
36
  HELPER_TIME_FMT,
@@ -39,6 +41,46 @@ import { statementGenMethods } from "./statements.js";
39
41
  import { stdlibGenMethods } from "./stdlib/index.js";
40
42
  import { templGenMethods } from "./templ.js";
41
43
 
44
+ // Valid Go identifiers that cannot be used as JS bindings; emitted with a `$` suffix.
45
+ const JS_RESERVED = new Set([
46
+ "arguments",
47
+ "await",
48
+ "catch",
49
+ "class",
50
+ "debugger",
51
+ "delete",
52
+ "do",
53
+ "enum",
54
+ "eval",
55
+ "export",
56
+ "extends",
57
+ "finally",
58
+ "function",
59
+ "implements",
60
+ "in",
61
+ "instanceof",
62
+ "let",
63
+ "new",
64
+ "null",
65
+ "private",
66
+ "protected",
67
+ "public",
68
+ "static",
69
+ "super",
70
+ "this",
71
+ "throw",
72
+ "try",
73
+ "typeof",
74
+ "undefined",
75
+ "void",
76
+ "while",
77
+ "with",
78
+ "yield",
79
+ ]);
80
+
81
+ const jsSafeName = (n) =>
82
+ typeof n === "string" && JS_RESERVED.has(n) ? `${n}$` : n;
83
+
42
84
  export class CodeGen {
43
85
  // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
44
86
  // bundledPackages: Set<string> — GoFront package names bundled inline;
@@ -74,6 +116,10 @@ export class CodeGen {
74
116
  this._usesTimeFmt = false;
75
117
  this._usesTimeParse = false;
76
118
  this._usesTesting = false;
119
+ this._usesSClone = false;
120
+ this._usesIfaceBox = false;
121
+ // Per-function context for Go value semantics (see _withFnCtx)
122
+ this._fnCtx = null;
77
123
  // Iterator (range-over-func) context
78
124
  this._inIteratorBody = false;
79
125
  this._iterDepth = 0;
@@ -153,6 +199,7 @@ export class CodeGen {
153
199
 
154
200
  generate(program, options = {}) {
155
201
  const isTest = options.isTest ?? false;
202
+ this._renameReservedIdents(program.decls);
156
203
  const methods = this._collectDecls(program);
157
204
  this._emitJsImports();
158
205
  this._emitTypeDecls(program, methods);
@@ -189,6 +236,82 @@ export class CodeGen {
189
236
  return initNames;
190
237
  }
191
238
 
239
+ _renameParams(params) {
240
+ for (const p of params ?? []) p.name = jsSafeName(p.name);
241
+ }
242
+
243
+ _renameNamedReturns(node) {
244
+ for (const r of node.returnType?._namedReturns ?? [])
245
+ r.name = jsSafeName(r.name);
246
+ }
247
+
248
+ _renameSignature(node) {
249
+ this._renameParams(node.params);
250
+ this._renameNamedReturns(node);
251
+ }
252
+
253
+ _renameIdentNode(node) {
254
+ if (!node._isStructKey && node._type?.kind !== "builtin")
255
+ node.name = jsSafeName(node.name);
256
+ }
257
+
258
+ _renameFuncDeclNode(node) {
259
+ node.name = jsSafeName(node.name);
260
+ this._renameSignature(node);
261
+ }
262
+
263
+ _renameMethodDeclNode(node) {
264
+ node.recvName = jsSafeName(node.recvName);
265
+ this._renameSignature(node);
266
+ }
267
+
268
+ _renameDeclSpecs(node) {
269
+ for (const spec of node.decls ?? [])
270
+ spec.names = spec.names.map(jsSafeName);
271
+ }
272
+
273
+ _renameTypeSwitchNode(node) {
274
+ node.assign = jsSafeName(node.assign);
275
+ }
276
+
277
+ _markStructKeys(node) {
278
+ const isStruct =
279
+ this._isStructType(node._type) ||
280
+ node.elems?.some((e) => e._positionalField);
281
+ if (!isStruct) return;
282
+ for (const e of node.elems ?? [])
283
+ if (e.kind === "KeyValueExpr" && e.key?.kind === "Ident")
284
+ e.key._isStructKey = true;
285
+ }
286
+
287
+ static _RENAME_HANDLERS = {
288
+ Ident: "_renameIdentNode",
289
+ FuncDecl: "_renameFuncDeclNode",
290
+ MethodDecl: "_renameMethodDeclNode",
291
+ FuncLit: "_renameSignature",
292
+ TemplDecl: "_renameSignature",
293
+ VarDecl: "_renameDeclSpecs",
294
+ ConstDecl: "_renameDeclSpecs",
295
+ TypeSwitchStmt: "_renameTypeSwitchNode",
296
+ CompositeLit: "_markStructKeys",
297
+ };
298
+
299
+ _renameReservedIdents(node) {
300
+ if (!node || typeof node !== "object") return;
301
+ if (Array.isArray(node)) {
302
+ for (const n of node) this._renameReservedIdents(n);
303
+ return;
304
+ }
305
+ if (node._jsRenamed) return;
306
+ node._jsRenamed = true;
307
+ const handler = CodeGen._RENAME_HANDLERS[node.kind];
308
+ if (handler) this[handler](node);
309
+ for (const key of Object.keys(node)) {
310
+ if (key.startsWith("_")) continue;
311
+ this._renameReservedIdents(node[key]);
312
+ }
313
+ }
314
+
192
315
  // Collects struct names, method map, and named wrapper names from program decls.
193
316
  // Returns the method map (typeName → MethodDecl[]).
194
317
  _collectStructNames(program) {
@@ -236,6 +359,8 @@ export class CodeGen {
236
359
  [this._usesLen, HELPER_LEN],
237
360
  [this._usesAppend, HELPER_APPEND],
238
361
  [this._usesSliceGuard, HELPER_S],
362
+ [this._usesSClone, HELPER_SCLONE],
363
+ [this._usesIfaceBox, HELPER_IFACE_BOX],
239
364
  [this._usesEqual, HELPER_EQUAL],
240
365
  [this._usesCmul, HELPER_CMUL],
241
366
  [this._usesCdiv, HELPER_CDIV],
@@ -357,13 +482,15 @@ export class CodeGen {
357
482
  this.line(`constructor(${params}) {`);
358
483
  this.indented(() => {
359
484
  const first = binding(fields[0]);
360
- const isFirstObj = fields[0].zero === "{}" || fields[0].zero === "null";
361
- const optCheck = isFirstObj
362
- ? `("${fields[0].name}" in ${first})`
363
- : "true";
364
- this.line(
365
- `if (typeof ${first} === "object" && ${first} !== null && ${first}.constructor === Object && ${optCheck}) { Object.assign(this, ${first}); return; }`,
366
- );
485
+ // Legacy `new T({...})` support; only unambiguous when field 0 is a primitive.
486
+ if (/^(0|""|false)$/.test(fields[0].zero)) {
487
+ const pattern = fields
488
+ .map((f) => `${f.name}: ${binding(f)} = ${f.zero}`)
489
+ .join(", ");
490
+ this.line(
491
+ `if (typeof ${first} === "object" && ${first} !== null && ${first}.constructor === Object) ({ ${pattern} } = ${first});`,
492
+ );
493
+ }
367
494
  for (const f of fields) {
368
495
  this.line(`this.${f.name} = ${binding(f)};`);
369
496
  }
@@ -371,6 +498,30 @@ export class CodeGen {
371
498
  this.line("}");
372
499
  }
373
500
 
501
+ _genStructClone(name, structTypeAst, fields) {
502
+ const resolved = this.checker?.types.get(name)?.underlying;
503
+ const astTypes = new Map();
504
+ for (const f of structTypeAst?.fields ?? [])
505
+ for (const n of f.names ?? []) astTypes.set(n, f.type);
506
+ const args = fields.map((f) => {
507
+ const t =
508
+ resolved?.fields?.get(f.name) ??
509
+ this._typeFromNode(astTypes.get(f.name));
510
+ return this._cloneJs(t, `this.${f.name}`);
511
+ });
512
+ this.blank();
513
+ this.line(`__clone() { return new ${name}(${args.join(", ")}); }`);
514
+ }
515
+
516
+ _structDefinesValue(name, fields, methodDecls) {
517
+ if (fields.some((f) => f.name === "value")) return true;
518
+ if (methodDecls.some((m) => m.name === "value")) return true;
519
+ const resolved = this.checker?.types.get(name)?.underlying;
520
+ return Boolean(
521
+ resolved?.fields?.has?.("value") || resolved?.methods?.has?.("value"),
522
+ );
523
+ }
524
+
374
525
  genStruct(name, structTypeAst, methodDecls) {
375
526
  const fields = this._collectStructFields(name, structTypeAst);
376
527
  this.structFields.set(name, fields);
@@ -382,11 +533,15 @@ export class CodeGen {
382
533
  this.genMethod(m);
383
534
  }
384
535
  this._genEmbeddedMethodStubs(name, methodDecls);
536
+ this._genStructClone(name, structTypeAst, fields);
385
537
  });
386
538
  this.line("}");
387
- this.line(
388
- `Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
389
- );
539
+ // Lets generic `p.value` dereferences resolve to the struct itself.
540
+ if (!this._structDefinesValue(name, fields, methodDecls)) {
541
+ this.line(
542
+ `Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
543
+ );
544
+ }
390
545
  }
391
546
 
392
547
  _genSingleEmbedStubs(embed, declared) {
@@ -421,22 +576,40 @@ export class CodeGen {
421
576
  this._boxedVars = new Set();
422
577
  this._scanAddressTaken(decl.body);
423
578
  const prevUnwrapped = this._unwrappedRecv;
424
- this.indented(() => {
425
- if (decl.recvName && decl.recvName !== "_") {
426
- if (recvField) {
427
- this.line(`const ${decl.recvName} = this.${recvField};`);
428
- this._unwrappedRecv = decl.recvName;
429
- } else {
430
- this.line(`const ${decl.recvName} = this;`);
579
+ this._withFnCtx(decl.body, () =>
580
+ this.indented(() => {
581
+ if (decl.recvName && decl.recvName !== "_") {
582
+ if (recvField) {
583
+ this.line(`const ${decl.recvName} = this.${recvField};`);
584
+ this._unwrappedRecv = decl.recvName;
585
+ } else {
586
+ this.line(`const ${decl.recvName} = ${this._receiverValue(decl)};`);
587
+ }
431
588
  }
432
- }
433
- this._withNamedReturns(decl, () => this._genBody(decl.body));
434
- });
589
+ this._emitParamCopies(decl.params);
590
+ this._withNamedReturns(decl, () => this._genBody(decl.body));
591
+ }),
592
+ );
435
593
  this._unwrappedRecv = prevUnwrapped;
436
594
  this._boxedVars = prevBoxed;
437
595
  this.line("}");
438
596
  }
439
597
 
598
+ // Value receivers are copies in Go; only materialize the copy when the body mutates it.
599
+ _receiverValue(decl) {
600
+ const name = decl.recvName;
601
+ if (decl.recvPointer || !this.structNames.has(decl.recvType.name)) {
602
+ this._markOwnership(name, false);
603
+ return "this";
604
+ }
605
+ if (this._fnMutates(name)) {
606
+ this._markOwnership(name, true);
607
+ return "this.__clone()";
608
+ }
609
+ this._markOwnership(name, false);
610
+ return "this";
611
+ }
612
+
440
613
  // Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
441
614
  // non-struct type emitted as a wrapper class, or null otherwise.
442
615
  // Pass `expr` so we can skip unwrapping when the expression is the method receiver
@@ -470,8 +643,11 @@ export class CodeGen {
470
643
  const prevBoxed = this._boxedVars;
471
644
  this._boxedVars = new Set();
472
645
  this._scanAddressTaken(decl.body);
473
- this.indented(() =>
474
- this._withNamedReturns(decl, () => this._genBody(decl.body)),
646
+ this._withFnCtx(decl.body, () =>
647
+ this.indented(() => {
648
+ this._emitParamCopies(decl.params);
649
+ this._withNamedReturns(decl, () => this._genBody(decl.body));
650
+ }),
475
651
  );
476
652
  this._boxedVars = prevBoxed;
477
653
  this.line("}");
@@ -483,8 +659,10 @@ export class CodeGen {
483
659
  if (named) {
484
660
  // Emit zero-value declarations for named return vars
485
661
  for (const { name, type } of named) {
486
- if (name)
662
+ if (name) {
663
+ this._markOwnership(name, true);
487
664
  this.line(`let ${name} = ${this.zeroValueForTypeNode(type)};`);
665
+ }
488
666
  }
489
667
  this.namedReturnVars = named.map((r) => r.name).filter(Boolean);
490
668
  } else {
@@ -565,8 +743,11 @@ export class CodeGen {
565
743
  genVarDecl(decl) {
566
744
  for (const spec of decl.decls) {
567
745
  if (spec.value) {
568
- const vals = spec.value.map((v) => {
569
- const js = this.genExpr(v);
746
+ const paired = spec.value.length === spec.names.length;
747
+ const vals = spec.value.map((v, i) => {
748
+ const js = paired
749
+ ? this._genDeclValue(spec.names[i], v)
750
+ : this.genExpr(v);
570
751
  // Wrap numeric values assigned to complex-typed vars
571
752
  if (
572
753
  spec.type?.name === "complex128" ||
@@ -590,6 +771,7 @@ export class CodeGen {
590
771
  } else {
591
772
  const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
592
773
  for (const name of spec.names) {
774
+ this._markOwnership(name, true);
593
775
  const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
594
776
  this.line(`let ${name} = ${val};`);
595
777
  }
@@ -3,7 +3,7 @@
3
3
  // multiple compiled files bundled together can safely repeat the declaration.
4
4
 
5
5
  export const HELPER_LEN = `var __len = __len || function(a) {
6
- if (a && typeof a === 'object' && !Array.isArray(a)) return Object.keys(a).length;
6
+ if (a && typeof a === 'object' && !Array.isArray(a) && !ArrayBuffer.isView(a)) return Object.keys(a).length;
7
7
  return a?.length ?? 0;
8
8
  };`;
9
9
 
@@ -19,6 +19,27 @@ export const HELPER_APPEND = `var __append = __append || function(a, ...b) {
19
19
 
20
20
  export const HELPER_S = "var __s = __s || function(a) { return a || []; };";
21
21
 
22
+ // Copy of a struct value whose class is not statically known.
23
+ export const HELPER_SCLONE = `var __sclone = __sclone || function(x) {
24
+ if (x === null || typeof x !== "object") return x;
25
+ if (typeof x.__clone === "function") return x.__clone();
26
+ return Object.assign(Object.create(Object.getPrototypeOf(x)), x);
27
+ };`;
28
+
29
+ // Interface boxing: tag struct values (__v) vs struct pointers (__p) so type
30
+ // switches can tell T from *T; tags are non-enumerable so __equal/JSON ignore them.
31
+ export const HELPER_IFACE_BOX = `var __ifv = __ifv || function(x) {
32
+ if (x === null || typeof x !== "object") return x;
33
+ const c = typeof x.__clone === "function" ? x.__clone() : Object.assign(Object.create(Object.getPrototypeOf(x)), x);
34
+ Object.defineProperty(c, "__v", { value: true, configurable: true });
35
+ return c;
36
+ };
37
+ var __ifp = __ifp || function(x) {
38
+ if (x !== null && typeof x === "object" && x.__p !== true && Object.isExtensible(x))
39
+ Object.defineProperty(x, "__p", { value: true, configurable: true });
40
+ return x;
41
+ };`;
42
+
22
43
  export const HELPER_EQUAL = `var __equal = __equal || function __equal(a, b) {
23
44
  if (a === b) return true;
24
45
  if (a === null || b === null) return false;
@@ -1,6 +1,6 @@
1
1
  // CodeGen statement methods — installed as a mixin on CodeGen.prototype.
2
2
 
3
- import { isComplex } from "../typechecker/types.js";
3
+ import { isComplex, isNumeric } from "../typechecker/types.js";
4
4
 
5
5
  /** @typedef {import('./index.js').CodeGen} CodeGen */
6
6
 
@@ -122,14 +122,20 @@ export const statementGenMethods = {
122
122
 
123
123
  _genDefineMixedRedecl(lhs, rhs, redecls) {
124
124
  if (rhs.length === 1 && lhs.length > 1) {
125
- const tmp = "__t";
126
- this.line(`let ${tmp} = ${rhs[0]};`);
125
+ this._tmpCounter = (this._tmpCounter ?? 0) + 1;
126
+ const tmp = `__t${this._tmpCounter}`;
127
+ this.line(`const ${tmp} = ${rhs[0]};`);
127
128
  for (let i = 0; i < lhs.length; i++) {
129
+ if (lhs[i] === "_") continue;
128
130
  const prefix = redecls[i] ? "" : "let ";
129
131
  this.line(`${prefix}${lhs[i]} = ${tmp}[${i}];`);
130
132
  }
131
133
  } else {
132
134
  for (let i = 0; i < lhs.length; i++) {
135
+ if (lhs[i] === "_") {
136
+ this.line(`${rhs[i]};`);
137
+ continue;
138
+ }
133
139
  const prefix = redecls[i] ? "" : "let ";
134
140
  this.line(`${prefix}${lhs[i]} = ${rhs[i]};`);
135
141
  }
@@ -142,7 +148,13 @@ export const statementGenMethods = {
142
148
  this._genCommaOkMapIndex(stmt, rhsNode, true);
143
149
  return;
144
150
  }
145
- const rhs = stmt.rhs.map((e) => this.genExpr(e));
151
+ const paired = stmt.rhs.length === stmt.lhs.length;
152
+ const rhs = stmt.rhs.map((e, i) => {
153
+ const l = paired ? stmt.lhs[i] : null;
154
+ if (!l || l.name === "_") return this.genExpr(e);
155
+ if (l._redecl) return this.genValueExpr(e);
156
+ return this._genDeclValue(l.name, e);
157
+ });
146
158
  const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
147
159
  const redecls = stmt.lhs.map((e) => !!e._redecl);
148
160
  const anyRedecl = redecls.some((r) => r);
@@ -150,7 +162,8 @@ export const statementGenMethods = {
150
162
  this._genDefineSingleVar(lhs, rhs, redecls);
151
163
  } else if (!anyRedecl) {
152
164
  const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
153
- this.line(`let [${lhs.join(", ")}] = ${rhsStr};`);
165
+ const names = lhs.map((n) => (n === "_" ? "" : n));
166
+ this.line(`let [${names.join(", ")}] = ${rhsStr};`);
154
167
  } else {
155
168
  this._genDefineMixedRedecl(lhs, rhs, redecls);
156
169
  }
@@ -218,7 +231,12 @@ export const statementGenMethods = {
218
231
  this._genStructDerefAssign(stmt);
219
232
  return;
220
233
  }
221
- const rhs = stmt.rhs.map((e) => this.genExpr(e));
234
+ const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
235
+ const rhs = stmt.rhs.map((e, i) =>
236
+ paired && stmt.lhs[i].name !== "_"
237
+ ? this.genValueExpr(e)
238
+ : this.genExpr(e),
239
+ );
222
240
  this._markIndexLvalues(stmt.lhs);
223
241
  const lhs = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
224
242
  const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
@@ -243,7 +261,14 @@ export const statementGenMethods = {
243
261
 
244
262
  _genStructDerefAssign(stmt) {
245
263
  const target = this.genExpr(stmt.lhs[0].operand);
246
- const source = this.genExpr(stmt.rhs[0]);
264
+ const rhsNode = stmt.rhs[0];
265
+ const fields = this._aggregateBase(rhsNode._type)?.fields;
266
+ const shallowOk =
267
+ fields instanceof Map &&
268
+ ![...fields.values()].some((t) => this._aggregateBase(t));
269
+ const source = shallowOk
270
+ ? this.genExpr(rhsNode)
271
+ : this.genValueExpr(rhsNode);
247
272
  this.line(`Object.assign(${target}, ${source});`);
248
273
  },
249
274
 
@@ -293,7 +318,7 @@ export const statementGenMethods = {
293
318
  }
294
319
  const vals =
295
320
  stmt.values.length > 0
296
- ? stmt.values.map((v) => this.genExpr(v))
321
+ ? stmt.values.map((v) => this._genReturnValue(v))
297
322
  : (this.namedReturnVars ?? []);
298
323
  const stored = vals.length === 1 ? vals[0] : `[${vals.join(", ")}]`;
299
324
  this.line(
@@ -318,10 +343,10 @@ export const statementGenMethods = {
318
343
  this.line("return;");
319
344
  }
320
345
  } else if (stmt.values.length === 1) {
321
- this.line(`return ${this.genExpr(stmt.values[0])};`);
346
+ this.line(`return ${this._genReturnValue(stmt.values[0])};`);
322
347
  } else {
323
348
  this.line(
324
- `return [${stmt.values.map((v) => this.genExpr(v)).join(", ")}];`,
349
+ `return [${stmt.values.map((v) => this._genReturnValue(v)).join(", ")}];`,
325
350
  );
326
351
  }
327
352
  },
@@ -425,12 +450,7 @@ export const statementGenMethods = {
425
450
  },
426
451
 
427
452
  _isIntRangeType(iterType) {
428
- return (
429
- (iterType?.kind === "basic" &&
430
- (iterType.name === "int" || iterType.name === "float64")) ||
431
- (iterType?.kind === "untyped" &&
432
- (iterType.base === "int" || iterType.base === "float64"))
433
- );
453
+ return isNumeric(iterType);
434
454
  },
435
455
 
436
456
  _isMapRangeType(iterType) {
@@ -447,47 +467,152 @@ export const statementGenMethods = {
447
467
  );
448
468
  },
449
469
 
450
- _emitSliceLoopHeader(rawIdx, iteree, isAssign, d) {
470
+ _rangeVarTarget(e, isAssign) {
471
+ if (e.kind === "Ident") {
472
+ if (e.name === "_") return null;
473
+ const boxed = this._boxedVars.has(e.name);
474
+ if (isAssign) return boxed ? `${e.name}.value = ` : `${e.name} = `;
475
+ return boxed ? { name: e.name, boxed } : { name: e.name };
476
+ }
477
+ this._markIndexLvalues([e]);
478
+ return `${this._genAssignLhsExpr(e)} = `;
479
+ },
480
+
481
+ _emitRangeVar(e, valueJs, isAssign) {
482
+ if (!e) return;
483
+ const target = this._rangeVarTarget(e, isAssign);
484
+ if (target === null) return;
485
+ if (typeof target === "string") {
486
+ this.line(`${target}${valueJs};`);
487
+ return;
488
+ }
489
+ const v = target.boxed ? `{ value: ${valueJs} }` : valueJs;
490
+ this.line(`let ${target.name} = ${v};`);
491
+ },
492
+
493
+ // True when the body re-declares one of `names` at its top level (legal Go shadowing).
494
+ _bodyRedeclares(body, names) {
495
+ if (names.length === 0) return false;
496
+ for (const s of body.stmts ?? []) {
497
+ if (s.kind === "DefineStmt" && s.lhs.some((e) => names.includes(e.name)))
498
+ return true;
499
+ if (
500
+ (s.kind === "VarDecl" || s.kind === "ConstDecl") &&
501
+ s.decls?.some((d) => d.names?.some((n) => names.includes(n)))
502
+ )
503
+ return true;
504
+ }
505
+ return false;
506
+ },
507
+
508
+ // Range value variable: aliases the element unless the body writes the variable or the collection.
509
+ _rangeElemValue(stmt, valNode, js, isAssign) {
510
+ const iterNode = stmt.init.rhs[0].expr;
511
+ const iterType = iterNode._type;
512
+ const base = iterType?.kind === "named" ? iterType.underlying : iterType;
513
+ const elemType = base?.elem;
514
+ if (!valNode || !this._aggregateBase(elemType)) return js;
515
+ if (isAssign || valNode.kind !== "Ident")
516
+ return this._cloneJs(elemType, js);
517
+ if (valNode.name === "_") return js;
518
+ const root = this._rootIdentName(iterNode);
519
+ const mutated =
520
+ this._fnMutates(valNode.name) ||
521
+ (root !== null && this._nodeMutatesVar(stmt.body, root));
522
+ this._markOwnership(valNode.name, mutated);
523
+ return mutated ? this._cloneJs(elemType, js) : js;
524
+ },
525
+
526
+ _genSliceRangeFor(stmt, iteree, isAssign) {
527
+ this._loopDepth = this._loopDepth ?? 0;
528
+ const d = this._loopDepth++;
529
+ const [idxNode, valNode] = stmt.init.lhs;
530
+ const i = `__i${d}`;
451
531
  const arr = `__arr${d}`;
452
532
  const len = `__len${d}`;
453
- const hasIdx = rawIdx && rawIdx !== "_";
454
- if (isAssign && hasIdx) {
455
- this.line(`const ${arr} = ${iteree};`);
456
- this.line(`const ${len} = ${arr} ? ${arr}.length : 0;`);
457
- this.line(`for (${rawIdx} = 0; ${rawIdx} < ${len}; ${rawIdx}++) {`);
458
- } else {
459
- const idx = hasIdx ? rawIdx : `__i${d}`;
460
- this.line(
461
- `for (let ${idx} = 0, ${arr} = ${iteree}, ${len} = ${arr} ? ${arr}.length : 0; ${idx} < ${len}; ${idx}++) {`,
533
+ this.line(
534
+ `for (let ${i} = 0, ${arr} = ${iteree}, ${len} = ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
535
+ );
536
+ this.indented(() => {
537
+ this._emitRangeVar(idxNode, i, isAssign);
538
+ this._emitRangeVar(
539
+ valNode,
540
+ this._rangeElemValue(stmt, valNode, `${arr}[${i}]`, isAssign),
541
+ isAssign,
462
542
  );
463
- }
543
+ const declared = isAssign
544
+ ? []
545
+ : [idxNode, valNode]
546
+ .filter((e) => e?.kind === "Ident" && e.name !== "_")
547
+ .map((e) => e.name);
548
+ if (this._bodyRedeclares(stmt.body, declared)) {
549
+ this.line("{");
550
+ this.indented(() => this.genBlock(stmt.body));
551
+ this.line("}");
552
+ } else {
553
+ this.genBlock(stmt.body);
554
+ }
555
+ });
556
+ this.line("}");
557
+ this._loopDepth--;
464
558
  },
465
559
 
466
- _emitSliceValAssign(rawIdx, rawVal, isAssign, d) {
467
- if (!rawVal || rawVal === "_") return;
468
- const idx = rawIdx && rawIdx !== "_" ? rawIdx : `__i${d}`;
469
- const kw = isAssign ? "" : "let ";
470
- this.line(`${kw}${rawVal} = __arr${d}[${idx}];`);
560
+ // Whether `for i := range n` can be emitted as a plain `for (let i = 0; …)` without hidden registers.
561
+ _isSimpleIntRange(stmt, name, isAssign) {
562
+ if (isAssign) return false;
563
+ if (!name || name === "_") return true;
564
+ return !this._boxedVars.has(name) && !this._nodeAssigns(stmt.body, name);
471
565
  },
472
566
 
473
- _genSliceRangeFor(stmt, iteree, lhs, isAssign) {
567
+ _genIntRangeBody(stmt, name, isAssign) {
568
+ const wrap = !isAssign && name && this._bodyRedeclares(stmt.body, [name]);
569
+ if (!wrap) return this.genBlock(stmt.body);
570
+ this.line("{");
571
+ this.indented(() => this.genBlock(stmt.body));
572
+ this.line("}");
573
+ },
574
+
575
+ _genIntRangeFor(stmt, iteree, isAssign) {
576
+ const varNode = stmt.init.lhs[0];
577
+ const name = varNode?.kind === "Ident" ? varNode.name : null;
578
+ if (this._isSimpleIntRange(stmt, name, isAssign)) {
579
+ const v = !name || name === "_" ? "_$" : name;
580
+ this.line(`for (let ${v} = 0; ${v} < ${iteree}; ${v}++) {`);
581
+ this.indented(() => this.genBlock(stmt.body));
582
+ this.line("}");
583
+ return;
584
+ }
474
585
  this._loopDepth = this._loopDepth ?? 0;
475
- const d = this._loopDepth;
476
- this._loopDepth++;
477
- this._emitSliceLoopHeader(lhs[0], iteree, isAssign, d);
586
+ const d = this._loopDepth++;
587
+ const i = `__i${d}`;
588
+ const n = `__n${d}`;
589
+ this.line(`for (let ${i} = 0, ${n} = ${iteree}; ${i} < ${n}; ${i}++) {`);
478
590
  this.indented(() => {
479
- this._emitSliceValAssign(lhs[0], lhs[1], isAssign, d);
480
- this.genBlock(stmt.body);
591
+ this._emitRangeVar(varNode, i, isAssign);
592
+ this._genIntRangeBody(stmt, name, isAssign);
481
593
  });
482
594
  this.line("}");
483
595
  this._loopDepth--;
484
596
  },
485
597
 
486
- _genIntRangeFor(rawVar, iteree, body) {
487
- const v = rawVar === "_" ? "_$" : rawVar;
488
- this.line(`for (let ${v} = 0; ${v} < ${iteree}; ${v}++) {`);
489
- this.indented(() => this.genBlock(body));
490
- this.line("}");
598
+ // True when `node` contains an assignment or ++/-- targeting identifier `name`.
599
+ _nodeAssigns(node, name) {
600
+ if (!node || typeof node !== "object") return false;
601
+ if (Array.isArray(node))
602
+ return node.some((n) => this._nodeAssigns(n, name));
603
+ if (
604
+ (node.kind === "AssignStmt" &&
605
+ node.lhs.some((e) => e.kind === "Ident" && e.name === name)) ||
606
+ (node.kind === "IncDecStmt" &&
607
+ node.expr.kind === "Ident" &&
608
+ node.expr.name === name)
609
+ )
610
+ return true;
611
+ for (const key of Object.keys(node)) {
612
+ if (key.startsWith("_")) continue;
613
+ if (this._nodeAssigns(node[key], name)) return true;
614
+ }
615
+ return false;
491
616
  },
492
617
 
493
618
  _genIterFor(init, iterType, iteree, lhs, body) {
@@ -515,12 +640,12 @@ export const statementGenMethods = {
515
640
  : this.genExpr(range.expr);
516
641
 
517
642
  if (lhs.length <= 1 && this._isIntRangeType(iterType)) {
518
- this._genIntRangeFor(lhs[0], iteree, stmt.body);
643
+ this._genIntRangeFor(stmt, iteree, init.kind === "AssignStmt");
519
644
  return;
520
645
  }
521
646
 
522
647
  if (!this._isMapRangeType(iterType) && !this._isStringRangeType(iterType)) {
523
- this._genSliceRangeFor(stmt, iteree, lhs, init.kind === "AssignStmt");
648
+ this._genSliceRangeFor(stmt, iteree, init.kind === "AssignStmt");
524
649
  return;
525
650
  }
526
651