gofront 1.7.0 → 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 +13 -13
- package/package.json +8 -37
- package/src/backend/js/expressions.js +1341 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +375 -57
- package/src/backend/js/runtime.js +1460 -133
- package/src/backend/js/statements.js +900 -86
- 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 +2 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +8 -3
- package/src/backend/js/stdlib/json.js +247 -52
- 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 +10 -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 +436 -80
- package/src/backend/wasm/emit-builtins.js +303 -13
- package/src/backend/wasm/emit-exprs.js +1397 -203
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +512 -77
- package/src/backend/wasm/encode.js +43 -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/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 +0 -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 +293 -28
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +200 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -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} +3 -4
- package/src/compiler.js +107 -269
- 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 -1
- package/src/lower/captures.js +176 -32
- 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/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +69 -9
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +176 -17
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +931 -122
- package/src/typechecker/index.js +459 -67
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +451 -56
- package/src/typechecker/statements.js +90 -9
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +322 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +149 -25
- 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,34 +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,
|
|
44
54
|
HELPER_IFEQ,
|
|
45
55
|
HELPER_INJECT_STYLES,
|
|
46
56
|
HELPER_IO_EOF,
|
|
47
57
|
HELPER_JSON,
|
|
48
58
|
HELPER_LEN,
|
|
49
59
|
HELPER_NIL_CALL,
|
|
60
|
+
HELPER_NIL_MAP,
|
|
61
|
+
HELPER_NM,
|
|
62
|
+
HELPER_OS,
|
|
50
63
|
HELPER_PANIC,
|
|
51
64
|
HELPER_PATH_CLEAN,
|
|
52
65
|
HELPER_PATH_ERROR,
|
|
66
|
+
HELPER_PTREQ,
|
|
53
67
|
HELPER_S,
|
|
54
68
|
HELPER_SCLONE,
|
|
55
69
|
HELPER_SHA256,
|
|
70
|
+
HELPER_SHIFT,
|
|
56
71
|
HELPER_SIMD,
|
|
72
|
+
HELPER_SLICE,
|
|
57
73
|
HELPER_SORT_SLICE,
|
|
74
|
+
HELPER_SPRINT,
|
|
58
75
|
HELPER_SPRINTF,
|
|
59
76
|
HELPER_STRCONV,
|
|
60
77
|
HELPER_STRCONV_ERROR,
|
|
78
|
+
HELPER_STRUCT_KEY,
|
|
79
|
+
HELPER_SV,
|
|
61
80
|
HELPER_SYNC,
|
|
62
81
|
HELPER_TESTING,
|
|
63
82
|
HELPER_TIME_FMT,
|
|
64
83
|
HELPER_TIME_PARSE,
|
|
84
|
+
HELPER_VA,
|
|
85
|
+
HELPER_VCLONE,
|
|
65
86
|
} from "./runtime.js";
|
|
66
87
|
import { buildSourceMap, buildWasmSourceMap } from "./source-map.js";
|
|
67
88
|
import { statementGenMethods } from "./statements.js";
|
|
68
89
|
import { stdlibGenMethods } from "./stdlib/index.js";
|
|
69
90
|
import { templGenMethods } from "./templ.js";
|
|
91
|
+
import { typeInfoGenMethods } from "./type-info.js";
|
|
70
92
|
|
|
71
93
|
export { buildSourceMap, buildWasmSourceMap };
|
|
72
94
|
|
|
@@ -110,21 +132,41 @@ const JS_RESERVED = new Set([
|
|
|
110
132
|
const jsSafeName = (n) =>
|
|
111
133
|
typeof n === "string" && JS_RESERVED.has(n) ? `${n}$` : n;
|
|
112
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
|
+
|
|
113
141
|
// Runtime helpers by registry name, in output order. Mark one as needed with
|
|
114
142
|
// `this.useHelper(name)`; `_prependHelpers` emits every marked entry.
|
|
115
143
|
const HELPER_MAP = [
|
|
116
144
|
["len", HELPER_LEN],
|
|
145
|
+
["sv", HELPER_SV],
|
|
146
|
+
["slice", HELPER_SLICE],
|
|
117
147
|
["append", HELPER_APPEND],
|
|
118
148
|
["sliceGuard", HELPER_S],
|
|
119
149
|
["sclone", HELPER_SCLONE],
|
|
150
|
+
["va", HELPER_VA],
|
|
151
|
+
["vclone", HELPER_VCLONE],
|
|
120
152
|
["ifaceBox", HELPER_IFACE_BOX],
|
|
153
|
+
["ifd", HELPER_IFD],
|
|
121
154
|
["equal", HELPER_EQUAL],
|
|
155
|
+
["geq", HELPER_GEQ],
|
|
156
|
+
["ptreq", HELPER_PTREQ],
|
|
122
157
|
["ifeq", HELPER_IFEQ],
|
|
123
158
|
["cmul", HELPER_CMUL],
|
|
124
159
|
["cdiv", HELPER_CDIV],
|
|
125
160
|
["panicRt", HELPER_PANIC],
|
|
161
|
+
["shift", HELPER_SHIFT],
|
|
126
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],
|
|
127
168
|
["sprintf", HELPER_SPRINTF],
|
|
169
|
+
["sprint", HELPER_SPRINT],
|
|
128
170
|
["error", HELPER_ERROR],
|
|
129
171
|
["errorIs", HELPER_ERROR_IS],
|
|
130
172
|
["errorAs", HELPER_ERROR_AS],
|
|
@@ -132,6 +174,7 @@ const HELPER_MAP = [
|
|
|
132
174
|
["strconvError", HELPER_STRCONV_ERROR],
|
|
133
175
|
["pathError", HELPER_PATH_ERROR],
|
|
134
176
|
["ioEOF", HELPER_IO_EOF],
|
|
177
|
+
["os", HELPER_OS],
|
|
135
178
|
["strconv", HELPER_STRCONV],
|
|
136
179
|
["pathClean", HELPER_PATH_CLEAN],
|
|
137
180
|
["sortSlice", HELPER_SORT_SLICE],
|
|
@@ -149,6 +192,10 @@ const HELPER_MAP = [
|
|
|
149
192
|
["simd", HELPER_SIMD],
|
|
150
193
|
];
|
|
151
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
|
+
|
|
152
199
|
export class CodeGen {
|
|
153
200
|
// jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
|
|
154
201
|
// bundledPackages: Set<string> — GoFront package names bundled inline;
|
|
@@ -168,6 +215,9 @@ export class CodeGen {
|
|
|
168
215
|
this.structFields = new Map();
|
|
169
216
|
this.namedWrapperNames = new Set();
|
|
170
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
|
|
171
221
|
this.jsImports = jsImports;
|
|
172
222
|
this.bundledPackages = bundledPackages;
|
|
173
223
|
this.namedReturnVars = null; // names of current function's named return vars
|
|
@@ -175,6 +225,8 @@ export class CodeGen {
|
|
|
175
225
|
this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
|
|
176
226
|
this._boxedVars = new Set(); // address-taken scalar variables that need boxing
|
|
177
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
|
|
178
230
|
// Runtime helper usage tracking — only emit helpers that are actually used
|
|
179
231
|
this._helpers = new Set();
|
|
180
232
|
// Per-function context for Go value semantics (see _withFnCtx)
|
|
@@ -221,6 +273,9 @@ export class CodeGen {
|
|
|
221
273
|
}
|
|
222
274
|
|
|
223
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);
|
|
224
279
|
for (const d of program.decls) {
|
|
225
280
|
if (d.kind === "TypeDecl") {
|
|
226
281
|
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
@@ -231,20 +286,28 @@ export class CodeGen {
|
|
|
231
286
|
}
|
|
232
287
|
|
|
233
288
|
_emitVarConstDecls(program) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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;
|
|
239
300
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
this.
|
|
244
|
-
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] });
|
|
245
305
|
this.blank();
|
|
246
306
|
}
|
|
247
|
-
}
|
|
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);
|
|
248
311
|
}
|
|
249
312
|
|
|
250
313
|
_callInitAndMain(initNames, program, isTest = false) {
|
|
@@ -262,19 +325,28 @@ export class CodeGen {
|
|
|
262
325
|
|
|
263
326
|
generate(program, options = {}) {
|
|
264
327
|
const isTest = options.isTest ?? false;
|
|
328
|
+
this._precisionWarned = new Set();
|
|
265
329
|
// Defer lowering (argument/receiver hoisting, recover-target marking) is
|
|
266
330
|
// idempotent and owned by the backends: the wasm path runs it via
|
|
267
331
|
// `lower()`, the JS path here.
|
|
268
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];
|
|
269
336
|
// Ensure _pkgName is set on all decls (generateAll sets it for
|
|
270
337
|
// multi-file; for single-file compiles we derive it from the AST).
|
|
271
338
|
const pkgName = program.pkg?.name;
|
|
272
339
|
if (pkgName) {
|
|
340
|
+
this.pkgName = pkgName;
|
|
273
341
|
for (const d of program.decls) {
|
|
274
342
|
if (!d._pkgName) d._pkgName = pkgName;
|
|
275
343
|
}
|
|
276
344
|
}
|
|
277
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);
|
|
278
350
|
const methods = this._collectDecls(program);
|
|
279
351
|
this._emitJsImports();
|
|
280
352
|
this._emitTypeDecls(program, methods);
|
|
@@ -331,12 +403,12 @@ export class CodeGen {
|
|
|
331
403
|
}
|
|
332
404
|
|
|
333
405
|
_renameParams(params) {
|
|
334
|
-
for (const p of params ?? []) p.name =
|
|
406
|
+
for (const p of params ?? []) p.name = jsBindingName(p.name);
|
|
335
407
|
}
|
|
336
408
|
|
|
337
409
|
_renameNamedReturns(node) {
|
|
338
410
|
for (const r of node.returnType?._namedReturns ?? [])
|
|
339
|
-
r.name =
|
|
411
|
+
r.name = jsBindingName(r.name);
|
|
340
412
|
}
|
|
341
413
|
|
|
342
414
|
_renameSignature(node) {
|
|
@@ -345,27 +417,37 @@ export class CodeGen {
|
|
|
345
417
|
}
|
|
346
418
|
|
|
347
419
|
_renameIdentNode(node) {
|
|
348
|
-
if (
|
|
349
|
-
|
|
420
|
+
if (node._isStructKey || node._type?.kind === "builtin") return;
|
|
421
|
+
node.name = node._shadowsGlobal
|
|
422
|
+
? jsBindingName(node.name)
|
|
423
|
+
: jsSafeName(node.name);
|
|
350
424
|
}
|
|
351
425
|
|
|
352
426
|
_renameFuncDeclNode(node) {
|
|
353
|
-
node.name =
|
|
427
|
+
node.name = jsBindingName(node.name);
|
|
354
428
|
this._renameSignature(node);
|
|
355
429
|
}
|
|
356
430
|
|
|
357
431
|
_renameMethodDeclNode(node) {
|
|
358
|
-
node.recvName =
|
|
432
|
+
node.recvName = jsBindingName(node.recvName);
|
|
359
433
|
this._renameSignature(node);
|
|
360
434
|
}
|
|
361
435
|
|
|
362
436
|
_renameDeclSpecs(node) {
|
|
363
437
|
for (const spec of node.decls ?? [])
|
|
364
|
-
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);
|
|
365
447
|
}
|
|
366
448
|
|
|
367
449
|
_renameTypeSwitchNode(node) {
|
|
368
|
-
node.assign =
|
|
450
|
+
node.assign = jsBindingName(node.assign);
|
|
369
451
|
}
|
|
370
452
|
|
|
371
453
|
_markStructKeys(node) {
|
|
@@ -384,7 +466,15 @@ export class CodeGen {
|
|
|
384
466
|
MethodDecl: "_renameMethodDeclNode",
|
|
385
467
|
FuncLit: "_renameSignature",
|
|
386
468
|
TemplDecl: "_renameSignature",
|
|
469
|
+
TemplExpr: "_renameTemplExprNode",
|
|
470
|
+
TemplComponent: "_renameTemplExprNode",
|
|
471
|
+
TemplIf: "_renameTemplExprNode",
|
|
472
|
+
TemplSwitch: "_renameTemplExprNode",
|
|
473
|
+
TemplCase: "_renameTemplExprNode",
|
|
474
|
+
TemplFor: "_renameTemplForNode",
|
|
475
|
+
TemplElement: "_renameTemplElementNode",
|
|
387
476
|
CssDecl: "_renameSignature",
|
|
477
|
+
DefineStmt: "_renameDefineNode",
|
|
388
478
|
VarDecl: "_renameDeclSpecs",
|
|
389
479
|
ConstDecl: "_renameDeclSpecs",
|
|
390
480
|
TypeSwitchStmt: "_renameTypeSwitchNode",
|
|
@@ -410,6 +500,18 @@ export class CodeGen {
|
|
|
410
500
|
// Collects struct names, method map, and named wrapper names from program decls.
|
|
411
501
|
// Returns the method map (typeName → MethodDecl[]).
|
|
412
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
|
+
}
|
|
413
515
|
for (const d of program.decls) {
|
|
414
516
|
if (d.kind === "TypeDecl" && d.type.kind === "StructType")
|
|
415
517
|
this.structNames.add(d.name);
|
|
@@ -427,11 +529,14 @@ export class CodeGen {
|
|
|
427
529
|
|
|
428
530
|
_collectMethodMap(program) {
|
|
429
531
|
const methods = new Map();
|
|
532
|
+
this.nilSafeMethods = new Set();
|
|
430
533
|
for (const d of program.decls) {
|
|
431
534
|
if (d.kind === "MethodDecl") {
|
|
432
535
|
const name = d.recvType.name;
|
|
433
536
|
if (!methods.has(name)) methods.set(name, []);
|
|
434
537
|
methods.get(name).push(d);
|
|
538
|
+
if (d.recvPointer && comparesToNil(d.body, d.recvName))
|
|
539
|
+
this.nilSafeMethods.add(`${name}.${d.name}`);
|
|
435
540
|
}
|
|
436
541
|
}
|
|
437
542
|
return methods;
|
|
@@ -460,6 +565,8 @@ export class CodeGen {
|
|
|
460
565
|
/** Marks a runtime helper (key of HELPER_MAP) as needed by the output. */
|
|
461
566
|
useHelper(name) {
|
|
462
567
|
this._helpers.add(name);
|
|
568
|
+
if (name === "ifeq" || name === "geq") this._helpers.add("equal");
|
|
569
|
+
if (name === "geq") this._helpers.add("ifeq");
|
|
463
570
|
if (
|
|
464
571
|
name === "strconvError" ||
|
|
465
572
|
name === "pathError" ||
|
|
@@ -473,6 +580,13 @@ export class CodeGen {
|
|
|
473
580
|
if (name === "json") {
|
|
474
581
|
this.useHelper("base64");
|
|
475
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");
|
|
476
590
|
if (name === "binary") {
|
|
477
591
|
this.useHelper("ioEOF");
|
|
478
592
|
this._helpers.add("error");
|
|
@@ -501,16 +615,29 @@ export class CodeGen {
|
|
|
501
615
|
// Generate a single bundle from multiple programs (same-package multi-file).
|
|
502
616
|
// Annotates each decl with its source file index before merging.
|
|
503
617
|
generateAll(programs, options = {}) {
|
|
618
|
+
this._srcFileNames = programs.map((p) => p._filename ?? null);
|
|
504
619
|
for (let i = 0; i < programs.length; i++) {
|
|
505
620
|
for (const decl of programs[i].decls) {
|
|
506
621
|
decl._srcFileIdx = i;
|
|
507
622
|
decl._pkgName = programs[i].pkg?.name;
|
|
508
623
|
}
|
|
509
624
|
}
|
|
510
|
-
const merged = {
|
|
625
|
+
const merged = {
|
|
626
|
+
pkg: programs[0]?.pkg,
|
|
627
|
+
decls: programs.flatMap((p) => p.decls),
|
|
628
|
+
};
|
|
511
629
|
return this.generate(merged, options);
|
|
512
630
|
}
|
|
513
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
|
+
|
|
514
641
|
getCss() {
|
|
515
642
|
return [...new Set(this.collectedCss)].join("\n\n");
|
|
516
643
|
}
|
|
@@ -529,14 +656,40 @@ export class CodeGen {
|
|
|
529
656
|
|
|
530
657
|
// ── Type declarations ────────────────────────────────────────
|
|
531
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
|
+
|
|
532
676
|
genTypeDeclWithMethods(decl, methodDecls) {
|
|
677
|
+
const definedStruct =
|
|
678
|
+
decl.type.kind === "StructType" ? null : this._definedStructAst(decl);
|
|
533
679
|
if (decl.type.kind === "StructType") {
|
|
534
680
|
this.genStruct(decl.name, decl.type, methodDecls);
|
|
681
|
+
} else if (definedStruct) {
|
|
682
|
+
this.genStruct(decl.name, definedStruct, methodDecls);
|
|
535
683
|
} else if (decl.type.kind === "InterfaceType") {
|
|
536
684
|
// Interfaces are compile-time only — no JS output needed.
|
|
537
685
|
this.line(`// interface ${decl.name} (compile-time only)`);
|
|
538
|
-
} else if (
|
|
686
|
+
} else if (
|
|
687
|
+
methodDecls.length > 0 ||
|
|
688
|
+
this._isPlainNamedBox(this.checker?.types.get(decl.name))
|
|
689
|
+
) {
|
|
539
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)}`);
|
|
540
693
|
this.genNamedTypeClass(decl.name, methodDecls);
|
|
541
694
|
} else {
|
|
542
695
|
this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
|
|
@@ -589,12 +742,28 @@ export class CodeGen {
|
|
|
589
742
|
return "_items";
|
|
590
743
|
}
|
|
591
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
|
+
|
|
592
755
|
// Box/dispatch info for a named non-struct type with methods, or null.
|
|
593
756
|
_namedBoxInfo(type) {
|
|
594
757
|
if (type?.kind !== "named") return null;
|
|
595
758
|
const u = type.underlying;
|
|
596
759
|
if (!u || u.kind === "struct" || u.kind === "interface") return null;
|
|
597
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
|
+
};
|
|
598
767
|
if (!methods || methods.size === 0) return null;
|
|
599
768
|
if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
|
|
600
769
|
return null;
|
|
@@ -626,9 +795,11 @@ export class CodeGen {
|
|
|
626
795
|
const resolved = this.checker.types.get(typeName)?.underlying;
|
|
627
796
|
if (resolved?.kind === "struct") {
|
|
628
797
|
const fields = [];
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
+
});
|
|
632
803
|
this.structFields.set(typeName, fields);
|
|
633
804
|
return fields;
|
|
634
805
|
}
|
|
@@ -661,17 +832,69 @@ export class CodeGen {
|
|
|
661
832
|
this.line("}");
|
|
662
833
|
}
|
|
663
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
|
+
|
|
664
876
|
_genStructClone(name, structTypeAst, fields) {
|
|
665
877
|
const resolved = this.checker?.types.get(name)?.underlying;
|
|
666
878
|
const astTypes = new Map();
|
|
667
879
|
for (const f of structTypeAst?.fields ?? [])
|
|
668
880
|
for (const n of f.names ?? []) astTypes.set(n, f.type);
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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]");
|
|
675
898
|
this.blank();
|
|
676
899
|
this.line(`__clone() { return new ${name}(${args.join(", ")}); }`);
|
|
677
900
|
}
|
|
@@ -685,18 +908,38 @@ export class CodeGen {
|
|
|
685
908
|
);
|
|
686
909
|
}
|
|
687
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
|
+
|
|
688
918
|
genStruct(name, structTypeAst, methodDecls) {
|
|
689
|
-
const
|
|
690
|
-
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
|
+
);
|
|
691
932
|
this.line(`class ${name} {`);
|
|
692
933
|
this.indented(() => {
|
|
693
|
-
this.
|
|
934
|
+
if (tpNames) this._genGenericStructConstructor(fields, tpNames);
|
|
935
|
+
else this._genStructConstructor(fields);
|
|
694
936
|
for (const m of methodDecls) {
|
|
695
937
|
this.blank();
|
|
696
938
|
this.genMethod(m);
|
|
697
939
|
}
|
|
698
940
|
this._genEmbeddedMethodStubs(name, methodDecls);
|
|
699
941
|
this._genStructClone(name, structTypeAst, fields);
|
|
942
|
+
this._genStructKeyString(name, fields);
|
|
700
943
|
});
|
|
701
944
|
this.line("}");
|
|
702
945
|
// Lets generic `p.value` dereferences resolve to the struct itself.
|
|
@@ -705,9 +948,11 @@ export class CodeGen {
|
|
|
705
948
|
`Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
|
|
706
949
|
);
|
|
707
950
|
}
|
|
951
|
+
this._genFmtClassMeta(name, methodDecls);
|
|
708
952
|
}
|
|
709
953
|
|
|
710
954
|
_emitJsonCodecs() {
|
|
955
|
+
this._emitJsonAnyHooks();
|
|
711
956
|
if (!this._jsonCodecs || this._jsonCodecs.size === 0) return;
|
|
712
957
|
for (const [, code] of this._jsonCodecs) {
|
|
713
958
|
this.line(code);
|
|
@@ -715,6 +960,16 @@ export class CodeGen {
|
|
|
715
960
|
}
|
|
716
961
|
}
|
|
717
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}; }`;
|
|
971
|
+
}
|
|
972
|
+
|
|
718
973
|
_genSingleEmbedStubs(embed, declared) {
|
|
719
974
|
const embedName = embed.kind === "named" ? embed.name : null;
|
|
720
975
|
if (!embedName) return;
|
|
@@ -723,9 +978,7 @@ export class CodeGen {
|
|
|
723
978
|
for (const [mName] of embedBase.methods.entries()) {
|
|
724
979
|
if (!declared.has(mName)) {
|
|
725
980
|
this.blank();
|
|
726
|
-
this.line(
|
|
727
|
-
`${mName}(...__a) { return ${embedName}.prototype.${mName}.call(this, ...__a); }`,
|
|
728
|
-
);
|
|
981
|
+
this.line(this._embedStub(mName, embedName));
|
|
729
982
|
}
|
|
730
983
|
}
|
|
731
984
|
}
|
|
@@ -734,9 +987,7 @@ export class CodeGen {
|
|
|
734
987
|
const stubs = computeEmbeddedStubs(name, methodDecls, this.checker);
|
|
735
988
|
for (const stub of stubs) {
|
|
736
989
|
this.blank();
|
|
737
|
-
this.line(
|
|
738
|
-
`${stub.methodName}(...__a) { return ${stub.embedName}.prototype.${stub.methodName}.call(this, ...__a); }`,
|
|
739
|
-
);
|
|
990
|
+
this.line(this._embedStub(stub.methodName, stub.embedName));
|
|
740
991
|
}
|
|
741
992
|
}
|
|
742
993
|
|
|
@@ -751,12 +1002,35 @@ export class CodeGen {
|
|
|
751
1002
|
decl._line ?? null,
|
|
752
1003
|
);
|
|
753
1004
|
const prevBoxed = this._boxedVars;
|
|
754
|
-
this._boxedVars = new Set();
|
|
1005
|
+
this._boxedVars = new Set(this._boxedGlobals);
|
|
755
1006
|
this._scanAddressTaken(decl.body);
|
|
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) {
|
|
756
1028
|
this._withFnCtx(decl.body, () =>
|
|
757
1029
|
this.indented(() => {
|
|
1030
|
+
if (tpNames) this._genTypeParamBindings(tpNames, "this?.[__tz]");
|
|
1031
|
+
if (decl.recvName) this._fnCtx.params.add(decl.recvName);
|
|
758
1032
|
if (!staticNamed && decl.recvName && decl.recvName !== "_") {
|
|
759
|
-
this.line(`
|
|
1033
|
+
this.line(`let ${decl.recvName} = ${this._receiverValue(decl)};`);
|
|
760
1034
|
}
|
|
761
1035
|
if (staticNamed && this._boxedVars.has(recvParam)) {
|
|
762
1036
|
this.line(`${recvParam} = { value: ${recvParam} };`);
|
|
@@ -765,14 +1039,18 @@ export class CodeGen {
|
|
|
765
1039
|
this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
|
|
766
1040
|
}),
|
|
767
1041
|
);
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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
|
+
);
|
|
771
1049
|
}
|
|
772
1050
|
|
|
773
1051
|
// Static methods of named non-struct types take the receiver as first parameter.
|
|
774
1052
|
_methodSignature(decl, staticNamed, recvParam) {
|
|
775
|
-
const params = decl.params
|
|
1053
|
+
const params = this._paramNames(decl.params);
|
|
776
1054
|
const asyncPrefix = decl.async ? "async " : "";
|
|
777
1055
|
if (!staticNamed)
|
|
778
1056
|
return `${asyncPrefix}${decl.name}(${params.join(", ")}) {`;
|
|
@@ -803,17 +1081,33 @@ export class CodeGen {
|
|
|
803
1081
|
// ── Function declarations ────────────────────────────────────
|
|
804
1082
|
|
|
805
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) {
|
|
806
1095
|
const name = nameOverride ?? decl.name;
|
|
807
|
-
const params =
|
|
808
|
-
.map((
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
.join(", ");
|
|
1096
|
+
const params = [
|
|
1097
|
+
...(tpNames ?? []).map((n) => `$z_${n}`),
|
|
1098
|
+
...this._paramNames(decl.params),
|
|
1099
|
+
].join(", ");
|
|
812
1100
|
const asyncPrefix = decl.async ? "async " : "";
|
|
813
1101
|
const srcLine = decl._line ?? null;
|
|
814
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
|
+
}
|
|
815
1109
|
const prevBoxed = this._boxedVars;
|
|
816
|
-
this._boxedVars = new Set();
|
|
1110
|
+
this._boxedVars = new Set(this._boxedGlobals);
|
|
817
1111
|
this._scanAddressTaken(decl.body);
|
|
818
1112
|
this._withFnCtx(decl.body, () =>
|
|
819
1113
|
this.indented(() => {
|
|
@@ -836,6 +1130,7 @@ export class CodeGen {
|
|
|
836
1130
|
const { name, type } = named[i];
|
|
837
1131
|
const varName = !name || name === "_" ? `__ret_blank$${i}` : name;
|
|
838
1132
|
this._markOwnership(varName, true);
|
|
1133
|
+
this._fnCtx?.params.add(varName);
|
|
839
1134
|
this.line(`let ${varName} = ${this.zeroValueForTypeNode(type)};`);
|
|
840
1135
|
}
|
|
841
1136
|
this.namedReturnVars = named.map((r, i) =>
|
|
@@ -910,11 +1205,25 @@ export class CodeGen {
|
|
|
910
1205
|
this._recoverOkInScope = prevRecoverOk;
|
|
911
1206
|
}
|
|
912
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
|
+
|
|
913
1222
|
// Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
|
|
914
1223
|
// A pointer-receiver method call on a named non-struct local (`l.Set(5)`)
|
|
915
1224
|
// takes the variable's address too, so the variable must be boxed.
|
|
916
|
-
_scanAddressTaken(node) {
|
|
917
|
-
scanAddressTaken(node,
|
|
1225
|
+
_scanAddressTaken(node, into = this._boxedVars) {
|
|
1226
|
+
scanAddressTaken(node, into);
|
|
918
1227
|
const visit = (n) => {
|
|
919
1228
|
if (!n || typeof n !== "object") return;
|
|
920
1229
|
if (Array.isArray(n)) {
|
|
@@ -927,7 +1236,7 @@ export class CodeGen {
|
|
|
927
1236
|
recv?.kind === "Ident" &&
|
|
928
1237
|
this._namedPtrMethodOn(recv._type, n.func.field)
|
|
929
1238
|
)
|
|
930
|
-
|
|
1239
|
+
into.add(recv.name);
|
|
931
1240
|
}
|
|
932
1241
|
for (const key of Object.keys(n)) {
|
|
933
1242
|
if (key.startsWith("_") || key === "kind") continue;
|
|
@@ -935,6 +1244,7 @@ export class CodeGen {
|
|
|
935
1244
|
}
|
|
936
1245
|
};
|
|
937
1246
|
visit(node);
|
|
1247
|
+
return into;
|
|
938
1248
|
}
|
|
939
1249
|
|
|
940
1250
|
_namedPtrMethodOn(type, method) {
|
|
@@ -974,6 +1284,7 @@ export class CodeGen {
|
|
|
974
1284
|
} else {
|
|
975
1285
|
const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
|
|
976
1286
|
for (const name of spec.names) {
|
|
1287
|
+
if (name === "_") continue;
|
|
977
1288
|
this._markOwnership(name, true);
|
|
978
1289
|
const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
|
|
979
1290
|
this.line(`let ${name} = ${val};`);
|
|
@@ -984,6 +1295,11 @@ export class CodeGen {
|
|
|
984
1295
|
|
|
985
1296
|
// `var a = v` / `var a, b = v1, v2` / `var a, b = f()`, boxing address-taken names.
|
|
986
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
|
+
}
|
|
987
1303
|
if (names.length === 1) {
|
|
988
1304
|
const val = this._boxedVars.has(names[0])
|
|
989
1305
|
? `{ value: ${vals[0]} }`
|
|
@@ -992,7 +1308,8 @@ export class CodeGen {
|
|
|
992
1308
|
return;
|
|
993
1309
|
}
|
|
994
1310
|
const rhs = paired ? `[${vals.join(", ")}]` : vals[0];
|
|
995
|
-
|
|
1311
|
+
const targets = names.map((n) => (n === "_" ? "" : n));
|
|
1312
|
+
this.line(`let [${targets.join(", ")}] = ${rhs};`);
|
|
996
1313
|
this._boxDeclaredNames(names);
|
|
997
1314
|
}
|
|
998
1315
|
|
|
@@ -1012,5 +1329,6 @@ export class CodeGen {
|
|
|
1012
1329
|
|
|
1013
1330
|
Object.assign(CodeGen.prototype, statementGenMethods);
|
|
1014
1331
|
Object.assign(CodeGen.prototype, expressionGenMethods);
|
|
1332
|
+
Object.assign(CodeGen.prototype, typeInfoGenMethods);
|
|
1015
1333
|
Object.assign(CodeGen.prototype, stdlibGenMethods);
|
|
1016
1334
|
Object.assign(CodeGen.prototype, templGenMethods);
|