gofront 1.7.0 → 1.9.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 +14 -14
- package/package.json +10 -38
- package/src/backend/js/expressions.js +2060 -389
- package/src/backend/js/index.js +586 -174
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +936 -1644
- package/src/backend/js/statements.js +1349 -132
- package/src/backend/js/stdlib/index.js +51 -94
- package/src/backend/js/templ.js +67 -118
- package/src/backend/js/type-info.js +436 -0
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +80 -0
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +753 -872
- package/src/backend/wasm/emit-builtins.js +914 -237
- package/src/backend/wasm/emit-exprs.js +1456 -195
- package/src/backend/wasm/emit-maps.js +78 -380
- package/src/backend/wasm/emit-stmts.js +1659 -214
- package/src/backend/wasm/emit.js +614 -88
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +491 -0
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +406 -3195
- package/src/backend/wasm/method-expr.js +392 -0
- package/src/backend/wasm/module-emitter.js +1615 -0
- package/src/backend/wasm/monomorph.js +164 -9
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +249 -0
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -37
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/types.js +260 -27
- 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} +241 -11
- package/src/{test-runner.js → cli/test-runner.js} +188 -122
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +202 -386
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +203 -62
- package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
- package/src/{tokens.js → lexer/tokens.js} +2 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +737 -0
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +318 -0
- package/src/lower/bounds.js +547 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +267 -64
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +33 -19
- package/src/lower/json-codecs.js +1379 -0
- package/src/lower/ownership.js +47 -8
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +150 -0
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +83 -15
- package/src/parser/expressions.js +110 -18
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +38 -22
- package/src/{templ-parser.js → parser/templ.js} +179 -17
- package/src/parser/types.js +70 -9
- package/src/resolver.js +21 -1
- package/src/typechecker/assignability.js +291 -19
- package/src/typechecker/const-fold.js +399 -0
- package/src/typechecker/constants.js +282 -0
- package/src/typechecker/expressions.js +1220 -183
- package/src/typechecker/index.js +659 -92
- package/src/typechecker/init-order.js +426 -0
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +613 -62
- package/src/typechecker/statements.js +212 -20
- package/src/typechecker/stdlib/excluded.js +902 -0
- package/src/typechecker/stdlib/index.js +38 -0
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +11 -2
- package/src/typechecker/types.js +268 -514
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +871 -0
- package/stdlib/internal/jsonrt/enc.go +516 -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 +658 -0
- package/stdlib/io/io.go +502 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +272 -0
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +500 -0
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +526 -0
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +250 -0
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -46
- package/src/backend/js/stdlib/builder.js +0 -63
- package/src/backend/js/stdlib/bytes.js +0 -122
- package/src/backend/js/stdlib/errors.js +0 -44
- package/src/backend/js/stdlib/fmt.js +0 -178
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -972
- package/src/backend/js/stdlib/maps.js +0 -45
- package/src/backend/js/stdlib/math.js +0 -69
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -24
- package/src/backend/js/stdlib/slices.js +0 -77
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -74
- package/src/backend/js/stdlib/strings.js +0 -222
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -34
- package/src/backend/js/stdlib/time.js +0 -128
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- package/src/backend/wasm/stdlib/bytes.js +0 -5039
- package/src/backend/wasm/stdlib/core.js +0 -8903
- package/src/backend/wasm/stdlib/io.js +0 -2161
- package/src/backend/wasm/stdlib/packages.js +0 -2400
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -1200
- package/src/typechecker/stdlib/core.js +0 -801
- package/src/typechecker/stdlib/extended.js +0 -914
- package/src/typechecker/stdlib.js +0 -22
- /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/backend/js/index.js
CHANGED
|
@@ -20,53 +20,64 @@
|
|
|
20
20
|
import {
|
|
21
21
|
computeEmbeddedStubs,
|
|
22
22
|
hasDirectRecover,
|
|
23
|
-
|
|
23
|
+
markInBoundsIndexes,
|
|
24
|
+
markRedundantNilChecks,
|
|
24
25
|
normalizeDefers,
|
|
25
26
|
scanAddressTaken,
|
|
27
|
+
takesElementAddress,
|
|
26
28
|
} from "../../lower/index.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
+
import { orderPackageInits } from "../../typechecker/init-order.js";
|
|
30
|
+
import {
|
|
31
|
+
BROWSER_GLOBALS,
|
|
32
|
+
isComplex,
|
|
33
|
+
isNumeric,
|
|
34
|
+
} from "../../typechecker/types.js";
|
|
35
|
+
import {
|
|
36
|
+
unicodeIsJs,
|
|
37
|
+
unicodeToLowerJs,
|
|
38
|
+
unicodeToUpperJs,
|
|
39
|
+
} from "../shared/host-unicode.js";
|
|
40
|
+
import { comparesToNil, expressionGenMethods } from "./expressions.js";
|
|
29
41
|
import {
|
|
30
42
|
HELPER_APPEND,
|
|
31
|
-
|
|
32
|
-
HELPER_BINARY,
|
|
43
|
+
HELPER_BOUNDS,
|
|
33
44
|
HELPER_CDIV,
|
|
34
45
|
HELPER_CMUL,
|
|
35
|
-
HELPER_CRYPTO_RAND,
|
|
36
|
-
HELPER_DURATION,
|
|
37
46
|
HELPER_EQUAL,
|
|
38
47
|
HELPER_ERROR,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
HELPER_ERROR_JOIN,
|
|
42
|
-
HELPER_HEX,
|
|
48
|
+
HELPER_GENERICS,
|
|
49
|
+
HELPER_GEQ,
|
|
43
50
|
HELPER_IFACE_BOX,
|
|
44
51
|
HELPER_IFEQ,
|
|
45
52
|
HELPER_INJECT_STYLES,
|
|
46
|
-
|
|
47
|
-
HELPER_JSON,
|
|
53
|
+
HELPER_JSONRT,
|
|
48
54
|
HELPER_LEN,
|
|
55
|
+
HELPER_MKLEN,
|
|
49
56
|
HELPER_NIL_CALL,
|
|
57
|
+
HELPER_NIL_MAP,
|
|
58
|
+
HELPER_NM,
|
|
50
59
|
HELPER_PANIC,
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
HELPER_PTREQ,
|
|
61
|
+
HELPER_PUSHS,
|
|
62
|
+
HELPER_RUNESTR,
|
|
53
63
|
HELPER_S,
|
|
54
64
|
HELPER_SCLONE,
|
|
55
|
-
|
|
65
|
+
HELPER_SHIFT,
|
|
56
66
|
HELPER_SIMD,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
HELPER_SLICE,
|
|
68
|
+
HELPER_STRUCT_KEY,
|
|
69
|
+
HELPER_SV,
|
|
70
|
+
HELPER_TO64,
|
|
71
|
+
HELPER_U8DEC,
|
|
72
|
+
HELPER_U8ENC,
|
|
73
|
+
HELPER_VA,
|
|
74
|
+
HELPER_VCLONE,
|
|
65
75
|
} from "./runtime.js";
|
|
66
76
|
import { buildSourceMap, buildWasmSourceMap } from "./source-map.js";
|
|
67
77
|
import { statementGenMethods } from "./statements.js";
|
|
68
78
|
import { stdlibGenMethods } from "./stdlib/index.js";
|
|
69
79
|
import { templGenMethods } from "./templ.js";
|
|
80
|
+
import { typeInfoGenMethods } from "./type-info.js";
|
|
70
81
|
|
|
71
82
|
export { buildSourceMap, buildWasmSourceMap };
|
|
72
83
|
|
|
@@ -110,45 +121,119 @@ const JS_RESERVED = new Set([
|
|
|
110
121
|
const jsSafeName = (n) =>
|
|
111
122
|
typeof n === "string" && JS_RESERVED.has(n) ? `${n}$` : n;
|
|
112
123
|
|
|
124
|
+
// Go declarations may shadow browser globals (Task 0.9). The JS bundle is one
|
|
125
|
+
// flat module scope, so such a binding gets a `$` suffix: generated code and
|
|
126
|
+
// other packages keep reaching the real global (`Array.from`, `Object.keys`).
|
|
127
|
+
const jsBindingName = (n) =>
|
|
128
|
+
typeof n === "string" && BROWSER_GLOBALS.has(n) ? `${n}$` : jsSafeName(n);
|
|
129
|
+
|
|
113
130
|
// Runtime helpers by registry name, in output order. Mark one as needed with
|
|
114
131
|
// `this.useHelper(name)`; `_prependHelpers` emits every marked entry.
|
|
115
132
|
const HELPER_MAP = [
|
|
116
133
|
["len", HELPER_LEN],
|
|
134
|
+
["u8enc", HELPER_U8ENC],
|
|
135
|
+
["u8dec", HELPER_U8DEC],
|
|
136
|
+
["sv", HELPER_SV],
|
|
137
|
+
["slice", HELPER_SLICE],
|
|
138
|
+
["mklen", HELPER_MKLEN],
|
|
139
|
+
["runestr", HELPER_RUNESTR],
|
|
117
140
|
["append", HELPER_APPEND],
|
|
118
141
|
["sliceGuard", HELPER_S],
|
|
142
|
+
["pushs", HELPER_PUSHS],
|
|
119
143
|
["sclone", HELPER_SCLONE],
|
|
144
|
+
["va", HELPER_VA],
|
|
145
|
+
["vclone", HELPER_VCLONE],
|
|
120
146
|
["ifaceBox", HELPER_IFACE_BOX],
|
|
121
147
|
["equal", HELPER_EQUAL],
|
|
148
|
+
["geq", HELPER_GEQ],
|
|
149
|
+
["ptreq", HELPER_PTREQ],
|
|
122
150
|
["ifeq", HELPER_IFEQ],
|
|
123
151
|
["cmul", HELPER_CMUL],
|
|
124
152
|
["cdiv", HELPER_CDIV],
|
|
125
153
|
["panicRt", HELPER_PANIC],
|
|
154
|
+
["shift", HELPER_SHIFT],
|
|
155
|
+
["to64", HELPER_TO64],
|
|
126
156
|
["nilCall", HELPER_NIL_CALL],
|
|
127
|
-
["
|
|
157
|
+
["bounds", HELPER_BOUNDS],
|
|
158
|
+
["nilMap", HELPER_NIL_MAP],
|
|
159
|
+
["nm", HELPER_NM],
|
|
160
|
+
["structKey", HELPER_STRUCT_KEY],
|
|
161
|
+
["generics", HELPER_GENERICS],
|
|
128
162
|
["error", HELPER_ERROR],
|
|
129
|
-
["
|
|
130
|
-
["errorAs", HELPER_ERROR_AS],
|
|
131
|
-
["errorJoin", HELPER_ERROR_JOIN],
|
|
132
|
-
["strconvError", HELPER_STRCONV_ERROR],
|
|
133
|
-
["pathError", HELPER_PATH_ERROR],
|
|
134
|
-
["ioEOF", HELPER_IO_EOF],
|
|
135
|
-
["strconv", HELPER_STRCONV],
|
|
136
|
-
["pathClean", HELPER_PATH_CLEAN],
|
|
137
|
-
["sortSlice", HELPER_SORT_SLICE],
|
|
138
|
-
["timeFmt", HELPER_TIME_FMT],
|
|
139
|
-
["timeParse", HELPER_TIME_PARSE],
|
|
140
|
-
["duration", HELPER_DURATION],
|
|
141
|
-
["testing", HELPER_TESTING],
|
|
142
|
-
["sync", HELPER_SYNC],
|
|
143
|
-
["cryptoRand", HELPER_CRYPTO_RAND],
|
|
144
|
-
["sha256", HELPER_SHA256],
|
|
145
|
-
["binary", HELPER_BINARY],
|
|
146
|
-
["hex", HELPER_HEX],
|
|
147
|
-
["base64", HELPER_BASE64],
|
|
148
|
-
["json", HELPER_JSON],
|
|
163
|
+
["jsonrt", HELPER_JSONRT],
|
|
149
164
|
["simd", HELPER_SIMD],
|
|
150
165
|
];
|
|
151
166
|
|
|
167
|
+
// JS bodies of `//gofront:intrinsic` functions (names: INTRINSICS in
|
|
168
|
+
// typechecker/types.js). The WASM backend keeps their empty body.
|
|
169
|
+
const JS_INTRINSICS = {
|
|
170
|
+
rand_source: () => "return Math.random();",
|
|
171
|
+
time_now: () =>
|
|
172
|
+
'return BigInt(Math.floor(typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now())) * 1000000n;',
|
|
173
|
+
time_sleep: ([ns]) =>
|
|
174
|
+
`await new Promise((r) => setTimeout(r, Number(${ns}) / 1000000));`,
|
|
175
|
+
time_zone_offset: ([unix]) =>
|
|
176
|
+
`return (-new Date(Number(${unix}) * 1000).getTimezoneOffset() * 60) | 0;`,
|
|
177
|
+
// os: node writes straight to the stream; other hosts print whole lines
|
|
178
|
+
// (console.log adds the newline), buffering the partial line.
|
|
179
|
+
os_write: ([fd, s]) =>
|
|
180
|
+
`if (typeof process !== "undefined" && process?.stdout?.write) { (${fd} === 2 && process.stderr?.write ? process.stderr : process.stdout).write(${s}); return; } let b = (globalThis.__os_buf ?? "") + ${s}; const nl = b.lastIndexOf("\\n"); if (nl >= 0) { (${fd} === 2 && typeof console.error === "function" ? console.error : console.log)(b.slice(0, nl)); b = b.slice(nl + 1); } globalThis.__os_buf = b;`,
|
|
181
|
+
os_exit: ([code]) =>
|
|
182
|
+
`if (typeof process !== "undefined" && process?.exit) process.exit(${code}); throw new Error("os.Exit(" + ${code} + ")");`,
|
|
183
|
+
os_getenv: ([key]) =>
|
|
184
|
+
`return (typeof process !== "undefined" && process?.env ? process.env : (globalThis.__os_env ??= {}))[${key}] ?? "";`,
|
|
185
|
+
os_setenv: ([key, value]) =>
|
|
186
|
+
`(typeof process !== "undefined" && process?.env ? process.env : (globalThis.__os_env ??= {}))[${key}] = ${value};`,
|
|
187
|
+
unicode_is: ([r, class_]) => unicodeIsJs(r, class_),
|
|
188
|
+
unicode_to_upper: ([r]) => unicodeToUpperJs(r),
|
|
189
|
+
unicode_to_lower: ([r]) => unicodeToLowerJs(r),
|
|
190
|
+
crypto_rand_u32: () =>
|
|
191
|
+
'const c = typeof crypto !== "undefined" ? crypto : globalThis.crypto; if (c?.getRandomValues) return c.getRandomValues(new Uint32Array(1))[0]; return Math.floor(Math.random() * 4294967296);',
|
|
192
|
+
// A number per object for the whole run, from 1 (pointers print as 0x<id>).
|
|
193
|
+
testing_nanotime: () => "return BigInt(Math.round(performance.now() * 1e6));",
|
|
194
|
+
testing_match: ([pattern, s]) =>
|
|
195
|
+
`const c = (globalThis.__testRe ??= new Map()); let re = c.get(${pattern}); if (!re) { re = new RegExp(${pattern}); c.set(${pattern}, re); } return re.test(${s});`,
|
|
196
|
+
testing_call: ([f, t]) =>
|
|
197
|
+
`const r = ${f}(${t}); return r && typeof r.then === "function" ? r : null;`,
|
|
198
|
+
fmt_ptr_id: ([v]) =>
|
|
199
|
+
`if (${v} === null || ${v} === undefined || (typeof ${v} !== "object" && typeof ${v} !== "function")) return 0n; const t = (globalThis.__fmtptr ??= { m: new WeakMap(), n: 1 }); let id = t.m.get(${v}); if (!id) { id = t.n++; t.m.set(${v}, id); } return BigInt(id);`,
|
|
200
|
+
math_float64bits: ([f]) =>
|
|
201
|
+
`const $b = new ArrayBuffer(8); new Float64Array($b)[0] = ${f}; return new BigUint64Array($b)[0];`,
|
|
202
|
+
math_float64frombits: ([u]) =>
|
|
203
|
+
`const $b = new ArrayBuffer(8); new BigUint64Array($b)[0] = BigInt(${u}); return new Float64Array($b)[0];`,
|
|
204
|
+
math_float32bits: ([f]) =>
|
|
205
|
+
`const $b = new ArrayBuffer(4); new Float32Array($b)[0] = ${f}; return new Uint32Array($b)[0];`,
|
|
206
|
+
math_float32frombits: ([u]) =>
|
|
207
|
+
`const $b = new ArrayBuffer(4); new Uint32Array($b)[0] = ${u} >>> 0; return new Float32Array($b)[0];`,
|
|
208
|
+
math_mod: ([x, y]) => `return ${x} % ${y};`,
|
|
209
|
+
...Object.fromEntries(
|
|
210
|
+
[
|
|
211
|
+
"sin",
|
|
212
|
+
"cos",
|
|
213
|
+
"tan",
|
|
214
|
+
"asin",
|
|
215
|
+
"acos",
|
|
216
|
+
"atan",
|
|
217
|
+
"sinh",
|
|
218
|
+
"cosh",
|
|
219
|
+
"tanh",
|
|
220
|
+
"exp",
|
|
221
|
+
"expm1",
|
|
222
|
+
"log",
|
|
223
|
+
"log1p",
|
|
224
|
+
"log2",
|
|
225
|
+
"log10",
|
|
226
|
+
"cbrt",
|
|
227
|
+
].map((f) => [`math_${f}`, ([x]) => `return Math.${f}(${x});`]),
|
|
228
|
+
),
|
|
229
|
+
...Object.fromEntries(
|
|
230
|
+
["atan2", "pow", "hypot"].map((f) => [
|
|
231
|
+
`math_${f}`,
|
|
232
|
+
([x, y]) => `return Math.${f}(${x}, ${y});`,
|
|
233
|
+
]),
|
|
234
|
+
),
|
|
235
|
+
};
|
|
236
|
+
|
|
152
237
|
export class CodeGen {
|
|
153
238
|
// jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
|
|
154
239
|
// bundledPackages: Set<string> — GoFront package names bundled inline;
|
|
@@ -162,19 +247,25 @@ export class CodeGen {
|
|
|
162
247
|
this.checker = checker;
|
|
163
248
|
this.target = options.target ?? "js";
|
|
164
249
|
this.strict = Boolean(options.strict || this.target === "both");
|
|
250
|
+
this.natives = options.natives !== false; // replace native calls with host code
|
|
165
251
|
this.out = [];
|
|
166
252
|
this.indent = 0;
|
|
167
253
|
this.structNames = new Set();
|
|
168
254
|
this.structFields = new Map();
|
|
169
255
|
this.namedWrapperNames = new Set();
|
|
170
256
|
this.collectedCss = [];
|
|
257
|
+
this.warnings = []; // non-fatal diagnostics; the compiler prints them
|
|
258
|
+
this._precisionWarned = new Set(); // nodes already given a precision warning
|
|
259
|
+
this._srcFileNames = []; // source file name per _srcFileIdx
|
|
171
260
|
this.jsImports = jsImports;
|
|
172
261
|
this.bundledPackages = bundledPackages;
|
|
173
262
|
this.namedReturnVars = null; // names of current function's named return vars
|
|
174
263
|
this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
|
|
175
264
|
this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
|
|
176
265
|
this._boxedVars = new Set(); // address-taken scalar variables that need boxing
|
|
177
|
-
this.
|
|
266
|
+
this._hoistedLocals = new Set(); // function-local classes the json codecs name
|
|
267
|
+
this._hasJsonCodecs = false;
|
|
268
|
+
this._jsonCodecFileIdx = -1;
|
|
178
269
|
// Runtime helper usage tracking — only emit helpers that are actually used
|
|
179
270
|
this._helpers = new Set();
|
|
180
271
|
// Per-function context for Go value semantics (see _withFnCtx)
|
|
@@ -221,6 +312,9 @@ export class CodeGen {
|
|
|
221
312
|
}
|
|
222
313
|
|
|
223
314
|
_emitTypeDecls(program, methods) {
|
|
315
|
+
this._typeDeclsByName = new Map();
|
|
316
|
+
for (const d of program.decls)
|
|
317
|
+
if (d.kind === "TypeDecl") this._typeDeclsByName.set(d.name, d);
|
|
224
318
|
for (const d of program.decls) {
|
|
225
319
|
if (d.kind === "TypeDecl") {
|
|
226
320
|
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
@@ -231,20 +325,28 @@ export class CodeGen {
|
|
|
231
325
|
}
|
|
232
326
|
|
|
233
327
|
_emitVarConstDecls(program) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
328
|
+
const { consts, vars } = orderPackageInits([program]);
|
|
329
|
+
const emit = (decls, gen, ordered) => {
|
|
330
|
+
const natural = decls.flatMap((d) => d.decls ?? []);
|
|
331
|
+
const inOrder = ordered.every((e, i) => e.spec === natural[i]);
|
|
332
|
+
if (inOrder) {
|
|
333
|
+
for (const d of decls) {
|
|
334
|
+
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
335
|
+
gen.call(this, d);
|
|
336
|
+
this.blank();
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
239
339
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
this.
|
|
244
|
-
this.genVarDecl(d);
|
|
340
|
+
// Initialisers depend on declarations that come later (Go allows it).
|
|
341
|
+
for (const e of ordered) {
|
|
342
|
+
this._currentSrcFileIdx = e.decl._srcFileIdx ?? 0;
|
|
343
|
+
gen.call(this, { ...e.decl, decls: [e.spec] });
|
|
245
344
|
this.blank();
|
|
246
345
|
}
|
|
247
|
-
}
|
|
346
|
+
};
|
|
347
|
+
const declsOf = (kind) => program.decls.filter((d) => d.kind === kind);
|
|
348
|
+
emit(declsOf("ConstDecl"), this.genConstDecl, consts);
|
|
349
|
+
emit(declsOf("VarDecl"), this.genVarDecl, vars);
|
|
248
350
|
}
|
|
249
351
|
|
|
250
352
|
_callInitAndMain(initNames, program, isTest = false) {
|
|
@@ -262,25 +364,43 @@ export class CodeGen {
|
|
|
262
364
|
|
|
263
365
|
generate(program, options = {}) {
|
|
264
366
|
const isTest = options.isTest ?? false;
|
|
367
|
+
this._precisionWarned = new Set();
|
|
265
368
|
// Defer lowering (argument/receiver hoisting, recover-target marking) is
|
|
266
369
|
// idempotent and owned by the backends: the wasm path runs it via
|
|
267
370
|
// `lower()`, the JS path here.
|
|
268
371
|
normalizeDefers(program);
|
|
372
|
+
// Strict mode: index checks the loop shape proves unnecessary are elided.
|
|
373
|
+
if (this.strict) {
|
|
374
|
+
markInBoundsIndexes(program);
|
|
375
|
+
markRedundantNilChecks(program, (d) => comparesToNil(d.body, d.recvName));
|
|
376
|
+
}
|
|
377
|
+
if (program._filename) this._srcFileNames = [program._filename];
|
|
269
378
|
// Ensure _pkgName is set on all decls (generateAll sets it for
|
|
270
379
|
// multi-file; for single-file compiles we derive it from the AST).
|
|
271
380
|
const pkgName = program.pkg?.name;
|
|
272
381
|
if (pkgName) {
|
|
382
|
+
this.pkgName = pkgName;
|
|
273
383
|
for (const d of program.decls) {
|
|
274
384
|
if (!d._pkgName) d._pkgName = pkgName;
|
|
275
385
|
}
|
|
276
386
|
}
|
|
277
387
|
this._renameReservedIdents(program.decls);
|
|
388
|
+
this._elemAddrTaken =
|
|
389
|
+
takesElementAddress(program.decls) ||
|
|
390
|
+
this.checker?.depElemAddrTaken === true;
|
|
391
|
+
this._boxedGlobals = this._collectBoxedGlobals(program);
|
|
392
|
+
this._boxedVars = new Set(this._boxedGlobals);
|
|
393
|
+
this._collectResliceNames(program);
|
|
278
394
|
const methods = this._collectDecls(program);
|
|
279
395
|
this._emitJsImports();
|
|
280
396
|
this._emitTypeDecls(program, methods);
|
|
397
|
+
// Package variable initialisers may call functions that declare local
|
|
398
|
+
// structs, so the hoisted classes are declared before them.
|
|
399
|
+
const hoistedAt = this.out.length;
|
|
400
|
+
if (this._hasJsonCodecs) this.line("");
|
|
281
401
|
this._emitVarConstDecls(program);
|
|
282
402
|
const initNames = this._emitFuncDecls(program);
|
|
283
|
-
this.
|
|
403
|
+
this._emitHoistedLocals(hoistedAt);
|
|
284
404
|
this._callInitAndMain(initNames, program, isTest);
|
|
285
405
|
const prependedLines = this._prependHelpers(isTest);
|
|
286
406
|
return this._finalizeOutput(prependedLines);
|
|
@@ -331,12 +451,12 @@ export class CodeGen {
|
|
|
331
451
|
}
|
|
332
452
|
|
|
333
453
|
_renameParams(params) {
|
|
334
|
-
for (const p of params ?? []) p.name =
|
|
454
|
+
for (const p of params ?? []) p.name = jsBindingName(p.name);
|
|
335
455
|
}
|
|
336
456
|
|
|
337
457
|
_renameNamedReturns(node) {
|
|
338
458
|
for (const r of node.returnType?._namedReturns ?? [])
|
|
339
|
-
r.name =
|
|
459
|
+
r.name = jsBindingName(r.name);
|
|
340
460
|
}
|
|
341
461
|
|
|
342
462
|
_renameSignature(node) {
|
|
@@ -345,27 +465,37 @@ export class CodeGen {
|
|
|
345
465
|
}
|
|
346
466
|
|
|
347
467
|
_renameIdentNode(node) {
|
|
348
|
-
if (
|
|
349
|
-
|
|
468
|
+
if (node._isStructKey || node._type?.kind === "builtin") return;
|
|
469
|
+
node.name = node._shadowsGlobal
|
|
470
|
+
? jsBindingName(node.name)
|
|
471
|
+
: jsSafeName(node.name);
|
|
350
472
|
}
|
|
351
473
|
|
|
352
474
|
_renameFuncDeclNode(node) {
|
|
353
|
-
node.name =
|
|
475
|
+
node.name = jsBindingName(node.name);
|
|
354
476
|
this._renameSignature(node);
|
|
355
477
|
}
|
|
356
478
|
|
|
357
479
|
_renameMethodDeclNode(node) {
|
|
358
|
-
node.recvName =
|
|
480
|
+
node.recvName = jsBindingName(node.recvName);
|
|
359
481
|
this._renameSignature(node);
|
|
360
482
|
}
|
|
361
483
|
|
|
362
484
|
_renameDeclSpecs(node) {
|
|
363
485
|
for (const spec of node.decls ?? [])
|
|
364
|
-
spec.names = spec.names.map(
|
|
486
|
+
spec.names = spec.names.map(jsBindingName);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// `document := 1` declares a binding that shadows the browser global. A
|
|
490
|
+
// `:=` redeclaration (`x, document := 2, 3`) always targets a local of the
|
|
491
|
+
// same scope, which carries the same suffix.
|
|
492
|
+
_renameDefineNode(node) {
|
|
493
|
+
for (const l of node.lhs ?? [])
|
|
494
|
+
if (l.kind === "Ident") l.name = jsBindingName(l.name);
|
|
365
495
|
}
|
|
366
496
|
|
|
367
497
|
_renameTypeSwitchNode(node) {
|
|
368
|
-
node.assign =
|
|
498
|
+
node.assign = jsBindingName(node.assign);
|
|
369
499
|
}
|
|
370
500
|
|
|
371
501
|
_markStructKeys(node) {
|
|
@@ -384,7 +514,16 @@ export class CodeGen {
|
|
|
384
514
|
MethodDecl: "_renameMethodDeclNode",
|
|
385
515
|
FuncLit: "_renameSignature",
|
|
386
516
|
TemplDecl: "_renameSignature",
|
|
517
|
+
TemplExpr: "_renameTemplExprNode",
|
|
518
|
+
TemplChildren: "_renameTemplExprNode",
|
|
519
|
+
TemplComponent: "_renameTemplExprNode",
|
|
520
|
+
TemplIf: "_renameTemplExprNode",
|
|
521
|
+
TemplSwitch: "_renameTemplExprNode",
|
|
522
|
+
TemplCase: "_renameTemplExprNode",
|
|
523
|
+
TemplFor: "_renameTemplForNode",
|
|
524
|
+
TemplElement: "_renameTemplElementNode",
|
|
387
525
|
CssDecl: "_renameSignature",
|
|
526
|
+
DefineStmt: "_renameDefineNode",
|
|
388
527
|
VarDecl: "_renameDeclSpecs",
|
|
389
528
|
ConstDecl: "_renameDeclSpecs",
|
|
390
529
|
TypeSwitchStmt: "_renameTypeSwitchNode",
|
|
@@ -410,6 +549,18 @@ export class CodeGen {
|
|
|
410
549
|
// Collects struct names, method map, and named wrapper names from program decls.
|
|
411
550
|
// Returns the method map (typeName → MethodDecl[]).
|
|
412
551
|
_collectStructNames(program) {
|
|
552
|
+
// Go-compiled variadic functions, methods and templ components: a
|
|
553
|
+
// `f(xs...)` call to one passes the slice itself (see `_genCallArgs`,
|
|
554
|
+
// `_emitParamCopies`).
|
|
555
|
+
this.variadicFns = new Set();
|
|
556
|
+
for (const d of program.decls) {
|
|
557
|
+
const last = d.params?.[d.params.length - 1];
|
|
558
|
+
if (!last?.variadic) continue;
|
|
559
|
+
if (d.kind === "FuncDecl" || d.kind === "TemplDecl")
|
|
560
|
+
this.variadicFns.add(d.name);
|
|
561
|
+
else if (d.kind === "MethodDecl")
|
|
562
|
+
this.variadicFns.add(`${d.recvType.name}.${d.name}`);
|
|
563
|
+
}
|
|
413
564
|
for (const d of program.decls) {
|
|
414
565
|
if (d.kind === "TypeDecl" && d.type.kind === "StructType")
|
|
415
566
|
this.structNames.add(d.name);
|
|
@@ -427,11 +578,14 @@ export class CodeGen {
|
|
|
427
578
|
|
|
428
579
|
_collectMethodMap(program) {
|
|
429
580
|
const methods = new Map();
|
|
581
|
+
this.nilSafeMethods = new Set();
|
|
430
582
|
for (const d of program.decls) {
|
|
431
583
|
if (d.kind === "MethodDecl") {
|
|
432
584
|
const name = d.recvType.name;
|
|
433
585
|
if (!methods.has(name)) methods.set(name, []);
|
|
434
586
|
methods.get(name).push(d);
|
|
587
|
+
if (d.recvPointer && comparesToNil(d.body, d.recvName))
|
|
588
|
+
this.nilSafeMethods.add(`${name}.${d.name}`);
|
|
435
589
|
}
|
|
436
590
|
}
|
|
437
591
|
return methods;
|
|
@@ -460,30 +614,17 @@ export class CodeGen {
|
|
|
460
614
|
/** Marks a runtime helper (key of HELPER_MAP) as needed by the output. */
|
|
461
615
|
useHelper(name) {
|
|
462
616
|
this._helpers.add(name);
|
|
463
|
-
if (
|
|
464
|
-
|
|
465
|
-
name === "pathError" ||
|
|
466
|
-
name === "ioEOF" ||
|
|
467
|
-
name === "base64" ||
|
|
468
|
-
name === "hex" ||
|
|
469
|
-
name === "json"
|
|
470
|
-
) {
|
|
471
|
-
this._helpers.add("error");
|
|
472
|
-
}
|
|
617
|
+
if (name === "ifeq" || name === "geq") this._helpers.add("equal");
|
|
618
|
+
if (name === "geq") this._helpers.add("ifeq");
|
|
473
619
|
if (name === "json") {
|
|
474
|
-
this.useHelper("base64");
|
|
475
|
-
}
|
|
476
|
-
if (name === "binary") {
|
|
477
|
-
this.useHelper("ioEOF");
|
|
478
620
|
this._helpers.add("error");
|
|
479
621
|
}
|
|
622
|
+
if (name === "panicRt") this._helpers.add("error");
|
|
623
|
+
if (name === "append") this.useHelper("slice");
|
|
624
|
+
if (name === "slice") this._helpers.add("sv");
|
|
480
625
|
}
|
|
481
626
|
|
|
482
|
-
_prependHelpers(
|
|
483
|
-
if (isTest || this._helpers.has("testing")) {
|
|
484
|
-
this.useHelper("sprintf");
|
|
485
|
-
this.useHelper("testing");
|
|
486
|
-
}
|
|
627
|
+
_prependHelpers(_isTest = false) {
|
|
487
628
|
const helpers = [];
|
|
488
629
|
if (this.collectedCss.length > 0) helpers.push(HELPER_INJECT_STYLES);
|
|
489
630
|
for (const [name, src] of HELPER_MAP) {
|
|
@@ -501,16 +642,36 @@ export class CodeGen {
|
|
|
501
642
|
// Generate a single bundle from multiple programs (same-package multi-file).
|
|
502
643
|
// Annotates each decl with its source file index before merging.
|
|
503
644
|
generateAll(programs, options = {}) {
|
|
645
|
+
this._srcFileNames = programs.map((p) => p._filename ?? null);
|
|
646
|
+
this._jsonCodecFileIdx = programs.findIndex((p) => p._generated);
|
|
647
|
+
this._hasJsonCodecs = this._jsonCodecFileIdx >= 0;
|
|
504
648
|
for (let i = 0; i < programs.length; i++) {
|
|
505
649
|
for (const decl of programs[i].decls) {
|
|
506
650
|
decl._srcFileIdx = i;
|
|
507
651
|
decl._pkgName = programs[i].pkg?.name;
|
|
508
652
|
}
|
|
509
653
|
}
|
|
510
|
-
const merged = {
|
|
654
|
+
const merged = {
|
|
655
|
+
pkg: programs[0]?.pkg,
|
|
656
|
+
decls: programs.flatMap((p) => p.decls),
|
|
657
|
+
};
|
|
511
658
|
return this.generate(merged, options);
|
|
512
659
|
}
|
|
513
660
|
|
|
661
|
+
// Records a non-fatal diagnostic at `node` ("main.go at line 3:5: msg").
|
|
662
|
+
_warn(msg, node) {
|
|
663
|
+
const file = this._srcFileNames[this._currentSrcFileIdx ?? 0];
|
|
664
|
+
// Binary and unary expressions carry no position of their own: use the
|
|
665
|
+
// leftmost operand that does.
|
|
666
|
+
let pos = node;
|
|
667
|
+
while (pos && !(pos.line ?? pos._line))
|
|
668
|
+
pos = pos.left ?? pos.operand ?? pos.expr;
|
|
669
|
+
const line = pos?.line ?? pos?._line;
|
|
670
|
+
const at = line ? `at line ${line}:${pos.col ?? pos._col ?? 1}` : "";
|
|
671
|
+
const loc = [file, at].filter(Boolean).join(" ");
|
|
672
|
+
this.warnings.push(loc ? `${loc}: ${msg}` : msg);
|
|
673
|
+
}
|
|
674
|
+
|
|
514
675
|
getCss() {
|
|
515
676
|
return [...new Set(this.collectedCss)].join("\n\n");
|
|
516
677
|
}
|
|
@@ -519,24 +680,45 @@ export class CodeGen {
|
|
|
519
680
|
return this._srcMappings;
|
|
520
681
|
}
|
|
521
682
|
|
|
522
|
-
// Returns a source map JSON string for the last generate() call.
|
|
523
|
-
// sources: string[] of source filenames (relative to the output file).
|
|
524
|
-
// sourcesContent: string[] of original file contents (embedded for DevTools breakpoints).
|
|
525
|
-
getSourceMap(sources, sourcesContent) {
|
|
526
|
-
const srcArray = Array.isArray(sources) ? sources : [sources];
|
|
527
|
-
return buildSourceMap(srcArray, this._srcMappings, sourcesContent);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
683
|
// ── Type declarations ────────────────────────────────────────
|
|
531
684
|
|
|
685
|
+
// `type S Base` with Base a struct declared in this package: the struct
|
|
686
|
+
// AST S inherits its fields from, or null.
|
|
687
|
+
_definedStructAst(decl) {
|
|
688
|
+
if (decl.isAlias || decl.typeParams) return null;
|
|
689
|
+
if (this.checker?.types.get(decl.name)?.underlying?.kind !== "struct")
|
|
690
|
+
return null;
|
|
691
|
+
let t = decl.type;
|
|
692
|
+
for (let depth = 0; depth < 16 && t; depth++) {
|
|
693
|
+
if (t.kind === "StructType") return t;
|
|
694
|
+
// `type S Base[int]`: the fields come from the checked type.
|
|
695
|
+
if (t.kind === "GenericTypeName" || t.kind === "InstantiationExpr")
|
|
696
|
+
return { kind: "StructType", fields: [] };
|
|
697
|
+
if (t.kind !== "TypeName" && t.kind !== "Ident") return null;
|
|
698
|
+
const next = this._typeDeclsByName?.get(t.name);
|
|
699
|
+
if (!next) return null;
|
|
700
|
+
t = next.type;
|
|
701
|
+
}
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
|
|
532
705
|
genTypeDeclWithMethods(decl, methodDecls) {
|
|
706
|
+
const definedStruct =
|
|
707
|
+
decl.type.kind === "StructType" ? null : this._definedStructAst(decl);
|
|
533
708
|
if (decl.type.kind === "StructType") {
|
|
534
709
|
this.genStruct(decl.name, decl.type, methodDecls);
|
|
710
|
+
} else if (definedStruct) {
|
|
711
|
+
this.genStruct(decl.name, definedStruct, methodDecls);
|
|
535
712
|
} else if (decl.type.kind === "InterfaceType") {
|
|
536
713
|
// Interfaces are compile-time only — no JS output needed.
|
|
537
714
|
this.line(`// interface ${decl.name} (compile-time only)`);
|
|
538
|
-
} else if (
|
|
715
|
+
} else if (
|
|
716
|
+
methodDecls.length > 0 ||
|
|
717
|
+
this._isPlainNamedBox(this.checker?.types.get(decl.name))
|
|
718
|
+
) {
|
|
539
719
|
// Named non-struct type with methods — emit an ES6 wrapper class.
|
|
720
|
+
if (methodDecls.length === 0)
|
|
721
|
+
this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
|
|
540
722
|
this.genNamedTypeClass(decl.name, methodDecls);
|
|
541
723
|
} else {
|
|
542
724
|
this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
|
|
@@ -562,6 +744,7 @@ export class CodeGen {
|
|
|
562
744
|
);
|
|
563
745
|
}
|
|
564
746
|
}
|
|
747
|
+
this._tagMultiUnwrap(methodDecls);
|
|
565
748
|
});
|
|
566
749
|
this.line("}");
|
|
567
750
|
// Box for *T in an interface. It also proxies `.value` to the pointer, so
|
|
@@ -579,22 +762,48 @@ export class CodeGen {
|
|
|
579
762
|
`${m.name}(...__a) { return ${name}.$${m.name}(${recv}, ...__a); }`,
|
|
580
763
|
);
|
|
581
764
|
}
|
|
765
|
+
this._tagMultiUnwrap(methodDecls);
|
|
582
766
|
});
|
|
583
767
|
this.line("}");
|
|
584
768
|
}
|
|
585
769
|
|
|
770
|
+
// `Unwrap() []error` on a box class is told apart from `Unwrap() error`
|
|
771
|
+
// in type tests, as on struct classes.
|
|
772
|
+
_tagMultiUnwrap(methodDecls) {
|
|
773
|
+
const unwrap = methodDecls.find(
|
|
774
|
+
(m) => m.name === "Unwrap" && m.returnType?.kind === "SliceType",
|
|
775
|
+
);
|
|
776
|
+
if (unwrap) this.line("static { this.prototype.Unwrap.__multi = true; }");
|
|
777
|
+
}
|
|
778
|
+
|
|
586
779
|
_namedBoxField(underlying) {
|
|
587
780
|
if (underlying?.kind === "func") return "_fn";
|
|
588
781
|
if (underlying?.kind === "map") return "_map";
|
|
589
782
|
return "_items";
|
|
590
783
|
}
|
|
591
784
|
|
|
785
|
+
// A user-declared named basic type without methods: still boxed in an
|
|
786
|
+
// interface so `any(L("x")).(string)` fails like Go.
|
|
787
|
+
_isPlainNamedBox(type) {
|
|
788
|
+
if (type?.kind !== "named" || type._generic) return false;
|
|
789
|
+
if (type._target === undefined || type._target === "wasm") return false;
|
|
790
|
+
if (type.underlying?.kind !== "basic" || type.methods?.size > 0)
|
|
791
|
+
return false;
|
|
792
|
+
return type.methods instanceof Map;
|
|
793
|
+
}
|
|
794
|
+
|
|
592
795
|
// Box/dispatch info for a named non-struct type with methods, or null.
|
|
593
796
|
_namedBoxInfo(type) {
|
|
594
797
|
if (type?.kind !== "named") return null;
|
|
595
798
|
const u = type.underlying;
|
|
596
799
|
if (!u || u.kind === "struct" || u.kind === "interface") return null;
|
|
597
800
|
const methods = type.methods ?? type._generic?.methods;
|
|
801
|
+
if (this._isPlainNamedBox(type))
|
|
802
|
+
return {
|
|
803
|
+
cls: this._dequalify(type.name),
|
|
804
|
+
field: this._namedBoxField(u),
|
|
805
|
+
methods: new Map(),
|
|
806
|
+
};
|
|
598
807
|
if (!methods || methods.size === 0) return null;
|
|
599
808
|
if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
|
|
600
809
|
return null;
|
|
@@ -626,9 +835,11 @@ export class CodeGen {
|
|
|
626
835
|
const resolved = this.checker.types.get(typeName)?.underlying;
|
|
627
836
|
if (resolved?.kind === "struct") {
|
|
628
837
|
const fields = [];
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
838
|
+
// Cached per type name, so never bound to the current generic body.
|
|
839
|
+
this._withTypeParamZeros(null, () => {
|
|
840
|
+
for (const [name, type] of resolved.fields.entries())
|
|
841
|
+
fields.push({ name, zero: this.zeroValueForType(type) });
|
|
842
|
+
});
|
|
632
843
|
this.structFields.set(typeName, fields);
|
|
633
844
|
return fields;
|
|
634
845
|
}
|
|
@@ -661,17 +872,69 @@ export class CodeGen {
|
|
|
661
872
|
this.line("}");
|
|
662
873
|
}
|
|
663
874
|
|
|
875
|
+
// A generic struct's constructor takes the instance's zero-value factories
|
|
876
|
+
// after the fields (see HELPER_GENERICS). A field whose zero value depends
|
|
877
|
+
// on a type parameter starts at it when its argument is undefined.
|
|
878
|
+
_genTypeParamBindings(tpNames, tz) {
|
|
879
|
+
tpNames.forEach((n, i) => {
|
|
880
|
+
this.line(`const $z_${n} = ${tz}?.[${i}] ?? __zn;`);
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
_genGenericStructConstructor(fields, tpNames) {
|
|
885
|
+
const usesTz = (f) => f.zero.includes("$z_");
|
|
886
|
+
const params = fields.map((f) =>
|
|
887
|
+
usesTz(f) ? `${f.name}$` : `${f.name}$ = ${f.zero}`,
|
|
888
|
+
);
|
|
889
|
+
this.line(`constructor(${[...params, "$tz"].join(", ")}) {`);
|
|
890
|
+
this.indented(() => {
|
|
891
|
+
this.line("this[__tz] = $tz;");
|
|
892
|
+
if (fields.some(usesTz)) this._genTypeParamBindings(tpNames, "$tz");
|
|
893
|
+
for (const f of fields) {
|
|
894
|
+
const v = usesTz(f)
|
|
895
|
+
? `${f.name}$ === undefined ? ${f.zero} : ${f.name}$`
|
|
896
|
+
: `${f.name}$`;
|
|
897
|
+
this.line(`this.${f.name} = ${v};`);
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
this.line("}");
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// JS object keys are strings: a struct used as a map key becomes an
|
|
904
|
+
// injective encoding of its fields (see HELPER_STRUCT_KEY), so equal values
|
|
905
|
+
// share a key and distinct values never collide.
|
|
906
|
+
_genStructKeyString(_name, fields) {
|
|
907
|
+
if (fields.some((f) => f.name === "toString")) return;
|
|
908
|
+
if (fields.length === 0) {
|
|
909
|
+
this.line('toString() { return "{}"; }');
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
this.useHelper("structKey");
|
|
913
|
+
this.line("toString() { return __sk(this); }");
|
|
914
|
+
}
|
|
915
|
+
|
|
664
916
|
_genStructClone(name, structTypeAst, fields) {
|
|
665
917
|
const resolved = this.checker?.types.get(name)?.underlying;
|
|
666
918
|
const astTypes = new Map();
|
|
667
919
|
for (const f of structTypeAst?.fields ?? [])
|
|
668
920
|
for (const n of f.names ?? []) astTypes.set(n, f.type);
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
921
|
+
const tpNames = this._genericTypeParamNames(name);
|
|
922
|
+
const prevZeros = this._typeParamZeros;
|
|
923
|
+
this._typeParamZeros = tpNames
|
|
924
|
+
? new Map(tpNames.map((n, i) => [n, `this[__tz]?.[${i}]`]))
|
|
925
|
+
: null;
|
|
926
|
+
let args;
|
|
927
|
+
try {
|
|
928
|
+
args = fields.map((f) => {
|
|
929
|
+
const t =
|
|
930
|
+
resolved?.fields?.get(f.name) ??
|
|
931
|
+
this._typeFromNode(astTypes.get(f.name));
|
|
932
|
+
return this._cloneJs(t, `this.${f.name}`);
|
|
933
|
+
});
|
|
934
|
+
} finally {
|
|
935
|
+
this._typeParamZeros = prevZeros;
|
|
936
|
+
}
|
|
937
|
+
if (tpNames) args.push("this[__tz]");
|
|
675
938
|
this.blank();
|
|
676
939
|
this.line(`__clone() { return new ${name}(${args.join(", ")}); }`);
|
|
677
940
|
}
|
|
@@ -685,18 +948,38 @@ export class CodeGen {
|
|
|
685
948
|
);
|
|
686
949
|
}
|
|
687
950
|
|
|
951
|
+
// Type parameter names of generic type `name`, or null.
|
|
952
|
+
_genericTypeParamNames(name) {
|
|
953
|
+
const generic = this.checker?.types.get(name)?._generic;
|
|
954
|
+
if (!generic?.typeParams?.length || generic.isAlias) return null;
|
|
955
|
+
return generic.typeParams.map((tp) => tp.name);
|
|
956
|
+
}
|
|
957
|
+
|
|
688
958
|
genStruct(name, structTypeAst, methodDecls) {
|
|
689
|
-
const
|
|
690
|
-
this.
|
|
959
|
+
const tpNames = this._genericTypeParamNames(name);
|
|
960
|
+
if (tpNames) this.useHelper("generics");
|
|
961
|
+
const fields = this._withTypeParamZeros(tpNames, () =>
|
|
962
|
+
this._collectStructFields(name, structTypeAst),
|
|
963
|
+
);
|
|
964
|
+
this.structFields.set(
|
|
965
|
+
name,
|
|
966
|
+
tpNames
|
|
967
|
+
? this._withTypeParamZeros(null, () =>
|
|
968
|
+
this._collectStructFields(name, structTypeAst),
|
|
969
|
+
)
|
|
970
|
+
: fields,
|
|
971
|
+
);
|
|
691
972
|
this.line(`class ${name} {`);
|
|
692
973
|
this.indented(() => {
|
|
693
|
-
this.
|
|
974
|
+
if (tpNames) this._genGenericStructConstructor(fields, tpNames);
|
|
975
|
+
else this._genStructConstructor(fields);
|
|
694
976
|
for (const m of methodDecls) {
|
|
695
977
|
this.blank();
|
|
696
978
|
this.genMethod(m);
|
|
697
979
|
}
|
|
698
980
|
this._genEmbeddedMethodStubs(name, methodDecls);
|
|
699
981
|
this._genStructClone(name, structTypeAst, fields);
|
|
982
|
+
this._genStructKeyString(name, fields);
|
|
700
983
|
});
|
|
701
984
|
this.line("}");
|
|
702
985
|
// Lets generic `p.value` dereferences resolve to the struct itself.
|
|
@@ -705,29 +988,68 @@ export class CodeGen {
|
|
|
705
988
|
`Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
|
|
706
989
|
);
|
|
707
990
|
}
|
|
991
|
+
this._genValueMethodMeta(name, methodDecls);
|
|
708
992
|
}
|
|
709
993
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
994
|
+
// Names of the value-receiver methods: a typed nil pointer in an interface
|
|
995
|
+
// panics when one is called (see __mknil).
|
|
996
|
+
_genValueMethodMeta(name, methodDecls) {
|
|
997
|
+
const vm = methodDecls.filter((m) => !m.recvPointer).map((m) => m.name);
|
|
998
|
+
if (vm.length > 0)
|
|
999
|
+
this.line(
|
|
1000
|
+
`Object.defineProperty(${name}, "__vm", { value: ${JSON.stringify(vm)} });`,
|
|
1001
|
+
);
|
|
1002
|
+
// Pointer-receiver methods are outside the method set of a struct value
|
|
1003
|
+
// held in an interface (see _ifaceMethodCheck).
|
|
1004
|
+
const pm = methodDecls.filter((m) => m.recvPointer).map((m) => m.name);
|
|
1005
|
+
// ...and so are those promoted from an embedded struct value.
|
|
1006
|
+
for (const stub of computeEmbeddedStubs(name, methodDecls, this.checker))
|
|
1007
|
+
if (stub.ptrOnly) pm.push(stub.methodName);
|
|
1008
|
+
if (pm.length > 0)
|
|
1009
|
+
this.line(
|
|
1010
|
+
`Object.defineProperty(${name}, "__pm", { value: ${JSON.stringify(pm)} });`,
|
|
1011
|
+
);
|
|
716
1012
|
}
|
|
717
1013
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
if (
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1014
|
+
// The generated json codecs live at package level and name function-local
|
|
1015
|
+
// struct classes: those are declared once at module level and assigned
|
|
1016
|
+
// where the Go source declares them.
|
|
1017
|
+
_hoistLocalClass(stmt, start) {
|
|
1018
|
+
if (!this._hasJsonCodecs || stmt.type.kind !== "StructType") return;
|
|
1019
|
+
const open = this.out.findIndex(
|
|
1020
|
+
(l, i) => i >= start && l.trimStart().startsWith(`class ${stmt.name} `),
|
|
1021
|
+
);
|
|
1022
|
+
if (open < 0) return;
|
|
1023
|
+
const indent = this.out[open].match(/^\s*/)[0];
|
|
1024
|
+
const close = this.out.findIndex((l, i) => i > open && l === `${indent}}`);
|
|
1025
|
+
if (close < 0) return;
|
|
1026
|
+
this.out[open] =
|
|
1027
|
+
`${indent}${stmt.name} = ${stmt.name}.__ph ? ${this.out[open].trimStart()}`;
|
|
1028
|
+
this.out[close] = `${indent}} : ${stmt.name};`;
|
|
1029
|
+
this._hoistedLocals.add(stmt.name);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
_emitHoistedLocals(at) {
|
|
1033
|
+
if (this._hoistedLocals.size > 0 && this._hasJsonCodecs)
|
|
1034
|
+
this.out[at] =
|
|
1035
|
+
`var ${[...this._hoistedLocals].map((n) => `${n} = class { static __ph = true; }`).join(", ")};`;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// A promoted method runs on the embedded value (`this.In`), so it and the
|
|
1039
|
+
// outer struct's promoted-field accesses see the same storage. Embedded
|
|
1040
|
+
// types that are not compiled Go structs keep the borrowed-`this` form.
|
|
1041
|
+
_embedStub(method, embedName, valueEmbed = false, fieldName = null) {
|
|
1042
|
+
// The field is named after the type without its package suffix
|
|
1043
|
+
// (`Mutex$$sync` is the field `Mutex`), or after the alias it embeds.
|
|
1044
|
+
const field = fieldName || embedName.replace(/\$\$.*$/, "");
|
|
1045
|
+
// A named non-struct type is boxed: its methods are static, taking
|
|
1046
|
+
// the value as the receiver.
|
|
1047
|
+
if (valueEmbed)
|
|
1048
|
+
return `${method}(...__a) { return ${embedName}.$${method}(this.${field}, ...__a); }`;
|
|
1049
|
+
const call = this.structNames.has(embedName)
|
|
1050
|
+
? `this.${field}.${method}(...__a)`
|
|
1051
|
+
: `${embedName}.prototype.${method}.call(this, ...__a)`;
|
|
1052
|
+
return `${method}(...__a) { return ${call}; }`;
|
|
731
1053
|
}
|
|
732
1054
|
|
|
733
1055
|
_genEmbeddedMethodStubs(name, methodDecls) {
|
|
@@ -735,7 +1057,12 @@ export class CodeGen {
|
|
|
735
1057
|
for (const stub of stubs) {
|
|
736
1058
|
this.blank();
|
|
737
1059
|
this.line(
|
|
738
|
-
|
|
1060
|
+
this._embedStub(
|
|
1061
|
+
stub.methodName,
|
|
1062
|
+
stub.embedName,
|
|
1063
|
+
stub.valueEmbed,
|
|
1064
|
+
stub.fieldName,
|
|
1065
|
+
),
|
|
739
1066
|
);
|
|
740
1067
|
}
|
|
741
1068
|
}
|
|
@@ -751,12 +1078,35 @@ export class CodeGen {
|
|
|
751
1078
|
decl._line ?? null,
|
|
752
1079
|
);
|
|
753
1080
|
const prevBoxed = this._boxedVars;
|
|
754
|
-
this._boxedVars = new Set();
|
|
1081
|
+
this._boxedVars = new Set(this._boxedGlobals);
|
|
755
1082
|
this._scanAddressTaken(decl.body);
|
|
1083
|
+
// Methods of a generic struct read its type arguments from the
|
|
1084
|
+
// instance; named non-struct types have no instance to carry them.
|
|
1085
|
+
const tpNames = staticNamed
|
|
1086
|
+
? null
|
|
1087
|
+
: this._genericTypeParamNames(decl.recvType?.name);
|
|
1088
|
+
this._withTypeParamZeros(tpNames, () =>
|
|
1089
|
+
this._genMethodBody(decl, staticNamed, recvParam, tpNames),
|
|
1090
|
+
);
|
|
1091
|
+
this._boxedVars = prevBoxed;
|
|
1092
|
+
this.line("}");
|
|
1093
|
+
// `Unwrap() []error` is told apart from `Unwrap() error` in type tests.
|
|
1094
|
+
if (
|
|
1095
|
+
!staticNamed &&
|
|
1096
|
+
decl.name === "Unwrap" &&
|
|
1097
|
+
decl.returnType?.kind === "SliceType"
|
|
1098
|
+
)
|
|
1099
|
+
this.line("static { this.prototype.Unwrap.__multi = true; }");
|
|
1100
|
+
this._currentSrcFileIdx = prevFileIdx;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
_genMethodBody(decl, staticNamed, recvParam, tpNames) {
|
|
756
1104
|
this._withFnCtx(decl.body, () =>
|
|
757
1105
|
this.indented(() => {
|
|
1106
|
+
if (tpNames) this._genTypeParamBindings(tpNames, "this?.[__tz]");
|
|
1107
|
+
if (decl.recvName) this._fnCtx.params.add(decl.recvName);
|
|
758
1108
|
if (!staticNamed && decl.recvName && decl.recvName !== "_") {
|
|
759
|
-
this.line(`
|
|
1109
|
+
this.line(`let ${decl.recvName} = ${this._receiverValue(decl)};`);
|
|
760
1110
|
}
|
|
761
1111
|
if (staticNamed && this._boxedVars.has(recvParam)) {
|
|
762
1112
|
this.line(`${recvParam} = { value: ${recvParam} };`);
|
|
@@ -765,14 +1115,18 @@ export class CodeGen {
|
|
|
765
1115
|
this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
|
|
766
1116
|
}),
|
|
767
1117
|
);
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// JS parameter names; a variadic Go parameter is a rest parameter.
|
|
1121
|
+
_paramNames(params) {
|
|
1122
|
+
return params.map((p, i) =>
|
|
1123
|
+
p.variadic && i === params.length - 1 ? `...${p.name}` : p.name,
|
|
1124
|
+
);
|
|
771
1125
|
}
|
|
772
1126
|
|
|
773
1127
|
// Static methods of named non-struct types take the receiver as first parameter.
|
|
774
1128
|
_methodSignature(decl, staticNamed, recvParam) {
|
|
775
|
-
const params = decl.params
|
|
1129
|
+
const params = this._paramNames(decl.params);
|
|
776
1130
|
const asyncPrefix = decl.async ? "async " : "";
|
|
777
1131
|
if (!staticNamed)
|
|
778
1132
|
return `${asyncPrefix}${decl.name}(${params.join(", ")}) {`;
|
|
@@ -803,17 +1157,33 @@ export class CodeGen {
|
|
|
803
1157
|
// ── Function declarations ────────────────────────────────────
|
|
804
1158
|
|
|
805
1159
|
genFuncDecl(decl, nameOverride) {
|
|
1160
|
+
const tpNames = decl.typeParams?.length
|
|
1161
|
+
? decl.typeParams.map((tp) => tp.name)
|
|
1162
|
+
: null;
|
|
1163
|
+
this._withTypeParamZeros(tpNames, () =>
|
|
1164
|
+
this._genFuncDecl(decl, nameOverride, tpNames),
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// A generic function takes one zero-value factory per type parameter
|
|
1169
|
+
// before its parameters (see HELPER_GENERICS).
|
|
1170
|
+
_genFuncDecl(decl, nameOverride, tpNames) {
|
|
806
1171
|
const name = nameOverride ?? decl.name;
|
|
807
|
-
const params =
|
|
808
|
-
.map((
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
.join(", ");
|
|
1172
|
+
const params = [
|
|
1173
|
+
...(tpNames ?? []).map((n) => `$z_${n}`),
|
|
1174
|
+
...this._paramNames(decl.params),
|
|
1175
|
+
].join(", ");
|
|
812
1176
|
const asyncPrefix = decl.async ? "async " : "";
|
|
813
1177
|
const srcLine = decl._line ?? null;
|
|
814
1178
|
this.line(`${asyncPrefix}function ${name}(${params}) {`, srcLine);
|
|
1179
|
+
if (decl.intrinsic) {
|
|
1180
|
+
const args = decl.params.map((p) => p.name);
|
|
1181
|
+
this.indented(() => this.line(JS_INTRINSICS[decl.intrinsic](args)));
|
|
1182
|
+
this.line("}");
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
815
1185
|
const prevBoxed = this._boxedVars;
|
|
816
|
-
this._boxedVars = new Set();
|
|
1186
|
+
this._boxedVars = new Set(this._boxedGlobals);
|
|
817
1187
|
this._scanAddressTaken(decl.body);
|
|
818
1188
|
this._withFnCtx(decl.body, () =>
|
|
819
1189
|
this.indented(() => {
|
|
@@ -836,6 +1206,7 @@ export class CodeGen {
|
|
|
836
1206
|
const { name, type } = named[i];
|
|
837
1207
|
const varName = !name || name === "_" ? `__ret_blank$${i}` : name;
|
|
838
1208
|
this._markOwnership(varName, true);
|
|
1209
|
+
this._fnCtx?.params.add(varName);
|
|
839
1210
|
this.line(`let ${varName} = ${this.zeroValueForTypeNode(type)};`);
|
|
840
1211
|
}
|
|
841
1212
|
this.namedReturnVars = named.map((r, i) =>
|
|
@@ -910,11 +1281,25 @@ export class CodeGen {
|
|
|
910
1281
|
this._recoverOkInScope = prevRecoverOk;
|
|
911
1282
|
}
|
|
912
1283
|
|
|
1284
|
+
// Package-level variables whose address is taken anywhere (`&g`, `g.Inc()`
|
|
1285
|
+
// on a pointer-receiver method) are boxed like address-taken locals.
|
|
1286
|
+
_collectBoxedGlobals(program) {
|
|
1287
|
+
const names = new Set();
|
|
1288
|
+
for (const d of program.decls)
|
|
1289
|
+
if (d.kind === "VarDecl")
|
|
1290
|
+
for (const spec of d.decls) for (const n of spec.names) names.add(n);
|
|
1291
|
+
const boxed = new Set();
|
|
1292
|
+
if (names.size === 0) return boxed;
|
|
1293
|
+
for (const n of this._scanAddressTaken(program.decls, new Set()))
|
|
1294
|
+
if (names.has(n)) boxed.add(n);
|
|
1295
|
+
return boxed;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
913
1298
|
// Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
|
|
914
1299
|
// A pointer-receiver method call on a named non-struct local (`l.Set(5)`)
|
|
915
1300
|
// takes the variable's address too, so the variable must be boxed.
|
|
916
|
-
_scanAddressTaken(node) {
|
|
917
|
-
scanAddressTaken(node,
|
|
1301
|
+
_scanAddressTaken(node, into = this._boxedVars) {
|
|
1302
|
+
scanAddressTaken(node, into);
|
|
918
1303
|
const visit = (n) => {
|
|
919
1304
|
if (!n || typeof n !== "object") return;
|
|
920
1305
|
if (Array.isArray(n)) {
|
|
@@ -927,7 +1312,7 @@ export class CodeGen {
|
|
|
927
1312
|
recv?.kind === "Ident" &&
|
|
928
1313
|
this._namedPtrMethodOn(recv._type, n.func.field)
|
|
929
1314
|
)
|
|
930
|
-
|
|
1315
|
+
into.add(recv.name);
|
|
931
1316
|
}
|
|
932
1317
|
for (const key of Object.keys(n)) {
|
|
933
1318
|
if (key.startsWith("_") || key === "kind") continue;
|
|
@@ -935,6 +1320,7 @@ export class CodeGen {
|
|
|
935
1320
|
}
|
|
936
1321
|
};
|
|
937
1322
|
visit(node);
|
|
1323
|
+
return into;
|
|
938
1324
|
}
|
|
939
1325
|
|
|
940
1326
|
_namedPtrMethodOn(type, method) {
|
|
@@ -942,48 +1328,72 @@ export class CodeGen {
|
|
|
942
1328
|
return Boolean(info?.methods.get(method)?._ptrRecv);
|
|
943
1329
|
}
|
|
944
1330
|
|
|
945
|
-
_isReferenceType(t) {
|
|
946
|
-
return isReferenceType(t);
|
|
947
|
-
}
|
|
948
|
-
|
|
949
1331
|
// ── Variable / const declarations ────────────────────────────
|
|
950
1332
|
|
|
951
1333
|
genVarDecl(decl) {
|
|
952
1334
|
for (const spec of decl.decls) {
|
|
953
|
-
if (spec.
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1335
|
+
if (spec._commaOk) this._genDefineStmt(this._varSpecAsDefine(spec));
|
|
1336
|
+
else if (spec.value) this._genVarSpecValues(spec);
|
|
1337
|
+
else this._genVarSpecZero(spec);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
_genVarSpecValues(spec) {
|
|
1342
|
+
const paired = spec.value.length === spec.names.length;
|
|
1343
|
+
const vals = spec.value.map((v, i) => {
|
|
1344
|
+
const js = paired
|
|
1345
|
+
? this._genDeclValue(spec.names[i], v)
|
|
1346
|
+
: this.genExpr(v);
|
|
1347
|
+
// Wrap numeric values assigned to complex-typed vars
|
|
1348
|
+
if (spec.type?.name === "complex128" || spec.type?.name === "complex64") {
|
|
1349
|
+
if (
|
|
1350
|
+
!isComplex(v._type) &&
|
|
1351
|
+
!v._asComplex &&
|
|
1352
|
+
(isNumeric(v._type) || v._type?.kind === "untyped")
|
|
1353
|
+
) {
|
|
1354
|
+
return `{ re: ${js}, im: 0 }`;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (spec.type?.name === "int64" || spec.type?.name === "uint64") {
|
|
1358
|
+
if (
|
|
1359
|
+
v._type?.kind === "untyped" &&
|
|
1360
|
+
!js.endsWith("n") &&
|
|
1361
|
+
/^-?\d+$/.test(js)
|
|
1362
|
+
) {
|
|
1363
|
+
return `${js}n`;
|
|
980
1364
|
}
|
|
981
1365
|
}
|
|
1366
|
+
return js;
|
|
1367
|
+
});
|
|
1368
|
+
this._emitVarBindings(spec.names, vals, paired);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
_genVarSpecZero(spec) {
|
|
1372
|
+
const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
|
|
1373
|
+
for (const name of spec.names) {
|
|
1374
|
+
if (name === "_") continue;
|
|
1375
|
+
this._markOwnership(name, true);
|
|
1376
|
+
const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
|
|
1377
|
+
this.line(`let ${name} = ${val};`);
|
|
982
1378
|
}
|
|
983
1379
|
}
|
|
984
1380
|
|
|
1381
|
+
// `var v, ok = m[k]` generates like `v, ok := m[k]`.
|
|
1382
|
+
_varSpecAsDefine(spec) {
|
|
1383
|
+
return {
|
|
1384
|
+
kind: "DefineStmt",
|
|
1385
|
+
lhs: spec.names.map((name) => ({ kind: "Ident", name })),
|
|
1386
|
+
rhs: spec.value,
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
|
|
985
1390
|
// `var a = v` / `var a, b = v1, v2` / `var a, b = f()`, boxing address-taken names.
|
|
986
1391
|
_emitVarBindings(names, vals, paired) {
|
|
1392
|
+
if (names.length === 1 && names[0] === "_") {
|
|
1393
|
+
// A blank binding only runs its initialiser (`var _ = f()` may repeat).
|
|
1394
|
+
this.line(`void (${vals[0]});`);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
987
1397
|
if (names.length === 1) {
|
|
988
1398
|
const val = this._boxedVars.has(names[0])
|
|
989
1399
|
? `{ value: ${vals[0]} }`
|
|
@@ -992,7 +1402,8 @@ export class CodeGen {
|
|
|
992
1402
|
return;
|
|
993
1403
|
}
|
|
994
1404
|
const rhs = paired ? `[${vals.join(", ")}]` : vals[0];
|
|
995
|
-
|
|
1405
|
+
const targets = names.map((n) => (n === "_" ? "" : n));
|
|
1406
|
+
this.line(`let [${targets.join(", ")}] = ${rhs};`);
|
|
996
1407
|
this._boxDeclaredNames(names);
|
|
997
1408
|
}
|
|
998
1409
|
|
|
@@ -1012,5 +1423,6 @@ export class CodeGen {
|
|
|
1012
1423
|
|
|
1013
1424
|
Object.assign(CodeGen.prototype, statementGenMethods);
|
|
1014
1425
|
Object.assign(CodeGen.prototype, expressionGenMethods);
|
|
1426
|
+
Object.assign(CodeGen.prototype, typeInfoGenMethods);
|
|
1015
1427
|
Object.assign(CodeGen.prototype, stdlibGenMethods);
|
|
1016
1428
|
Object.assign(CodeGen.prototype, templGenMethods);
|