gofront 1.3.5 → 1.3.7

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) {
@@ -196,6 +319,15 @@ export class CodeGen {
196
319
  if (d.kind === "TypeDecl" && d.type.kind === "StructType")
197
320
  this.structNames.add(d.name);
198
321
  }
322
+ // Structs from bundled GoFront packages are inlined under their bare name.
323
+ // Skip aliases (key differs from the type's own name) — no class is emitted for them.
324
+ if (this.checker) {
325
+ for (const [name, t] of this.checker.types) {
326
+ if (name.includes(".") || t?.name !== name) continue;
327
+ if (t.kind === "named" && t.underlying?.kind === "struct")
328
+ this.structNames.add(name);
329
+ }
330
+ }
199
331
  }
200
332
 
201
333
  _collectMethodMap(program) {
@@ -236,6 +368,8 @@ export class CodeGen {
236
368
  [this._usesLen, HELPER_LEN],
237
369
  [this._usesAppend, HELPER_APPEND],
238
370
  [this._usesSliceGuard, HELPER_S],
371
+ [this._usesSClone, HELPER_SCLONE],
372
+ [this._usesIfaceBox, HELPER_IFACE_BOX],
239
373
  [this._usesEqual, HELPER_EQUAL],
240
374
  [this._usesCmul, HELPER_CMUL],
241
375
  [this._usesCdiv, HELPER_CDIV],
@@ -357,13 +491,15 @@ export class CodeGen {
357
491
  this.line(`constructor(${params}) {`);
358
492
  this.indented(() => {
359
493
  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
- );
494
+ // Legacy `new T({...})` support; only unambiguous when field 0 is a primitive.
495
+ if (/^(0|""|false)$/.test(fields[0].zero)) {
496
+ const pattern = fields
497
+ .map((f) => `${f.name}: ${binding(f)} = ${f.zero}`)
498
+ .join(", ");
499
+ this.line(
500
+ `if (typeof ${first} === "object" && ${first} !== null && ${first}.constructor === Object) ({ ${pattern} } = ${first});`,
501
+ );
502
+ }
367
503
  for (const f of fields) {
368
504
  this.line(`this.${f.name} = ${binding(f)};`);
369
505
  }
@@ -371,6 +507,30 @@ export class CodeGen {
371
507
  this.line("}");
372
508
  }
373
509
 
510
+ _genStructClone(name, structTypeAst, fields) {
511
+ const resolved = this.checker?.types.get(name)?.underlying;
512
+ const astTypes = new Map();
513
+ for (const f of structTypeAst?.fields ?? [])
514
+ for (const n of f.names ?? []) astTypes.set(n, f.type);
515
+ const args = fields.map((f) => {
516
+ const t =
517
+ resolved?.fields?.get(f.name) ??
518
+ this._typeFromNode(astTypes.get(f.name));
519
+ return this._cloneJs(t, `this.${f.name}`);
520
+ });
521
+ this.blank();
522
+ this.line(`__clone() { return new ${name}(${args.join(", ")}); }`);
523
+ }
524
+
525
+ _structDefinesValue(name, fields, methodDecls) {
526
+ if (fields.some((f) => f.name === "value")) return true;
527
+ if (methodDecls.some((m) => m.name === "value")) return true;
528
+ const resolved = this.checker?.types.get(name)?.underlying;
529
+ return Boolean(
530
+ resolved?.fields?.has?.("value") || resolved?.methods?.has?.("value"),
531
+ );
532
+ }
533
+
374
534
  genStruct(name, structTypeAst, methodDecls) {
375
535
  const fields = this._collectStructFields(name, structTypeAst);
376
536
  this.structFields.set(name, fields);
@@ -382,11 +542,15 @@ export class CodeGen {
382
542
  this.genMethod(m);
383
543
  }
384
544
  this._genEmbeddedMethodStubs(name, methodDecls);
545
+ this._genStructClone(name, structTypeAst, fields);
385
546
  });
386
547
  this.line("}");
387
- this.line(
388
- `Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
389
- );
548
+ // Lets generic `p.value` dereferences resolve to the struct itself.
549
+ if (!this._structDefinesValue(name, fields, methodDecls)) {
550
+ this.line(
551
+ `Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
552
+ );
553
+ }
390
554
  }
391
555
 
392
556
  _genSingleEmbedStubs(embed, declared) {
@@ -421,22 +585,40 @@ export class CodeGen {
421
585
  this._boxedVars = new Set();
422
586
  this._scanAddressTaken(decl.body);
423
587
  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;`);
588
+ this._withFnCtx(decl.body, () =>
589
+ this.indented(() => {
590
+ if (decl.recvName && decl.recvName !== "_") {
591
+ if (recvField) {
592
+ this.line(`const ${decl.recvName} = this.${recvField};`);
593
+ this._unwrappedRecv = decl.recvName;
594
+ } else {
595
+ this.line(`const ${decl.recvName} = ${this._receiverValue(decl)};`);
596
+ }
431
597
  }
432
- }
433
- this._withNamedReturns(decl, () => this._genBody(decl.body));
434
- });
598
+ this._emitParamCopies(decl.params);
599
+ this._withNamedReturns(decl, () => this._genBody(decl.body));
600
+ }),
601
+ );
435
602
  this._unwrappedRecv = prevUnwrapped;
436
603
  this._boxedVars = prevBoxed;
437
604
  this.line("}");
438
605
  }
439
606
 
607
+ // Value receivers are copies in Go; only materialize the copy when the body mutates it.
608
+ _receiverValue(decl) {
609
+ const name = decl.recvName;
610
+ if (decl.recvPointer || !this.structNames.has(decl.recvType.name)) {
611
+ this._markOwnership(name, false);
612
+ return "this";
613
+ }
614
+ if (this._fnMutates(name)) {
615
+ this._markOwnership(name, true);
616
+ return "this.__clone()";
617
+ }
618
+ this._markOwnership(name, false);
619
+ return "this";
620
+ }
621
+
440
622
  // Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
441
623
  // non-struct type emitted as a wrapper class, or null otherwise.
442
624
  // Pass `expr` so we can skip unwrapping when the expression is the method receiver
@@ -470,8 +652,11 @@ export class CodeGen {
470
652
  const prevBoxed = this._boxedVars;
471
653
  this._boxedVars = new Set();
472
654
  this._scanAddressTaken(decl.body);
473
- this.indented(() =>
474
- this._withNamedReturns(decl, () => this._genBody(decl.body)),
655
+ this._withFnCtx(decl.body, () =>
656
+ this.indented(() => {
657
+ this._emitParamCopies(decl.params);
658
+ this._withNamedReturns(decl, () => this._genBody(decl.body));
659
+ }),
475
660
  );
476
661
  this._boxedVars = prevBoxed;
477
662
  this.line("}");
@@ -483,8 +668,10 @@ export class CodeGen {
483
668
  if (named) {
484
669
  // Emit zero-value declarations for named return vars
485
670
  for (const { name, type } of named) {
486
- if (name)
671
+ if (name) {
672
+ this._markOwnership(name, true);
487
673
  this.line(`let ${name} = ${this.zeroValueForTypeNode(type)};`);
674
+ }
488
675
  }
489
676
  this.namedReturnVars = named.map((r) => r.name).filter(Boolean);
490
677
  } else {
@@ -565,8 +752,11 @@ export class CodeGen {
565
752
  genVarDecl(decl) {
566
753
  for (const spec of decl.decls) {
567
754
  if (spec.value) {
568
- const vals = spec.value.map((v) => {
569
- const js = this.genExpr(v);
755
+ const paired = spec.value.length === spec.names.length;
756
+ const vals = spec.value.map((v, i) => {
757
+ const js = paired
758
+ ? this._genDeclValue(spec.names[i], v)
759
+ : this.genExpr(v);
570
760
  // Wrap numeric values assigned to complex-typed vars
571
761
  if (
572
762
  spec.type?.name === "complex128" ||
@@ -590,6 +780,7 @@ export class CodeGen {
590
780
  } else {
591
781
  const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
592
782
  for (const name of spec.names) {
783
+ this._markOwnership(name, true);
593
784
  const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
594
785
  this.line(`let ${name} = ${val};`);
595
786
  }
@@ -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;