gofront 1.6.1 → 1.8.0
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 +45 -49
- package/package.json +12 -8
- package/src/backend/js/expressions.js +1627 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +508 -102
- package/src/backend/js/runtime.js +1997 -106
- package/src/backend/js/statements.js +931 -87
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +7 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +19 -2
- package/src/backend/js/stdlib/json.js +1167 -0
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +34 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +446 -72
- package/src/backend/wasm/emit-builtins.js +320 -13
- package/src/backend/wasm/emit-exprs.js +1404 -199
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +518 -74
- package/src/backend/wasm/encode.js +128 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +3 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +499 -0
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +221 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +84 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +89 -15
- package/src/compiler.js +107 -269
- package/src/dts-parser.js +71 -14
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -6
- package/src/lower/captures.js +176 -32
- package/src/lower/embedding.js +2 -1
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/parser/struct-tag.js +144 -0
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +130 -13
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +215 -11
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +960 -99
- package/src/typechecker/index.js +491 -72
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +596 -52
- package/src/typechecker/statements.js +157 -17
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +359 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
- package/src/typechecker/stdlib/simd.js +123 -0
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +165 -26
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
package/src/backend/js/index.js
CHANGED
|
@@ -21,15 +21,22 @@ import {
|
|
|
21
21
|
computeEmbeddedStubs,
|
|
22
22
|
hasDirectRecover,
|
|
23
23
|
isReferenceType,
|
|
24
|
+
markInBoundsIndexes,
|
|
24
25
|
normalizeDefers,
|
|
25
26
|
scanAddressTaken,
|
|
26
27
|
} from "../../lower/index.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
28
|
+
import { orderPackageInits } from "../../typechecker/init-order.js";
|
|
29
|
+
import {
|
|
30
|
+
BROWSER_GLOBALS,
|
|
31
|
+
isComplex,
|
|
32
|
+
isNumeric,
|
|
33
|
+
} from "../../typechecker/types.js";
|
|
34
|
+
import { comparesToNil, expressionGenMethods } from "./expressions.js";
|
|
29
35
|
import {
|
|
30
36
|
HELPER_APPEND,
|
|
31
37
|
HELPER_BASE64,
|
|
32
38
|
HELPER_BINARY,
|
|
39
|
+
HELPER_BOUNDS,
|
|
33
40
|
HELPER_CDIV,
|
|
34
41
|
HELPER_CMUL,
|
|
35
42
|
HELPER_CRYPTO_RAND,
|
|
@@ -39,31 +46,49 @@ import {
|
|
|
39
46
|
HELPER_ERROR_AS,
|
|
40
47
|
HELPER_ERROR_IS,
|
|
41
48
|
HELPER_ERROR_JOIN,
|
|
49
|
+
HELPER_GENERICS,
|
|
50
|
+
HELPER_GEQ,
|
|
42
51
|
HELPER_HEX,
|
|
43
52
|
HELPER_IFACE_BOX,
|
|
53
|
+
HELPER_IFD,
|
|
54
|
+
HELPER_IFEQ,
|
|
44
55
|
HELPER_INJECT_STYLES,
|
|
45
56
|
HELPER_IO_EOF,
|
|
57
|
+
HELPER_JSON,
|
|
46
58
|
HELPER_LEN,
|
|
47
59
|
HELPER_NIL_CALL,
|
|
60
|
+
HELPER_NIL_MAP,
|
|
61
|
+
HELPER_NM,
|
|
62
|
+
HELPER_OS,
|
|
48
63
|
HELPER_PANIC,
|
|
49
64
|
HELPER_PATH_CLEAN,
|
|
50
65
|
HELPER_PATH_ERROR,
|
|
66
|
+
HELPER_PTREQ,
|
|
51
67
|
HELPER_S,
|
|
52
68
|
HELPER_SCLONE,
|
|
53
69
|
HELPER_SHA256,
|
|
70
|
+
HELPER_SHIFT,
|
|
71
|
+
HELPER_SIMD,
|
|
72
|
+
HELPER_SLICE,
|
|
54
73
|
HELPER_SORT_SLICE,
|
|
74
|
+
HELPER_SPRINT,
|
|
55
75
|
HELPER_SPRINTF,
|
|
56
76
|
HELPER_STRCONV,
|
|
57
77
|
HELPER_STRCONV_ERROR,
|
|
78
|
+
HELPER_STRUCT_KEY,
|
|
79
|
+
HELPER_SV,
|
|
58
80
|
HELPER_SYNC,
|
|
59
81
|
HELPER_TESTING,
|
|
60
82
|
HELPER_TIME_FMT,
|
|
61
83
|
HELPER_TIME_PARSE,
|
|
84
|
+
HELPER_VA,
|
|
85
|
+
HELPER_VCLONE,
|
|
62
86
|
} from "./runtime.js";
|
|
63
87
|
import { buildSourceMap, buildWasmSourceMap } from "./source-map.js";
|
|
64
88
|
import { statementGenMethods } from "./statements.js";
|
|
65
89
|
import { stdlibGenMethods } from "./stdlib/index.js";
|
|
66
90
|
import { templGenMethods } from "./templ.js";
|
|
91
|
+
import { typeInfoGenMethods } from "./type-info.js";
|
|
67
92
|
|
|
68
93
|
export { buildSourceMap, buildWasmSourceMap };
|
|
69
94
|
|
|
@@ -107,20 +132,41 @@ const JS_RESERVED = new Set([
|
|
|
107
132
|
const jsSafeName = (n) =>
|
|
108
133
|
typeof n === "string" && JS_RESERVED.has(n) ? `${n}$` : n;
|
|
109
134
|
|
|
135
|
+
// Go declarations may shadow browser globals (Task 0.9). The JS bundle is one
|
|
136
|
+
// flat module scope, so such a binding gets a `$` suffix: generated code and
|
|
137
|
+
// other packages keep reaching the real global (`Array.from`, `Object.keys`).
|
|
138
|
+
const jsBindingName = (n) =>
|
|
139
|
+
typeof n === "string" && BROWSER_GLOBALS.has(n) ? `${n}$` : jsSafeName(n);
|
|
140
|
+
|
|
110
141
|
// Runtime helpers by registry name, in output order. Mark one as needed with
|
|
111
142
|
// `this.useHelper(name)`; `_prependHelpers` emits every marked entry.
|
|
112
143
|
const HELPER_MAP = [
|
|
113
144
|
["len", HELPER_LEN],
|
|
145
|
+
["sv", HELPER_SV],
|
|
146
|
+
["slice", HELPER_SLICE],
|
|
114
147
|
["append", HELPER_APPEND],
|
|
115
148
|
["sliceGuard", HELPER_S],
|
|
116
149
|
["sclone", HELPER_SCLONE],
|
|
150
|
+
["va", HELPER_VA],
|
|
151
|
+
["vclone", HELPER_VCLONE],
|
|
117
152
|
["ifaceBox", HELPER_IFACE_BOX],
|
|
153
|
+
["ifd", HELPER_IFD],
|
|
118
154
|
["equal", HELPER_EQUAL],
|
|
155
|
+
["geq", HELPER_GEQ],
|
|
156
|
+
["ptreq", HELPER_PTREQ],
|
|
157
|
+
["ifeq", HELPER_IFEQ],
|
|
119
158
|
["cmul", HELPER_CMUL],
|
|
120
159
|
["cdiv", HELPER_CDIV],
|
|
121
160
|
["panicRt", HELPER_PANIC],
|
|
161
|
+
["shift", HELPER_SHIFT],
|
|
122
162
|
["nilCall", HELPER_NIL_CALL],
|
|
163
|
+
["bounds", HELPER_BOUNDS],
|
|
164
|
+
["nilMap", HELPER_NIL_MAP],
|
|
165
|
+
["nm", HELPER_NM],
|
|
166
|
+
["structKey", HELPER_STRUCT_KEY],
|
|
167
|
+
["generics", HELPER_GENERICS],
|
|
123
168
|
["sprintf", HELPER_SPRINTF],
|
|
169
|
+
["sprint", HELPER_SPRINT],
|
|
124
170
|
["error", HELPER_ERROR],
|
|
125
171
|
["errorIs", HELPER_ERROR_IS],
|
|
126
172
|
["errorAs", HELPER_ERROR_AS],
|
|
@@ -128,6 +174,7 @@ const HELPER_MAP = [
|
|
|
128
174
|
["strconvError", HELPER_STRCONV_ERROR],
|
|
129
175
|
["pathError", HELPER_PATH_ERROR],
|
|
130
176
|
["ioEOF", HELPER_IO_EOF],
|
|
177
|
+
["os", HELPER_OS],
|
|
131
178
|
["strconv", HELPER_STRCONV],
|
|
132
179
|
["pathClean", HELPER_PATH_CLEAN],
|
|
133
180
|
["sortSlice", HELPER_SORT_SLICE],
|
|
@@ -141,8 +188,14 @@ const HELPER_MAP = [
|
|
|
141
188
|
["binary", HELPER_BINARY],
|
|
142
189
|
["hex", HELPER_HEX],
|
|
143
190
|
["base64", HELPER_BASE64],
|
|
191
|
+
["json", HELPER_JSON],
|
|
192
|
+
["simd", HELPER_SIMD],
|
|
144
193
|
];
|
|
145
194
|
|
|
195
|
+
// JS bodies of `//gofront:intrinsic` functions (names: INTRINSICS in
|
|
196
|
+
// typechecker/types.js). The WASM backend keeps their empty body.
|
|
197
|
+
const JS_INTRINSICS = {};
|
|
198
|
+
|
|
146
199
|
export class CodeGen {
|
|
147
200
|
// jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
|
|
148
201
|
// bundledPackages: Set<string> — GoFront package names bundled inline;
|
|
@@ -162,12 +215,18 @@ export class CodeGen {
|
|
|
162
215
|
this.structFields = new Map();
|
|
163
216
|
this.namedWrapperNames = new Set();
|
|
164
217
|
this.collectedCss = [];
|
|
218
|
+
this.warnings = []; // non-fatal diagnostics; the compiler prints them
|
|
219
|
+
this._precisionWarned = new Set(); // nodes already given a precision warning
|
|
220
|
+
this._srcFileNames = []; // source file name per _srcFileIdx
|
|
165
221
|
this.jsImports = jsImports;
|
|
166
222
|
this.bundledPackages = bundledPackages;
|
|
167
223
|
this.namedReturnVars = null; // names of current function's named return vars
|
|
168
224
|
this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
|
|
169
225
|
this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
|
|
170
226
|
this._boxedVars = new Set(); // address-taken scalar variables that need boxing
|
|
227
|
+
this._jsonCodecs = new Map(); // memoized JSON encoders and decoders
|
|
228
|
+
this._localTypeNames = new Set(); // function-local type declarations
|
|
229
|
+
this._jsonAnyUsed = false; // program calls json.Marshal / Encoder.Encode
|
|
171
230
|
// Runtime helper usage tracking — only emit helpers that are actually used
|
|
172
231
|
this._helpers = new Set();
|
|
173
232
|
// Per-function context for Go value semantics (see _withFnCtx)
|
|
@@ -214,6 +273,9 @@ export class CodeGen {
|
|
|
214
273
|
}
|
|
215
274
|
|
|
216
275
|
_emitTypeDecls(program, methods) {
|
|
276
|
+
this._typeDeclsByName = new Map();
|
|
277
|
+
for (const d of program.decls)
|
|
278
|
+
if (d.kind === "TypeDecl") this._typeDeclsByName.set(d.name, d);
|
|
217
279
|
for (const d of program.decls) {
|
|
218
280
|
if (d.kind === "TypeDecl") {
|
|
219
281
|
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
@@ -224,20 +286,28 @@ export class CodeGen {
|
|
|
224
286
|
}
|
|
225
287
|
|
|
226
288
|
_emitVarConstDecls(program) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
289
|
+
const { consts, vars } = orderPackageInits([program]);
|
|
290
|
+
const emit = (decls, gen, ordered) => {
|
|
291
|
+
const natural = decls.flatMap((d) => d.decls ?? []);
|
|
292
|
+
const inOrder = ordered.every((e, i) => e.spec === natural[i]);
|
|
293
|
+
if (inOrder) {
|
|
294
|
+
for (const d of decls) {
|
|
295
|
+
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
296
|
+
gen.call(this, d);
|
|
297
|
+
this.blank();
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
232
300
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
this.
|
|
237
|
-
this.genVarDecl(d);
|
|
301
|
+
// Initialisers depend on declarations that come later (Go allows it).
|
|
302
|
+
for (const e of ordered) {
|
|
303
|
+
this._currentSrcFileIdx = e.decl._srcFileIdx ?? 0;
|
|
304
|
+
gen.call(this, { ...e.decl, decls: [e.spec] });
|
|
238
305
|
this.blank();
|
|
239
306
|
}
|
|
240
|
-
}
|
|
307
|
+
};
|
|
308
|
+
const declsOf = (kind) => program.decls.filter((d) => d.kind === kind);
|
|
309
|
+
emit(declsOf("ConstDecl"), this.genConstDecl, consts);
|
|
310
|
+
emit(declsOf("VarDecl"), this.genVarDecl, vars);
|
|
241
311
|
}
|
|
242
312
|
|
|
243
313
|
_callInitAndMain(initNames, program, isTest = false) {
|
|
@@ -255,24 +325,34 @@ export class CodeGen {
|
|
|
255
325
|
|
|
256
326
|
generate(program, options = {}) {
|
|
257
327
|
const isTest = options.isTest ?? false;
|
|
328
|
+
this._precisionWarned = new Set();
|
|
258
329
|
// Defer lowering (argument/receiver hoisting, recover-target marking) is
|
|
259
330
|
// idempotent and owned by the backends: the wasm path runs it via
|
|
260
331
|
// `lower()`, the JS path here.
|
|
261
332
|
normalizeDefers(program);
|
|
333
|
+
// Strict mode: index checks the loop shape proves unnecessary are elided.
|
|
334
|
+
if (this.strict) markInBoundsIndexes(program);
|
|
335
|
+
if (program._filename) this._srcFileNames = [program._filename];
|
|
262
336
|
// Ensure _pkgName is set on all decls (generateAll sets it for
|
|
263
337
|
// multi-file; for single-file compiles we derive it from the AST).
|
|
264
338
|
const pkgName = program.pkg?.name;
|
|
265
339
|
if (pkgName) {
|
|
340
|
+
this.pkgName = pkgName;
|
|
266
341
|
for (const d of program.decls) {
|
|
267
342
|
if (!d._pkgName) d._pkgName = pkgName;
|
|
268
343
|
}
|
|
269
344
|
}
|
|
270
345
|
this._renameReservedIdents(program.decls);
|
|
346
|
+
this._boxedGlobals = this._collectBoxedGlobals(program);
|
|
347
|
+
this._boxedVars = new Set(this._boxedGlobals);
|
|
348
|
+
this._collectResliceNames(program);
|
|
349
|
+
this._jsonAnyUsed = this._usesJsonAnyMarshal(program.decls);
|
|
271
350
|
const methods = this._collectDecls(program);
|
|
272
351
|
this._emitJsImports();
|
|
273
352
|
this._emitTypeDecls(program, methods);
|
|
274
353
|
this._emitVarConstDecls(program);
|
|
275
354
|
const initNames = this._emitFuncDecls(program);
|
|
355
|
+
this._emitJsonCodecs();
|
|
276
356
|
this._callInitAndMain(initNames, program, isTest);
|
|
277
357
|
const prependedLines = this._prependHelpers(isTest);
|
|
278
358
|
return this._finalizeOutput(prependedLines);
|
|
@@ -323,12 +403,12 @@ export class CodeGen {
|
|
|
323
403
|
}
|
|
324
404
|
|
|
325
405
|
_renameParams(params) {
|
|
326
|
-
for (const p of params ?? []) p.name =
|
|
406
|
+
for (const p of params ?? []) p.name = jsBindingName(p.name);
|
|
327
407
|
}
|
|
328
408
|
|
|
329
409
|
_renameNamedReturns(node) {
|
|
330
410
|
for (const r of node.returnType?._namedReturns ?? [])
|
|
331
|
-
r.name =
|
|
411
|
+
r.name = jsBindingName(r.name);
|
|
332
412
|
}
|
|
333
413
|
|
|
334
414
|
_renameSignature(node) {
|
|
@@ -337,27 +417,37 @@ export class CodeGen {
|
|
|
337
417
|
}
|
|
338
418
|
|
|
339
419
|
_renameIdentNode(node) {
|
|
340
|
-
if (
|
|
341
|
-
|
|
420
|
+
if (node._isStructKey || node._type?.kind === "builtin") return;
|
|
421
|
+
node.name = node._shadowsGlobal
|
|
422
|
+
? jsBindingName(node.name)
|
|
423
|
+
: jsSafeName(node.name);
|
|
342
424
|
}
|
|
343
425
|
|
|
344
426
|
_renameFuncDeclNode(node) {
|
|
345
|
-
node.name =
|
|
427
|
+
node.name = jsBindingName(node.name);
|
|
346
428
|
this._renameSignature(node);
|
|
347
429
|
}
|
|
348
430
|
|
|
349
431
|
_renameMethodDeclNode(node) {
|
|
350
|
-
node.recvName =
|
|
432
|
+
node.recvName = jsBindingName(node.recvName);
|
|
351
433
|
this._renameSignature(node);
|
|
352
434
|
}
|
|
353
435
|
|
|
354
436
|
_renameDeclSpecs(node) {
|
|
355
437
|
for (const spec of node.decls ?? [])
|
|
356
|
-
spec.names = spec.names.map(
|
|
438
|
+
spec.names = spec.names.map(jsBindingName);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// `document := 1` declares a binding that shadows the browser global. A
|
|
442
|
+
// `:=` redeclaration (`x, document := 2, 3`) always targets a local of the
|
|
443
|
+
// same scope, which carries the same suffix.
|
|
444
|
+
_renameDefineNode(node) {
|
|
445
|
+
for (const l of node.lhs ?? [])
|
|
446
|
+
if (l.kind === "Ident") l.name = jsBindingName(l.name);
|
|
357
447
|
}
|
|
358
448
|
|
|
359
449
|
_renameTypeSwitchNode(node) {
|
|
360
|
-
node.assign =
|
|
450
|
+
node.assign = jsBindingName(node.assign);
|
|
361
451
|
}
|
|
362
452
|
|
|
363
453
|
_markStructKeys(node) {
|
|
@@ -376,7 +466,15 @@ export class CodeGen {
|
|
|
376
466
|
MethodDecl: "_renameMethodDeclNode",
|
|
377
467
|
FuncLit: "_renameSignature",
|
|
378
468
|
TemplDecl: "_renameSignature",
|
|
469
|
+
TemplExpr: "_renameTemplExprNode",
|
|
470
|
+
TemplComponent: "_renameTemplExprNode",
|
|
471
|
+
TemplIf: "_renameTemplExprNode",
|
|
472
|
+
TemplSwitch: "_renameTemplExprNode",
|
|
473
|
+
TemplCase: "_renameTemplExprNode",
|
|
474
|
+
TemplFor: "_renameTemplForNode",
|
|
475
|
+
TemplElement: "_renameTemplElementNode",
|
|
379
476
|
CssDecl: "_renameSignature",
|
|
477
|
+
DefineStmt: "_renameDefineNode",
|
|
380
478
|
VarDecl: "_renameDeclSpecs",
|
|
381
479
|
ConstDecl: "_renameDeclSpecs",
|
|
382
480
|
TypeSwitchStmt: "_renameTypeSwitchNode",
|
|
@@ -402,6 +500,18 @@ export class CodeGen {
|
|
|
402
500
|
// Collects struct names, method map, and named wrapper names from program decls.
|
|
403
501
|
// Returns the method map (typeName → MethodDecl[]).
|
|
404
502
|
_collectStructNames(program) {
|
|
503
|
+
// Go-compiled variadic functions, methods and templ components: a
|
|
504
|
+
// `f(xs...)` call to one passes the slice itself (see `_genCallArgs`,
|
|
505
|
+
// `_emitParamCopies`).
|
|
506
|
+
this.variadicFns = new Set();
|
|
507
|
+
for (const d of program.decls) {
|
|
508
|
+
const last = d.params?.[d.params.length - 1];
|
|
509
|
+
if (!last?.variadic) continue;
|
|
510
|
+
if (d.kind === "FuncDecl" || d.kind === "TemplDecl")
|
|
511
|
+
this.variadicFns.add(d.name);
|
|
512
|
+
else if (d.kind === "MethodDecl")
|
|
513
|
+
this.variadicFns.add(`${d.recvType.name}.${d.name}`);
|
|
514
|
+
}
|
|
405
515
|
for (const d of program.decls) {
|
|
406
516
|
if (d.kind === "TypeDecl" && d.type.kind === "StructType")
|
|
407
517
|
this.structNames.add(d.name);
|
|
@@ -419,11 +529,14 @@ export class CodeGen {
|
|
|
419
529
|
|
|
420
530
|
_collectMethodMap(program) {
|
|
421
531
|
const methods = new Map();
|
|
532
|
+
this.nilSafeMethods = new Set();
|
|
422
533
|
for (const d of program.decls) {
|
|
423
534
|
if (d.kind === "MethodDecl") {
|
|
424
535
|
const name = d.recvType.name;
|
|
425
536
|
if (!methods.has(name)) methods.set(name, []);
|
|
426
537
|
methods.get(name).push(d);
|
|
538
|
+
if (d.recvPointer && comparesToNil(d.body, d.recvName))
|
|
539
|
+
this.nilSafeMethods.add(`${name}.${d.name}`);
|
|
427
540
|
}
|
|
428
541
|
}
|
|
429
542
|
return methods;
|
|
@@ -452,15 +565,28 @@ export class CodeGen {
|
|
|
452
565
|
/** Marks a runtime helper (key of HELPER_MAP) as needed by the output. */
|
|
453
566
|
useHelper(name) {
|
|
454
567
|
this._helpers.add(name);
|
|
568
|
+
if (name === "ifeq" || name === "geq") this._helpers.add("equal");
|
|
569
|
+
if (name === "geq") this._helpers.add("ifeq");
|
|
455
570
|
if (
|
|
456
571
|
name === "strconvError" ||
|
|
457
572
|
name === "pathError" ||
|
|
458
573
|
name === "ioEOF" ||
|
|
459
574
|
name === "base64" ||
|
|
460
|
-
name === "hex"
|
|
575
|
+
name === "hex" ||
|
|
576
|
+
name === "json"
|
|
461
577
|
) {
|
|
462
578
|
this._helpers.add("error");
|
|
463
579
|
}
|
|
580
|
+
if (name === "json") {
|
|
581
|
+
this.useHelper("base64");
|
|
582
|
+
}
|
|
583
|
+
if (name === "os") {
|
|
584
|
+
this.useHelper("ioEOF");
|
|
585
|
+
this._helpers.add("error");
|
|
586
|
+
}
|
|
587
|
+
if (name === "append") this.useHelper("slice");
|
|
588
|
+
if (name === "sprint") this._helpers.add("sprintf");
|
|
589
|
+
if (name === "slice") this._helpers.add("sv");
|
|
464
590
|
if (name === "binary") {
|
|
465
591
|
this.useHelper("ioEOF");
|
|
466
592
|
this._helpers.add("error");
|
|
@@ -489,16 +615,29 @@ export class CodeGen {
|
|
|
489
615
|
// Generate a single bundle from multiple programs (same-package multi-file).
|
|
490
616
|
// Annotates each decl with its source file index before merging.
|
|
491
617
|
generateAll(programs, options = {}) {
|
|
618
|
+
this._srcFileNames = programs.map((p) => p._filename ?? null);
|
|
492
619
|
for (let i = 0; i < programs.length; i++) {
|
|
493
620
|
for (const decl of programs[i].decls) {
|
|
494
621
|
decl._srcFileIdx = i;
|
|
495
622
|
decl._pkgName = programs[i].pkg?.name;
|
|
496
623
|
}
|
|
497
624
|
}
|
|
498
|
-
const merged = {
|
|
625
|
+
const merged = {
|
|
626
|
+
pkg: programs[0]?.pkg,
|
|
627
|
+
decls: programs.flatMap((p) => p.decls),
|
|
628
|
+
};
|
|
499
629
|
return this.generate(merged, options);
|
|
500
630
|
}
|
|
501
631
|
|
|
632
|
+
// Records a non-fatal diagnostic at `node` ("main.go at line 3:5: msg").
|
|
633
|
+
_warn(msg, node) {
|
|
634
|
+
const file = this._srcFileNames[this._currentSrcFileIdx ?? 0];
|
|
635
|
+
const line = node?.line ?? node?._line;
|
|
636
|
+
const at = line ? `at line ${line}:${node.col ?? node._col ?? 1}` : "";
|
|
637
|
+
const loc = [file, at].filter(Boolean).join(" ");
|
|
638
|
+
this.warnings.push(loc ? `${loc}: ${msg}` : msg);
|
|
639
|
+
}
|
|
640
|
+
|
|
502
641
|
getCss() {
|
|
503
642
|
return [...new Set(this.collectedCss)].join("\n\n");
|
|
504
643
|
}
|
|
@@ -517,14 +656,40 @@ export class CodeGen {
|
|
|
517
656
|
|
|
518
657
|
// ── Type declarations ────────────────────────────────────────
|
|
519
658
|
|
|
659
|
+
// `type S Base` with Base a struct declared in this package: the struct
|
|
660
|
+
// AST S inherits its fields from, or null.
|
|
661
|
+
_definedStructAst(decl) {
|
|
662
|
+
if (decl.isAlias || decl.typeParams) return null;
|
|
663
|
+
if (this.checker?.types.get(decl.name)?.underlying?.kind !== "struct")
|
|
664
|
+
return null;
|
|
665
|
+
let t = decl.type;
|
|
666
|
+
for (let depth = 0; depth < 16 && t; depth++) {
|
|
667
|
+
if (t.kind === "StructType") return t;
|
|
668
|
+
if (t.kind !== "TypeName" && t.kind !== "Ident") return null;
|
|
669
|
+
const next = this._typeDeclsByName?.get(t.name);
|
|
670
|
+
if (!next || next.isAlias) return null;
|
|
671
|
+
t = next.type;
|
|
672
|
+
}
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
|
|
520
676
|
genTypeDeclWithMethods(decl, methodDecls) {
|
|
677
|
+
const definedStruct =
|
|
678
|
+
decl.type.kind === "StructType" ? null : this._definedStructAst(decl);
|
|
521
679
|
if (decl.type.kind === "StructType") {
|
|
522
680
|
this.genStruct(decl.name, decl.type, methodDecls);
|
|
681
|
+
} else if (definedStruct) {
|
|
682
|
+
this.genStruct(decl.name, definedStruct, methodDecls);
|
|
523
683
|
} else if (decl.type.kind === "InterfaceType") {
|
|
524
684
|
// Interfaces are compile-time only — no JS output needed.
|
|
525
685
|
this.line(`// interface ${decl.name} (compile-time only)`);
|
|
526
|
-
} else if (
|
|
686
|
+
} else if (
|
|
687
|
+
methodDecls.length > 0 ||
|
|
688
|
+
this._isPlainNamedBox(this.checker?.types.get(decl.name))
|
|
689
|
+
) {
|
|
527
690
|
// Named non-struct type with methods — emit an ES6 wrapper class.
|
|
691
|
+
if (methodDecls.length === 0)
|
|
692
|
+
this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
|
|
528
693
|
this.genNamedTypeClass(decl.name, methodDecls);
|
|
529
694
|
} else {
|
|
530
695
|
this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
|
|
@@ -533,31 +698,79 @@ export class CodeGen {
|
|
|
533
698
|
|
|
534
699
|
genNamedTypeClass(name, methodDecls) {
|
|
535
700
|
const namedType = this.checker?.types.get(name);
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
ctorDefault = "null";
|
|
541
|
-
} else if (underlying?.kind === "map") {
|
|
542
|
-
field = "_map";
|
|
543
|
-
ctorDefault = "{}";
|
|
544
|
-
} else {
|
|
545
|
-
field = "_items";
|
|
546
|
-
ctorDefault = "[]";
|
|
547
|
-
}
|
|
701
|
+
const field = this._namedBoxField(namedType?.underlying);
|
|
702
|
+
// Values of the type stay raw (number, string, array, map, function); the
|
|
703
|
+
// class is only the box used when a value is stored in an interface.
|
|
704
|
+
// Methods are static (`T.$M(recv, ...)`) so raw values can call them.
|
|
548
705
|
this.line(`class ${name} {`);
|
|
549
706
|
this.indented(() => {
|
|
550
|
-
this.line(
|
|
551
|
-
|
|
552
|
-
);
|
|
707
|
+
this.line(`constructor(${field}) { this.${field} = ${field}; }`);
|
|
708
|
+
this.line(`__gfUnbox() { return this.${field}; }`);
|
|
553
709
|
for (const m of methodDecls) {
|
|
554
710
|
this.blank();
|
|
555
|
-
this.genMethod(m,
|
|
711
|
+
this.genMethod(m, { staticNamed: true });
|
|
712
|
+
if (!m.recvPointer) {
|
|
713
|
+
this.line(
|
|
714
|
+
`${m.name}(...__a) { return ${name}.$${m.name}(this.${field}, ...__a); }`,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
this.line("}");
|
|
720
|
+
// Box for *T in an interface. It also proxies `.value` to the pointer, so
|
|
721
|
+
// helpers that write through a pointer argument (errors.As, json.Unmarshal)
|
|
722
|
+
// keep working. Pointer methods get the { value } box, value methods its value.
|
|
723
|
+
this.line(`class ${name}$Ptr {`);
|
|
724
|
+
this.indented(() => {
|
|
725
|
+
this.line("constructor(_p) { this._p = _p; }");
|
|
726
|
+
this.line("__gfUnbox() { return this._p; }");
|
|
727
|
+
this.line("get value() { return this._p.value; }");
|
|
728
|
+
this.line("set value(v) { this._p.value = v; }");
|
|
729
|
+
for (const m of methodDecls) {
|
|
730
|
+
const recv = m.recvPointer ? "this._p" : "this._p.value";
|
|
731
|
+
this.line(
|
|
732
|
+
`${m.name}(...__a) { return ${name}.$${m.name}(${recv}, ...__a); }`,
|
|
733
|
+
);
|
|
556
734
|
}
|
|
557
735
|
});
|
|
558
736
|
this.line("}");
|
|
559
737
|
}
|
|
560
738
|
|
|
739
|
+
_namedBoxField(underlying) {
|
|
740
|
+
if (underlying?.kind === "func") return "_fn";
|
|
741
|
+
if (underlying?.kind === "map") return "_map";
|
|
742
|
+
return "_items";
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// A user-declared named basic type without methods: still boxed in an
|
|
746
|
+
// interface so `any(L("x")).(string)` fails like Go.
|
|
747
|
+
_isPlainNamedBox(type) {
|
|
748
|
+
if (type?.kind !== "named" || type._generic) return false;
|
|
749
|
+
if (type._target === undefined || type._target === "wasm") return false;
|
|
750
|
+
if (type.underlying?.kind !== "basic" || type.methods?.size > 0)
|
|
751
|
+
return false;
|
|
752
|
+
return type.methods instanceof Map;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Box/dispatch info for a named non-struct type with methods, or null.
|
|
756
|
+
_namedBoxInfo(type) {
|
|
757
|
+
if (type?.kind !== "named") return null;
|
|
758
|
+
const u = type.underlying;
|
|
759
|
+
if (!u || u.kind === "struct" || u.kind === "interface") return null;
|
|
760
|
+
const methods = type.methods ?? type._generic?.methods;
|
|
761
|
+
if (this._isPlainNamedBox(type))
|
|
762
|
+
return {
|
|
763
|
+
cls: this._dequalify(type.name),
|
|
764
|
+
field: this._namedBoxField(u),
|
|
765
|
+
methods: new Map(),
|
|
766
|
+
};
|
|
767
|
+
if (!methods || methods.size === 0) return null;
|
|
768
|
+
if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
|
|
769
|
+
return null;
|
|
770
|
+
const cls = this._dequalify(type.name).replace(/\[.*$/, "");
|
|
771
|
+
return { cls, field: this._namedBoxField(u), methods };
|
|
772
|
+
}
|
|
773
|
+
|
|
561
774
|
_collectStructFields(name, structTypeAst) {
|
|
562
775
|
const fields = [];
|
|
563
776
|
if (this.checker) {
|
|
@@ -582,9 +795,11 @@ export class CodeGen {
|
|
|
582
795
|
const resolved = this.checker.types.get(typeName)?.underlying;
|
|
583
796
|
if (resolved?.kind === "struct") {
|
|
584
797
|
const fields = [];
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
798
|
+
// Cached per type name, so never bound to the current generic body.
|
|
799
|
+
this._withTypeParamZeros(null, () => {
|
|
800
|
+
for (const [name, type] of resolved.fields.entries())
|
|
801
|
+
fields.push({ name, zero: this.zeroValueForType(type) });
|
|
802
|
+
});
|
|
588
803
|
this.structFields.set(typeName, fields);
|
|
589
804
|
return fields;
|
|
590
805
|
}
|
|
@@ -617,17 +832,69 @@ export class CodeGen {
|
|
|
617
832
|
this.line("}");
|
|
618
833
|
}
|
|
619
834
|
|
|
835
|
+
// A generic struct's constructor takes the instance's zero-value factories
|
|
836
|
+
// after the fields (see HELPER_GENERICS). A field whose zero value depends
|
|
837
|
+
// on a type parameter starts at it when its argument is undefined.
|
|
838
|
+
_genTypeParamBindings(tpNames, tz) {
|
|
839
|
+
tpNames.forEach((n, i) => {
|
|
840
|
+
this.line(`const $z_${n} = ${tz}?.[${i}] ?? __zn;`);
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
_genGenericStructConstructor(fields, tpNames) {
|
|
845
|
+
const usesTz = (f) => f.zero.includes("$z_");
|
|
846
|
+
const params = fields.map((f) =>
|
|
847
|
+
usesTz(f) ? `${f.name}$` : `${f.name}$ = ${f.zero}`,
|
|
848
|
+
);
|
|
849
|
+
this.line(`constructor(${[...params, "$tz"].join(", ")}) {`);
|
|
850
|
+
this.indented(() => {
|
|
851
|
+
this.line("this[__tz] = $tz;");
|
|
852
|
+
if (fields.some(usesTz)) this._genTypeParamBindings(tpNames, "$tz");
|
|
853
|
+
for (const f of fields) {
|
|
854
|
+
const v = usesTz(f)
|
|
855
|
+
? `${f.name}$ === undefined ? ${f.zero} : ${f.name}$`
|
|
856
|
+
: `${f.name}$`;
|
|
857
|
+
this.line(`this.${f.name} = ${v};`);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
this.line("}");
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// JS object keys are strings: a struct used as a map key becomes an
|
|
864
|
+
// injective encoding of its fields (see HELPER_STRUCT_KEY), so equal values
|
|
865
|
+
// share a key and distinct values never collide.
|
|
866
|
+
_genStructKeyString(_name, fields) {
|
|
867
|
+
if (fields.some((f) => f.name === "toString")) return;
|
|
868
|
+
if (fields.length === 0) {
|
|
869
|
+
this.line('toString() { return "{}"; }');
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
this.useHelper("structKey");
|
|
873
|
+
this.line("toString() { return __sk(this); }");
|
|
874
|
+
}
|
|
875
|
+
|
|
620
876
|
_genStructClone(name, structTypeAst, fields) {
|
|
621
877
|
const resolved = this.checker?.types.get(name)?.underlying;
|
|
622
878
|
const astTypes = new Map();
|
|
623
879
|
for (const f of structTypeAst?.fields ?? [])
|
|
624
880
|
for (const n of f.names ?? []) astTypes.set(n, f.type);
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
881
|
+
const tpNames = this._genericTypeParamNames(name);
|
|
882
|
+
const prevZeros = this._typeParamZeros;
|
|
883
|
+
this._typeParamZeros = tpNames
|
|
884
|
+
? new Map(tpNames.map((n, i) => [n, `this[__tz]?.[${i}]`]))
|
|
885
|
+
: null;
|
|
886
|
+
let args;
|
|
887
|
+
try {
|
|
888
|
+
args = fields.map((f) => {
|
|
889
|
+
const t =
|
|
890
|
+
resolved?.fields?.get(f.name) ??
|
|
891
|
+
this._typeFromNode(astTypes.get(f.name));
|
|
892
|
+
return this._cloneJs(t, `this.${f.name}`);
|
|
893
|
+
});
|
|
894
|
+
} finally {
|
|
895
|
+
this._typeParamZeros = prevZeros;
|
|
896
|
+
}
|
|
897
|
+
if (tpNames) args.push("this[__tz]");
|
|
631
898
|
this.blank();
|
|
632
899
|
this.line(`__clone() { return new ${name}(${args.join(", ")}); }`);
|
|
633
900
|
}
|
|
@@ -641,18 +908,38 @@ export class CodeGen {
|
|
|
641
908
|
);
|
|
642
909
|
}
|
|
643
910
|
|
|
911
|
+
// Type parameter names of generic type `name`, or null.
|
|
912
|
+
_genericTypeParamNames(name) {
|
|
913
|
+
const generic = this.checker?.types.get(name)?._generic;
|
|
914
|
+
if (!generic?.typeParams?.length || generic.isAlias) return null;
|
|
915
|
+
return generic.typeParams.map((tp) => tp.name);
|
|
916
|
+
}
|
|
917
|
+
|
|
644
918
|
genStruct(name, structTypeAst, methodDecls) {
|
|
645
|
-
const
|
|
646
|
-
this.
|
|
919
|
+
const tpNames = this._genericTypeParamNames(name);
|
|
920
|
+
if (tpNames) this.useHelper("generics");
|
|
921
|
+
const fields = this._withTypeParamZeros(tpNames, () =>
|
|
922
|
+
this._collectStructFields(name, structTypeAst),
|
|
923
|
+
);
|
|
924
|
+
this.structFields.set(
|
|
925
|
+
name,
|
|
926
|
+
tpNames
|
|
927
|
+
? this._withTypeParamZeros(null, () =>
|
|
928
|
+
this._collectStructFields(name, structTypeAst),
|
|
929
|
+
)
|
|
930
|
+
: fields,
|
|
931
|
+
);
|
|
647
932
|
this.line(`class ${name} {`);
|
|
648
933
|
this.indented(() => {
|
|
649
|
-
this.
|
|
934
|
+
if (tpNames) this._genGenericStructConstructor(fields, tpNames);
|
|
935
|
+
else this._genStructConstructor(fields);
|
|
650
936
|
for (const m of methodDecls) {
|
|
651
937
|
this.blank();
|
|
652
938
|
this.genMethod(m);
|
|
653
939
|
}
|
|
654
940
|
this._genEmbeddedMethodStubs(name, methodDecls);
|
|
655
941
|
this._genStructClone(name, structTypeAst, fields);
|
|
942
|
+
this._genStructKeyString(name, fields);
|
|
656
943
|
});
|
|
657
944
|
this.line("}");
|
|
658
945
|
// Lets generic `p.value` dereferences resolve to the struct itself.
|
|
@@ -661,6 +948,26 @@ export class CodeGen {
|
|
|
661
948
|
`Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
|
|
662
949
|
);
|
|
663
950
|
}
|
|
951
|
+
this._genFmtClassMeta(name, methodDecls);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
_emitJsonCodecs() {
|
|
955
|
+
this._emitJsonAnyHooks();
|
|
956
|
+
if (!this._jsonCodecs || this._jsonCodecs.size === 0) return;
|
|
957
|
+
for (const [, code] of this._jsonCodecs) {
|
|
958
|
+
this.line(code);
|
|
959
|
+
this.blank();
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// A promoted method runs on the embedded value (`this.In`), so it and the
|
|
964
|
+
// outer struct's promoted-field accesses see the same storage. Embedded
|
|
965
|
+
// types that are not compiled Go structs keep the borrowed-`this` form.
|
|
966
|
+
_embedStub(method, embedName) {
|
|
967
|
+
const call = this.structNames.has(embedName)
|
|
968
|
+
? `this.${embedName}.${method}(...__a)`
|
|
969
|
+
: `${embedName}.prototype.${method}.call(this, ...__a)`;
|
|
970
|
+
return `${method}(...__a) { return ${call}; }`;
|
|
664
971
|
}
|
|
665
972
|
|
|
666
973
|
_genSingleEmbedStubs(embed, declared) {
|
|
@@ -671,9 +978,7 @@ export class CodeGen {
|
|
|
671
978
|
for (const [mName] of embedBase.methods.entries()) {
|
|
672
979
|
if (!declared.has(mName)) {
|
|
673
980
|
this.blank();
|
|
674
|
-
this.line(
|
|
675
|
-
`${mName}(...__a) { return ${embedName}.prototype.${mName}.call(this, ...__a); }`,
|
|
676
|
-
);
|
|
981
|
+
this.line(this._embedStub(mName, embedName));
|
|
677
982
|
}
|
|
678
983
|
}
|
|
679
984
|
}
|
|
@@ -682,40 +987,74 @@ export class CodeGen {
|
|
|
682
987
|
const stubs = computeEmbeddedStubs(name, methodDecls, this.checker);
|
|
683
988
|
for (const stub of stubs) {
|
|
684
989
|
this.blank();
|
|
685
|
-
this.line(
|
|
686
|
-
`${stub.methodName}(...__a) { return ${stub.embedName}.prototype.${stub.methodName}.call(this, ...__a); }`,
|
|
687
|
-
);
|
|
990
|
+
this.line(this._embedStub(stub.methodName, stub.embedName));
|
|
688
991
|
}
|
|
689
992
|
}
|
|
690
993
|
|
|
691
|
-
genMethod(decl,
|
|
994
|
+
genMethod(decl, opts = null) {
|
|
692
995
|
const prevFileIdx = this._currentSrcFileIdx;
|
|
693
996
|
if (decl._srcFileIdx != null) this._currentSrcFileIdx = decl._srcFileIdx;
|
|
694
|
-
const
|
|
695
|
-
const
|
|
696
|
-
|
|
997
|
+
const staticNamed = Boolean(opts?.staticNamed);
|
|
998
|
+
const recvParam =
|
|
999
|
+
decl.recvName && decl.recvName !== "_" ? decl.recvName : "__recv";
|
|
1000
|
+
this.line(
|
|
1001
|
+
this._methodSignature(decl, staticNamed, recvParam),
|
|
1002
|
+
decl._line ?? null,
|
|
1003
|
+
);
|
|
697
1004
|
const prevBoxed = this._boxedVars;
|
|
698
|
-
this._boxedVars = new Set();
|
|
1005
|
+
this._boxedVars = new Set(this._boxedGlobals);
|
|
699
1006
|
this._scanAddressTaken(decl.body);
|
|
700
|
-
|
|
1007
|
+
// Methods of a generic struct read its type arguments from the
|
|
1008
|
+
// instance; named non-struct types have no instance to carry them.
|
|
1009
|
+
const tpNames = staticNamed
|
|
1010
|
+
? null
|
|
1011
|
+
: this._genericTypeParamNames(decl.recvType?.name);
|
|
1012
|
+
this._withTypeParamZeros(tpNames, () =>
|
|
1013
|
+
this._genMethodBody(decl, staticNamed, recvParam, tpNames),
|
|
1014
|
+
);
|
|
1015
|
+
this._boxedVars = prevBoxed;
|
|
1016
|
+
this.line("}");
|
|
1017
|
+
// `Unwrap() []error` is told apart from `Unwrap() error` in type tests.
|
|
1018
|
+
if (
|
|
1019
|
+
!staticNamed &&
|
|
1020
|
+
decl.name === "Unwrap" &&
|
|
1021
|
+
decl.returnType?.kind === "SliceType"
|
|
1022
|
+
)
|
|
1023
|
+
this.line("static { this.prototype.Unwrap.__multi = true; }");
|
|
1024
|
+
this._currentSrcFileIdx = prevFileIdx;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
_genMethodBody(decl, staticNamed, recvParam, tpNames) {
|
|
701
1028
|
this._withFnCtx(decl.body, () =>
|
|
702
1029
|
this.indented(() => {
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
}
|
|
1030
|
+
if (tpNames) this._genTypeParamBindings(tpNames, "this?.[__tz]");
|
|
1031
|
+
if (decl.recvName) this._fnCtx.params.add(decl.recvName);
|
|
1032
|
+
if (!staticNamed && decl.recvName && decl.recvName !== "_") {
|
|
1033
|
+
this.line(`let ${decl.recvName} = ${this._receiverValue(decl)};`);
|
|
1034
|
+
}
|
|
1035
|
+
if (staticNamed && this._boxedVars.has(recvParam)) {
|
|
1036
|
+
this.line(`${recvParam} = { value: ${recvParam} };`);
|
|
710
1037
|
}
|
|
711
1038
|
this._emitParamCopies(decl.params);
|
|
712
1039
|
this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
|
|
713
1040
|
}),
|
|
714
1041
|
);
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// JS parameter names; a variadic Go parameter is a rest parameter.
|
|
1045
|
+
_paramNames(params) {
|
|
1046
|
+
return params.map((p, i) =>
|
|
1047
|
+
p.variadic && i === params.length - 1 ? `...${p.name}` : p.name,
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Static methods of named non-struct types take the receiver as first parameter.
|
|
1052
|
+
_methodSignature(decl, staticNamed, recvParam) {
|
|
1053
|
+
const params = this._paramNames(decl.params);
|
|
1054
|
+
const asyncPrefix = decl.async ? "async " : "";
|
|
1055
|
+
if (!staticNamed)
|
|
1056
|
+
return `${asyncPrefix}${decl.name}(${params.join(", ")}) {`;
|
|
1057
|
+
return `static ${asyncPrefix}$${decl.name}(${[recvParam, ...params].join(", ")}) {`;
|
|
719
1058
|
}
|
|
720
1059
|
|
|
721
1060
|
// Value receivers are copies in Go; only materialize the copy when the body mutates it.
|
|
@@ -733,35 +1072,42 @@ export class CodeGen {
|
|
|
733
1072
|
return "this";
|
|
734
1073
|
}
|
|
735
1074
|
|
|
736
|
-
//
|
|
737
|
-
//
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
_namedWrapperField(type, expr = null) {
|
|
741
|
-
if (type?.kind !== "named") return null;
|
|
742
|
-
if (!this.namedWrapperNames.has(type.name)) return null;
|
|
743
|
-
if (expr?.kind === "Ident" && expr.name === this._unwrappedRecv)
|
|
744
|
-
return null;
|
|
745
|
-
const u = type.underlying;
|
|
746
|
-
if (u?.kind === "func") return "_fn";
|
|
747
|
-
if (u?.kind === "map") return "_map";
|
|
748
|
-
return "_items";
|
|
1075
|
+
// Named non-struct values are kept raw in the JS backend, so there is no
|
|
1076
|
+
// wrapper field to unwrap any more; kept for call-site compatibility.
|
|
1077
|
+
_namedWrapperField(_type, _expr = null) {
|
|
1078
|
+
return null;
|
|
749
1079
|
}
|
|
750
1080
|
|
|
751
1081
|
// ── Function declarations ────────────────────────────────────
|
|
752
1082
|
|
|
753
1083
|
genFuncDecl(decl, nameOverride) {
|
|
1084
|
+
const tpNames = decl.typeParams?.length
|
|
1085
|
+
? decl.typeParams.map((tp) => tp.name)
|
|
1086
|
+
: null;
|
|
1087
|
+
this._withTypeParamZeros(tpNames, () =>
|
|
1088
|
+
this._genFuncDecl(decl, nameOverride, tpNames),
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// A generic function takes one zero-value factory per type parameter
|
|
1093
|
+
// before its parameters (see HELPER_GENERICS).
|
|
1094
|
+
_genFuncDecl(decl, nameOverride, tpNames) {
|
|
754
1095
|
const name = nameOverride ?? decl.name;
|
|
755
|
-
const params =
|
|
756
|
-
.map((
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
.join(", ");
|
|
1096
|
+
const params = [
|
|
1097
|
+
...(tpNames ?? []).map((n) => `$z_${n}`),
|
|
1098
|
+
...this._paramNames(decl.params),
|
|
1099
|
+
].join(", ");
|
|
760
1100
|
const asyncPrefix = decl.async ? "async " : "";
|
|
761
1101
|
const srcLine = decl._line ?? null;
|
|
762
1102
|
this.line(`${asyncPrefix}function ${name}(${params}) {`, srcLine);
|
|
1103
|
+
if (decl.intrinsic) {
|
|
1104
|
+
const args = decl.params.map((p) => p.name);
|
|
1105
|
+
this.indented(() => this.line(JS_INTRINSICS[decl.intrinsic](args)));
|
|
1106
|
+
this.line("}");
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
763
1109
|
const prevBoxed = this._boxedVars;
|
|
764
|
-
this._boxedVars = new Set();
|
|
1110
|
+
this._boxedVars = new Set(this._boxedGlobals);
|
|
765
1111
|
this._scanAddressTaken(decl.body);
|
|
766
1112
|
this._withFnCtx(decl.body, () =>
|
|
767
1113
|
this.indented(() => {
|
|
@@ -784,6 +1130,7 @@ export class CodeGen {
|
|
|
784
1130
|
const { name, type } = named[i];
|
|
785
1131
|
const varName = !name || name === "_" ? `__ret_blank$${i}` : name;
|
|
786
1132
|
this._markOwnership(varName, true);
|
|
1133
|
+
this._fnCtx?.params.add(varName);
|
|
787
1134
|
this.line(`let ${varName} = ${this.zeroValueForTypeNode(type)};`);
|
|
788
1135
|
}
|
|
789
1136
|
this.namedReturnVars = named.map((r, i) =>
|
|
@@ -858,9 +1205,51 @@ export class CodeGen {
|
|
|
858
1205
|
this._recoverOkInScope = prevRecoverOk;
|
|
859
1206
|
}
|
|
860
1207
|
|
|
1208
|
+
// Package-level variables whose address is taken anywhere (`&g`, `g.Inc()`
|
|
1209
|
+
// on a pointer-receiver method) are boxed like address-taken locals.
|
|
1210
|
+
_collectBoxedGlobals(program) {
|
|
1211
|
+
const names = new Set();
|
|
1212
|
+
for (const d of program.decls)
|
|
1213
|
+
if (d.kind === "VarDecl")
|
|
1214
|
+
for (const spec of d.decls) for (const n of spec.names) names.add(n);
|
|
1215
|
+
const boxed = new Set();
|
|
1216
|
+
if (names.size === 0) return boxed;
|
|
1217
|
+
for (const n of this._scanAddressTaken(program.decls, new Set()))
|
|
1218
|
+
if (names.has(n)) boxed.add(n);
|
|
1219
|
+
return boxed;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
861
1222
|
// Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
|
|
862
|
-
|
|
863
|
-
|
|
1223
|
+
// A pointer-receiver method call on a named non-struct local (`l.Set(5)`)
|
|
1224
|
+
// takes the variable's address too, so the variable must be boxed.
|
|
1225
|
+
_scanAddressTaken(node, into = this._boxedVars) {
|
|
1226
|
+
scanAddressTaken(node, into);
|
|
1227
|
+
const visit = (n) => {
|
|
1228
|
+
if (!n || typeof n !== "object") return;
|
|
1229
|
+
if (Array.isArray(n)) {
|
|
1230
|
+
for (const item of n) visit(item);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (n.kind === "CallExpr" && n.func?.kind === "SelectorExpr") {
|
|
1234
|
+
const recv = n.func.expr;
|
|
1235
|
+
if (
|
|
1236
|
+
recv?.kind === "Ident" &&
|
|
1237
|
+
this._namedPtrMethodOn(recv._type, n.func.field)
|
|
1238
|
+
)
|
|
1239
|
+
into.add(recv.name);
|
|
1240
|
+
}
|
|
1241
|
+
for (const key of Object.keys(n)) {
|
|
1242
|
+
if (key.startsWith("_") || key === "kind") continue;
|
|
1243
|
+
visit(n[key]);
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
visit(node);
|
|
1247
|
+
return into;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
_namedPtrMethodOn(type, method) {
|
|
1251
|
+
const info = this._namedBoxInfo(type);
|
|
1252
|
+
return Boolean(info?.methods.get(method)?._ptrRecv);
|
|
864
1253
|
}
|
|
865
1254
|
|
|
866
1255
|
_isReferenceType(t) {
|
|
@@ -891,15 +1280,11 @@ export class CodeGen {
|
|
|
891
1280
|
}
|
|
892
1281
|
return js;
|
|
893
1282
|
});
|
|
894
|
-
|
|
895
|
-
this.line(`let ${spec.names[0]} = ${vals[0]};`);
|
|
896
|
-
} else {
|
|
897
|
-
// let [a, b] = [v1, v2]
|
|
898
|
-
this.line(`let [${spec.names.join(", ")}] = [${vals.join(", ")}];`);
|
|
899
|
-
}
|
|
1283
|
+
this._emitVarBindings(spec.names, vals, paired);
|
|
900
1284
|
} else {
|
|
901
1285
|
const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
|
|
902
1286
|
for (const name of spec.names) {
|
|
1287
|
+
if (name === "_") continue;
|
|
903
1288
|
this._markOwnership(name, true);
|
|
904
1289
|
const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
|
|
905
1290
|
this.line(`let ${name} = ${val};`);
|
|
@@ -908,6 +1293,26 @@ export class CodeGen {
|
|
|
908
1293
|
}
|
|
909
1294
|
}
|
|
910
1295
|
|
|
1296
|
+
// `var a = v` / `var a, b = v1, v2` / `var a, b = f()`, boxing address-taken names.
|
|
1297
|
+
_emitVarBindings(names, vals, paired) {
|
|
1298
|
+
if (names.length === 1 && names[0] === "_") {
|
|
1299
|
+
// A blank binding only runs its initialiser (`var _ = f()` may repeat).
|
|
1300
|
+
this.line(`void (${vals[0]});`);
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
if (names.length === 1) {
|
|
1304
|
+
const val = this._boxedVars.has(names[0])
|
|
1305
|
+
? `{ value: ${vals[0]} }`
|
|
1306
|
+
: vals[0];
|
|
1307
|
+
this.line(`let ${names[0]} = ${val};`);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
const rhs = paired ? `[${vals.join(", ")}]` : vals[0];
|
|
1311
|
+
const targets = names.map((n) => (n === "_" ? "" : n));
|
|
1312
|
+
this.line(`let [${targets.join(", ")}] = ${rhs};`);
|
|
1313
|
+
this._boxDeclaredNames(names);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
911
1316
|
genConstDecl(decl) {
|
|
912
1317
|
for (const spec of decl.decls) {
|
|
913
1318
|
const vals = spec.value.map((v) => this.genExpr(v));
|
|
@@ -924,5 +1329,6 @@ export class CodeGen {
|
|
|
924
1329
|
|
|
925
1330
|
Object.assign(CodeGen.prototype, statementGenMethods);
|
|
926
1331
|
Object.assign(CodeGen.prototype, expressionGenMethods);
|
|
1332
|
+
Object.assign(CodeGen.prototype, typeInfoGenMethods);
|
|
927
1333
|
Object.assign(CodeGen.prototype, stdlibGenMethods);
|
|
928
1334
|
Object.assign(CodeGen.prototype, templGenMethods);
|