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.
- package/package.json +1 -1
- package/src/cli-core.js +118 -13
- package/src/codegen/expressions.js +408 -89
- package/src/codegen/index.js +213 -29
- 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 +71 -31
- 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;
|
|
@@ -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 === "
|
|
176
|
+
if (d.kind === "ConstDecl") {
|
|
131
177
|
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
132
|
-
this.
|
|
178
|
+
this.genConstDecl(d);
|
|
133
179
|
this.blank();
|
|
134
180
|
}
|
|
135
|
-
|
|
181
|
+
}
|
|
182
|
+
for (const d of program.decls) {
|
|
183
|
+
if (d.kind === "VarDecl") {
|
|
136
184
|
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
137
|
-
this.
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
386
|
-
|
|
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.
|
|
423
|
-
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
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.
|
|
472
|
-
this.
|
|
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
|
|
567
|
-
|
|
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
|
}
|
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;
|