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.
- package/README.md +7 -6
- package/package.json +1 -1
- package/src/cli-core.js +118 -13
- package/src/codegen/expressions.js +408 -89
- package/src/codegen/index.js +216 -25
- package/src/codegen/runtime.js +22 -1
- package/src/codegen/statements.js +170 -45
- package/src/codegen/stdlib/builder.js +7 -2
- package/src/codegen/stdlib/fmt.js +2 -2
- package/src/codegen/stdlib/io.js +1 -1
- package/src/dev-server.js +22 -10
- package/src/index.js +8 -2
- package/src/parser/declarations.js +2 -1
- package/src/pwa.js +3 -1
- package/src/typechecker/assignability.js +50 -4
- package/src/typechecker/expressions.js +13 -1
- package/src/typechecker/index.js +50 -3
- package/src/typechecker/resolve.js +2 -0
- package/src/typechecker/statements.js +10 -1
- package/src/typechecker/stdlib/web.js +54 -26
- package/src/typechecker/types.js +3 -2
- package/src/vendor.js +12 -7
package/src/codegen/index.js
CHANGED
|
@@ -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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
388
|
-
|
|
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.
|
|
425
|
-
|
|
426
|
-
if (
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
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.
|
|
474
|
-
this.
|
|
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
|
|
569
|
-
|
|
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
|
}
|
package/src/codegen/runtime.js
CHANGED
|
@@ -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;
|