gofront 1.3.4 → 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;
@@ -127,14 +173,16 @@ export class CodeGen {
127
173
 
128
174
  _emitVarConstDecls(program) {
129
175
  for (const d of program.decls) {
130
- if (d.kind === "VarDecl") {
176
+ if (d.kind === "ConstDecl") {
131
177
  this._currentSrcFileIdx = d._srcFileIdx ?? 0;
132
- this.genVarDecl(d);
178
+ this.genConstDecl(d);
133
179
  this.blank();
134
180
  }
135
- if (d.kind === "ConstDecl") {
181
+ }
182
+ for (const d of program.decls) {
183
+ if (d.kind === "VarDecl") {
136
184
  this._currentSrcFileIdx = d._srcFileIdx ?? 0;
137
- this.genConstDecl(d);
185
+ this.genVarDecl(d);
138
186
  this.blank();
139
187
  }
140
188
  }
@@ -151,6 +199,7 @@ export class CodeGen {
151
199
 
152
200
  generate(program, options = {}) {
153
201
  const isTest = options.isTest ?? false;
202
+ this._renameReservedIdents(program.decls);
154
203
  const methods = this._collectDecls(program);
155
204
  this._emitJsImports();
156
205
  this._emitTypeDecls(program, methods);
@@ -187,6 +236,82 @@ export class CodeGen {
187
236
  return initNames;
188
237
  }
189
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
+
190
315
  // Collects struct names, method map, and named wrapper names from program decls.
191
316
  // Returns the method map (typeName → MethodDecl[]).
192
317
  _collectStructNames(program) {
@@ -234,6 +359,8 @@ export class CodeGen {
234
359
  [this._usesLen, HELPER_LEN],
235
360
  [this._usesAppend, HELPER_APPEND],
236
361
  [this._usesSliceGuard, HELPER_S],
362
+ [this._usesSClone, HELPER_SCLONE],
363
+ [this._usesIfaceBox, HELPER_IFACE_BOX],
237
364
  [this._usesEqual, HELPER_EQUAL],
238
365
  [this._usesCmul, HELPER_CMUL],
239
366
  [this._usesCdiv, HELPER_CDIV],
@@ -355,13 +482,15 @@ export class CodeGen {
355
482
  this.line(`constructor(${params}) {`);
356
483
  this.indented(() => {
357
484
  const first = binding(fields[0]);
358
- const isFirstObj = fields[0].zero === "{}" || fields[0].zero === "null";
359
- const optCheck = isFirstObj
360
- ? `("${fields[0].name}" in ${first})`
361
- : "true";
362
- this.line(
363
- `if (typeof ${first} === "object" && ${first} !== null && ${first}.constructor === Object && ${optCheck}) { Object.assign(this, ${first}); return; }`,
364
- );
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
+ }
365
494
  for (const f of fields) {
366
495
  this.line(`this.${f.name} = ${binding(f)};`);
367
496
  }
@@ -369,6 +498,30 @@ export class CodeGen {
369
498
  this.line("}");
370
499
  }
371
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
+
372
525
  genStruct(name, structTypeAst, methodDecls) {
373
526
  const fields = this._collectStructFields(name, structTypeAst);
374
527
  this.structFields.set(name, fields);
@@ -380,11 +533,15 @@ export class CodeGen {
380
533
  this.genMethod(m);
381
534
  }
382
535
  this._genEmbeddedMethodStubs(name, methodDecls);
536
+ this._genStructClone(name, structTypeAst, fields);
383
537
  });
384
538
  this.line("}");
385
- this.line(
386
- `Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
387
- );
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
+ }
388
545
  }
389
546
 
390
547
  _genSingleEmbedStubs(embed, declared) {
@@ -419,22 +576,40 @@ export class CodeGen {
419
576
  this._boxedVars = new Set();
420
577
  this._scanAddressTaken(decl.body);
421
578
  const prevUnwrapped = this._unwrappedRecv;
422
- this.indented(() => {
423
- if (decl.recvName && decl.recvName !== "_") {
424
- if (recvField) {
425
- this.line(`const ${decl.recvName} = this.${recvField};`);
426
- this._unwrappedRecv = decl.recvName;
427
- } else {
428
- 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
+ }
429
588
  }
430
- }
431
- this._withNamedReturns(decl, () => this._genBody(decl.body));
432
- });
589
+ this._emitParamCopies(decl.params);
590
+ this._withNamedReturns(decl, () => this._genBody(decl.body));
591
+ }),
592
+ );
433
593
  this._unwrappedRecv = prevUnwrapped;
434
594
  this._boxedVars = prevBoxed;
435
595
  this.line("}");
436
596
  }
437
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
+
438
613
  // Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
439
614
  // non-struct type emitted as a wrapper class, or null otherwise.
440
615
  // Pass `expr` so we can skip unwrapping when the expression is the method receiver
@@ -468,8 +643,11 @@ export class CodeGen {
468
643
  const prevBoxed = this._boxedVars;
469
644
  this._boxedVars = new Set();
470
645
  this._scanAddressTaken(decl.body);
471
- this.indented(() =>
472
- 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
+ }),
473
651
  );
474
652
  this._boxedVars = prevBoxed;
475
653
  this.line("}");
@@ -481,8 +659,10 @@ export class CodeGen {
481
659
  if (named) {
482
660
  // Emit zero-value declarations for named return vars
483
661
  for (const { name, type } of named) {
484
- if (name)
662
+ if (name) {
663
+ this._markOwnership(name, true);
485
664
  this.line(`let ${name} = ${this.zeroValueForTypeNode(type)};`);
665
+ }
486
666
  }
487
667
  this.namedReturnVars = named.map((r) => r.name).filter(Boolean);
488
668
  } else {
@@ -563,8 +743,11 @@ export class CodeGen {
563
743
  genVarDecl(decl) {
564
744
  for (const spec of decl.decls) {
565
745
  if (spec.value) {
566
- const vals = spec.value.map((v) => {
567
- 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);
568
751
  // Wrap numeric values assigned to complex-typed vars
569
752
  if (
570
753
  spec.type?.name === "complex128" ||
@@ -588,6 +771,7 @@ export class CodeGen {
588
771
  } else {
589
772
  const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
590
773
  for (const name of spec.names) {
774
+ this._markOwnership(name, true);
591
775
  const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
592
776
  this.line(`let ${name} = ${val};`);
593
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;