gofront 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -49
- package/package.json +12 -8
- package/src/backend/js/expressions.js +1627 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +508 -102
- package/src/backend/js/runtime.js +1997 -106
- package/src/backend/js/statements.js +931 -87
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +7 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +19 -2
- package/src/backend/js/stdlib/json.js +1167 -0
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +34 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +446 -72
- package/src/backend/wasm/emit-builtins.js +320 -13
- package/src/backend/wasm/emit-exprs.js +1404 -199
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +518 -74
- package/src/backend/wasm/encode.js +128 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +3 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +499 -0
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +221 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +84 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +89 -15
- package/src/compiler.js +107 -269
- package/src/dts-parser.js +71 -14
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -6
- package/src/lower/captures.js +176 -32
- package/src/lower/embedding.js +2 -1
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/parser/struct-tag.js +144 -0
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +130 -13
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +215 -11
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +960 -99
- package/src/typechecker/index.js +491 -72
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +596 -52
- package/src/typechecker/statements.js +157 -17
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +359 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
- package/src/typechecker/stdlib/simd.js +123 -0
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +165 -26
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
|
@@ -4,16 +4,82 @@ import {
|
|
|
4
4
|
nodeMutatesVar,
|
|
5
5
|
nodeWritesVar,
|
|
6
6
|
rootIdentName,
|
|
7
|
+
someChild,
|
|
7
8
|
} from "../../lower/index.js";
|
|
9
|
+
import { constIntValue } from "../../typechecker/const-fold.js";
|
|
8
10
|
import {
|
|
11
|
+
BROWSER_GLOBALS,
|
|
12
|
+
compositeLitIndices,
|
|
9
13
|
ERROR,
|
|
14
|
+
isAny,
|
|
10
15
|
isComplex,
|
|
11
16
|
isTypedArraySlice,
|
|
12
17
|
typedArrayConstructorForElem,
|
|
18
|
+
typeStr,
|
|
13
19
|
} from "../../typechecker/types.js";
|
|
14
20
|
|
|
21
|
+
// True when `node` compares the identifier `name` with nil (`l == nil`): a
|
|
22
|
+
// pointer-receiver method that does so is meant to run on a nil receiver.
|
|
23
|
+
export function comparesToNil(node, name) {
|
|
24
|
+
if (Array.isArray(node)) return node.some((n) => comparesToNil(n, name));
|
|
25
|
+
if (!node || typeof node !== "object") return false;
|
|
26
|
+
if (node.kind === "BinaryExpr" && (node.op === "==" || node.op === "!=")) {
|
|
27
|
+
const isRecv = (e) => e?.kind === "Ident" && e.name === name;
|
|
28
|
+
const isNil = (e) =>
|
|
29
|
+
(e?.kind === "Ident" && e.name === "nil") ||
|
|
30
|
+
(e?.kind === "BasicLit" && e.litKind === "NIL");
|
|
31
|
+
if (
|
|
32
|
+
(isRecv(node.left) && isNil(node.right)) ||
|
|
33
|
+
(isRecv(node.right) && isNil(node.left))
|
|
34
|
+
)
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return someChild(node, (c) => comparesToNil(c, name));
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
16
41
|
|
|
42
|
+
// True when type `t` mentions a type parameter anywhere.
|
|
43
|
+
export function mentionsTypeParam(t, depth = 0) {
|
|
44
|
+
if (!t || depth > 16) return false;
|
|
45
|
+
switch (t.kind) {
|
|
46
|
+
case "typeParam":
|
|
47
|
+
return true;
|
|
48
|
+
case "slice":
|
|
49
|
+
case "array":
|
|
50
|
+
return mentionsTypeParam(t.elem, depth + 1);
|
|
51
|
+
case "pointer":
|
|
52
|
+
return mentionsTypeParam(t.base, depth + 1);
|
|
53
|
+
case "map":
|
|
54
|
+
return (
|
|
55
|
+
mentionsTypeParam(t.key, depth + 1) ||
|
|
56
|
+
mentionsTypeParam(t.value, depth + 1)
|
|
57
|
+
);
|
|
58
|
+
case "func":
|
|
59
|
+
return [...t.params, ...t.returns].some((x) =>
|
|
60
|
+
mentionsTypeParam(x, depth + 1),
|
|
61
|
+
);
|
|
62
|
+
case "named":
|
|
63
|
+
return (t.typeArgs ?? []).some((x) => mentionsTypeParam(x, depth + 1));
|
|
64
|
+
default:
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// How an instance of a generic struct is identified at run time: the type
|
|
70
|
+
// arguments' printed form, kept after the zero-value factories in the
|
|
71
|
+
// instance's `__tz` array. Null when an argument is a type parameter (the
|
|
72
|
+
// instantiation is not known inside erased generic code).
|
|
73
|
+
export function instanceId(typeArgs) {
|
|
74
|
+
if (!typeArgs?.length || typeArgs.some((t) => mentionsTypeParam(t)))
|
|
75
|
+
return null;
|
|
76
|
+
return typeArgs.map(typeStr).join(",");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Array literals longer than this that leave most slots zero are emitted as
|
|
80
|
+
// the zero array plus assignments (_genSparseArrayLit).
|
|
81
|
+
const SPARSE_ARRAY_MIN = 32;
|
|
82
|
+
|
|
17
83
|
const STRICT_NUMERIC_TYPES = new Set([
|
|
18
84
|
"float32",
|
|
19
85
|
"int8",
|
|
@@ -26,6 +92,25 @@ const STRICT_NUMERIC_TYPES = new Set([
|
|
|
26
92
|
"uint32",
|
|
27
93
|
]);
|
|
28
94
|
|
|
95
|
+
// Unsigned types narrower than 64 bits: `-x` and `^x` wrap back into range.
|
|
96
|
+
const SMALL_UNSIGNED_WRAP = {
|
|
97
|
+
uint8: "& 0xFF",
|
|
98
|
+
byte: "& 0xFF",
|
|
99
|
+
uint16: "& 0xFFFF",
|
|
100
|
+
uint32: ">>> 0",
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Signed types narrower than 64 bits: `-x` wraps back into range.
|
|
104
|
+
const SIGNED_NEG_WRAP = {
|
|
105
|
+
int8: "<< 24 >> 24",
|
|
106
|
+
int16: "<< 16 >> 16",
|
|
107
|
+
int32: "| 0",
|
|
108
|
+
rune: "| 0",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Integer types whose JS shifts must follow Go's 64-bit semantics.
|
|
112
|
+
const WIDE_INT_NAMES = new Set(["int", "int64", "uint", "uint64", "uintptr"]);
|
|
113
|
+
|
|
29
114
|
const STRICT_OPS = new Set([
|
|
30
115
|
"+",
|
|
31
116
|
"-",
|
|
@@ -40,6 +125,21 @@ const STRICT_OPS = new Set([
|
|
|
40
125
|
">>",
|
|
41
126
|
]);
|
|
42
127
|
|
|
128
|
+
function collectFunctionLabels(node, labels = new Set()) {
|
|
129
|
+
if (!node || typeof node !== "object") return labels;
|
|
130
|
+
if (Array.isArray(node)) {
|
|
131
|
+
for (const item of node) collectFunctionLabels(item, labels);
|
|
132
|
+
return labels;
|
|
133
|
+
}
|
|
134
|
+
if (node.kind === "LabeledStmt" && node.label) labels.add(node.label);
|
|
135
|
+
if (node.kind === "ForStmt" && node.label) labels.add(node.label);
|
|
136
|
+
for (const k of Object.keys(node)) {
|
|
137
|
+
if (k.startsWith("_")) continue;
|
|
138
|
+
collectFunctionLabels(node[k], labels);
|
|
139
|
+
}
|
|
140
|
+
return labels;
|
|
141
|
+
}
|
|
142
|
+
|
|
43
143
|
const NAMED_ZERO_TABLE = {
|
|
44
144
|
"time.Time": [null, "{ _d: new Date(0), _isZero: true }"],
|
|
45
145
|
"strings.Builder": [null, '{ _buf: "" }'],
|
|
@@ -55,6 +155,50 @@ const NAMED_ZERO_TABLE = {
|
|
|
55
155
|
"encoding/binary.BigEndian": ["binary", "__binary_be"],
|
|
56
156
|
"base64.Encoding": ["base64", "__base64_std"],
|
|
57
157
|
"encoding/base64.Encoding": ["base64", "__base64_std"],
|
|
158
|
+
"simd.F32x4": [null, "Object.freeze([0, 0, 0, 0])"],
|
|
159
|
+
F32x4: [null, "Object.freeze([0, 0, 0, 0])"],
|
|
160
|
+
"simd.Mask32x4": [null, "Object.freeze([false, false, false, false])"],
|
|
161
|
+
Mask32x4: [null, "Object.freeze([false, false, false, false])"],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// gofront/simd receiver methods -> runtime helper; the receiver is passed first.
|
|
165
|
+
const SIMD_VECTOR_HELPERS = {
|
|
166
|
+
Add: "__simd_add",
|
|
167
|
+
Sub: "__simd_sub",
|
|
168
|
+
Mul: "__simd_mul",
|
|
169
|
+
Div: "__simd_div",
|
|
170
|
+
Min: "__simd_min",
|
|
171
|
+
Max: "__simd_max",
|
|
172
|
+
MulAdd: "__simd_muladd",
|
|
173
|
+
Neg: "__simd_neg",
|
|
174
|
+
Abs: "__simd_abs",
|
|
175
|
+
Sqrt: "__simd_sqrt",
|
|
176
|
+
Floor: "__simd_floor",
|
|
177
|
+
Ceil: "__simd_ceil",
|
|
178
|
+
Trunc: "__simd_trunc",
|
|
179
|
+
RoundToEven: "__simd_nearest",
|
|
180
|
+
Dot3: "__simd_dot3",
|
|
181
|
+
Dot4: "__simd_dot4",
|
|
182
|
+
Sum: "__simd_sum",
|
|
183
|
+
WithLane: "__simd_with_lane",
|
|
184
|
+
Swizzle: "__simd_swizzle",
|
|
185
|
+
Equal: "__simd_eq",
|
|
186
|
+
NotEqual: "__simd_ne",
|
|
187
|
+
Less: "__simd_lt",
|
|
188
|
+
LessEqual: "__simd_le",
|
|
189
|
+
Greater: "__simd_gt",
|
|
190
|
+
GreaterEqual: "__simd_ge",
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const SIMD_MASK_HELPERS = {
|
|
194
|
+
And: "__simd_mask_and",
|
|
195
|
+
Or: "__simd_mask_or",
|
|
196
|
+
Xor: "__simd_mask_xor",
|
|
197
|
+
AndNot: "__simd_mask_andnot",
|
|
198
|
+
Not: "__simd_mask_not",
|
|
199
|
+
Any: "__simd_mask_any",
|
|
200
|
+
All: "__simd_mask_all",
|
|
201
|
+
Bits: "__simd_mask_bits",
|
|
58
202
|
};
|
|
59
203
|
|
|
60
204
|
// Splits a generated JS lvalue at its outermost trailing access:
|
|
@@ -128,6 +272,9 @@ const NS_CONSTANTS = {
|
|
|
128
272
|
ErrSyntax: "__strconv_err_syntax",
|
|
129
273
|
ErrRange: "__strconv_err_range",
|
|
130
274
|
},
|
|
275
|
+
bits: {
|
|
276
|
+
UintSize: "64",
|
|
277
|
+
},
|
|
131
278
|
path: {
|
|
132
279
|
ErrBadPattern: "__path_err_bad_pattern",
|
|
133
280
|
},
|
|
@@ -161,7 +308,12 @@ const NS_CONSTANTS = {
|
|
|
161
308
|
rand: {
|
|
162
309
|
Reader: "__crypto_rand_reader",
|
|
163
310
|
},
|
|
164
|
-
os: {
|
|
311
|
+
os: {
|
|
312
|
+
Args: "process.argv",
|
|
313
|
+
Stdout: "__os_stdout",
|
|
314
|
+
Stderr: "__os_stderr",
|
|
315
|
+
Stdin: "__os_stdin",
|
|
316
|
+
},
|
|
165
317
|
time: {
|
|
166
318
|
Nanosecond: "1",
|
|
167
319
|
Microsecond: "1000",
|
|
@@ -255,6 +407,14 @@ const INT_TYPE_NAMES = new Set([
|
|
|
255
407
|
"rune",
|
|
256
408
|
]);
|
|
257
409
|
|
|
410
|
+
// Zero values with a shared factory in HELPER_GENERICS.
|
|
411
|
+
const SHARED_ZERO_FACTORIES = {
|
|
412
|
+
null: "__zn",
|
|
413
|
+
0: "__z0",
|
|
414
|
+
'""': "__zs",
|
|
415
|
+
false: "__zf",
|
|
416
|
+
};
|
|
417
|
+
|
|
258
418
|
// Zero-value JS literals for basic Go types.
|
|
259
419
|
const ZERO_FOR_BASIC = {
|
|
260
420
|
int: "0",
|
|
@@ -285,18 +445,38 @@ const TYPEOF_NUMBER_NAMES = new Set([...INT_TYPE_NAMES, "float32", "float64"]);
|
|
|
285
445
|
const BUILTIN_GEN = {
|
|
286
446
|
append: (s, e) => s.genAppend(e),
|
|
287
447
|
len: (s, e) => s._genBuiltinLen(e),
|
|
288
|
-
cap: (s, e) =>
|
|
448
|
+
cap: (s, e) => {
|
|
449
|
+
s.useHelper("slice");
|
|
450
|
+
return `__cap(${s.genExpr(e.args[0])})`;
|
|
451
|
+
},
|
|
289
452
|
make: (s, e) => s.genMake(e),
|
|
290
453
|
delete: (s, e) => {
|
|
291
454
|
const [m, k] = e.args.map((a) => s.genExpr(a));
|
|
292
455
|
return `(delete ${m}[${k}])`;
|
|
293
456
|
},
|
|
294
457
|
copy: (s, e) => {
|
|
295
|
-
const
|
|
296
|
-
|
|
458
|
+
const dst = s.genExpr(e.args[0]);
|
|
459
|
+
const src = s._genSliceSource(e.args[1]);
|
|
460
|
+
// Array elements are values: the copied elements must not alias src.
|
|
461
|
+
const t = e.args[1]._type;
|
|
462
|
+
const elem = (t?.kind === "named" ? t.underlying : t)?.elem;
|
|
463
|
+
const clone =
|
|
464
|
+
s._aggregateBase(elem)?.kind === "array"
|
|
465
|
+
? s._cloneJs(elem, "__e")
|
|
466
|
+
: "__e";
|
|
467
|
+
const items =
|
|
468
|
+
clone === "__e"
|
|
469
|
+
? "(__cs.slice ? __cs.slice(0, n) : __cs.subarray(0, n))"
|
|
470
|
+
: `__cs.slice(0, n).map((__e) => ${clone})`;
|
|
471
|
+
return `((__cd, __cs) => { if (!__cd || !__cs) return 0; const n = Math.min(__cd.length, __cs.length); if (__cd.set) { __cd.set(__cs.length === n ? __cs : (__cs.subarray ? __cs.subarray(0, n) : __cs.slice(0, n))); } else { __cd.splice(0, n, ...${items}); } return n; })(${dst}, ${src})`;
|
|
297
472
|
},
|
|
298
473
|
new: (s, e) => {
|
|
299
474
|
const arg = e.args[0];
|
|
475
|
+
const base = e._type?.kind === "pointer" ? e._type.base : null;
|
|
476
|
+
if (base?.kind === "named" && base.typeArgs?.length) {
|
|
477
|
+
const inst = s._genericStructNew(base.name, base.typeArgs);
|
|
478
|
+
if (inst) return inst;
|
|
479
|
+
}
|
|
300
480
|
const typeName = arg.name ?? s.getTypeName(arg);
|
|
301
481
|
if (typeName && s.structNames.has(typeName)) {
|
|
302
482
|
return `new ${typeName}()`;
|
|
@@ -306,7 +486,12 @@ const BUILTIN_GEN = {
|
|
|
306
486
|
const name = t.name ?? (t.kind === "named" ? t.name : null);
|
|
307
487
|
if (name) return `new ${name}()`;
|
|
308
488
|
}
|
|
309
|
-
|
|
489
|
+
// `new([4]byte)`: the type argument is wrapped in a TypeExpr node.
|
|
490
|
+
const zero = s.zeroValueForExpr(arg.kind === "TypeExpr" ? arg.type : arg);
|
|
491
|
+
// Erased type parameter: a struct instantiation is its own pointer.
|
|
492
|
+
if (base?.kind === "typeParam")
|
|
493
|
+
return `((__v) => (${s._typeParamIsStructJs(base.name)} ? __v : { value: __v }))(${zero})`;
|
|
494
|
+
return `{ value: ${zero} }`;
|
|
310
495
|
},
|
|
311
496
|
// `print` writes without a trailing newline (Go semantics); the wasm glue
|
|
312
497
|
// buffers the same way, so output parity holds across backends.
|
|
@@ -323,8 +508,8 @@ const BUILTIN_GEN = {
|
|
|
323
508
|
s.useHelper("error");
|
|
324
509
|
return `__error(${s.genExpr(e.args[0])})`;
|
|
325
510
|
},
|
|
326
|
-
min: (s, e) =>
|
|
327
|
-
max: (s, e) =>
|
|
511
|
+
min: (s, e) => genMinMax(s, e, "min", "<"),
|
|
512
|
+
max: (s, e) => genMinMax(s, e, "max", ">"),
|
|
328
513
|
clear: (s, e) => s._genBuiltinClear(e),
|
|
329
514
|
complex: (s, e) =>
|
|
330
515
|
`{ re: ${s.genExpr(e.args[0])}, im: ${s.genExpr(e.args[1])} }`,
|
|
@@ -332,6 +517,53 @@ const BUILTIN_GEN = {
|
|
|
332
517
|
imag: (s, e) => `(${s.genExpr(e.args[0])}).im`,
|
|
333
518
|
};
|
|
334
519
|
|
|
520
|
+
// Math.min/max coerce strings to NaN, so string operands fold with `<`/`>`.
|
|
521
|
+
// A type parameter may be a string or a number: pick the form at run time
|
|
522
|
+
// (Math.min/max keeps NaN propagation and -0 for floats).
|
|
523
|
+
function genMinMax(s, e, fn, cmp) {
|
|
524
|
+
const args = e.args.map((a) => s.genExpr(a));
|
|
525
|
+
const fold = (xs) => `${xs}.reduce((a, b) => (b ${cmp} a ? b : a))`;
|
|
526
|
+
if (args.length === 1) return args[0];
|
|
527
|
+
if (e._type?.kind === "typeParam")
|
|
528
|
+
return `((...v) => (typeof v[0] === "string" ? ${fold("v")} : Math.${fn}(...v)))(${args.join(", ")})`;
|
|
529
|
+
if (!s._isStringExprType(e._type)) return `Math.${fn}(${args.join(", ")})`;
|
|
530
|
+
return fold(`[${args.join(", ")}]`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Expression kinds whose folded constant value (`_const`, attached by the
|
|
534
|
+
// checker — see typechecker/const-fold.js) is emitted as a literal. Literals
|
|
535
|
+
// and constant names already emit their value.
|
|
536
|
+
const FOLDED_CONST_KINDS = new Set([
|
|
537
|
+
"UnaryExpr",
|
|
538
|
+
"BinaryExpr",
|
|
539
|
+
"ParenExpr",
|
|
540
|
+
"CallExpr",
|
|
541
|
+
"TypeConversion",
|
|
542
|
+
]);
|
|
543
|
+
|
|
544
|
+
const JS_SAFE_INT = 2n ** 53n;
|
|
545
|
+
|
|
546
|
+
// `-1.0` already emits its exact value; keep it as written.
|
|
547
|
+
const isSignedLit = (e) =>
|
|
548
|
+
e.kind === "UnaryExpr" &&
|
|
549
|
+
(e.op === "-" || e.op === "+") &&
|
|
550
|
+
e.operand?.kind === "BasicLit";
|
|
551
|
+
|
|
552
|
+
// Negative literals are parenthesised so they splice safely into operators.
|
|
553
|
+
const signed = (n, text) => (n < 0 || Object.is(n, -0) ? `(${text})` : text);
|
|
554
|
+
|
|
555
|
+
// JS literal per folded constant kind; null when JS cannot represent it.
|
|
556
|
+
const CONST_LITS = {
|
|
557
|
+
STRING: (v) => JSON.stringify(v),
|
|
558
|
+
BOOL: (v) => (v ? "true" : "false"),
|
|
559
|
+
FLOAT: (v) =>
|
|
560
|
+
Number.isFinite(v) ? signed(v, Object.is(v, -0) ? "-0" : String(v)) : null,
|
|
561
|
+
INT: (v) => {
|
|
562
|
+
const n = Number(v);
|
|
563
|
+
return Number.isFinite(n) ? signed(n, BigInt(n).toString()) : null;
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
|
|
335
567
|
// Method-name dispatch for genExpr — string values add no static call edges.
|
|
336
568
|
const EXPR_GEN_DELEGATE = {
|
|
337
569
|
UnaryExpr: "_genUnaryExpr",
|
|
@@ -347,35 +579,78 @@ const EXPR_GEN_DELEGATE = {
|
|
|
347
579
|
};
|
|
348
580
|
|
|
349
581
|
/** @type {ThisType<CodeGen>} */
|
|
582
|
+
const stdIdent = (name, type) => ({ kind: "Ident", name, _type: type });
|
|
583
|
+
|
|
584
|
+
// True when a func type mentions a type parameter (a generic stdlib function).
|
|
585
|
+
function hasTypeParamType(t) {
|
|
586
|
+
return JSON.stringify([t.params, t.returns], (k, v) =>
|
|
587
|
+
k.startsWith("_") || k === "underlying" ? undefined : v,
|
|
588
|
+
).includes('"typeParam"');
|
|
589
|
+
}
|
|
590
|
+
|
|
350
591
|
export const expressionGenMethods = {
|
|
351
592
|
genExpr(expr) {
|
|
352
|
-
if (expr.
|
|
593
|
+
if (expr._ifaceKeyDyn && !expr._ifaceKeying) {
|
|
594
|
+
// A key typed by a type parameter: encode it for the instantiated type.
|
|
595
|
+
const f = this._typeParamZeroFactory(expr._ifaceKeyParam);
|
|
596
|
+
if (f) {
|
|
597
|
+
expr._ifaceKeying = true;
|
|
598
|
+
try {
|
|
599
|
+
this.useHelper("structKey");
|
|
600
|
+
const box = expr._ifaceKeyDyn === "box" ? ", true" : "";
|
|
601
|
+
return `__ikt(${f}, ${this.genExpr(expr)}${box})`;
|
|
602
|
+
} finally {
|
|
603
|
+
expr._ifaceKeying = false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (expr._ifaceKey && !expr._ifaceKeying) {
|
|
608
|
+
// A string key is its own property key; any other dynamic value is
|
|
609
|
+
// encoded with its type (see HELPER_STRUCT_KEY).
|
|
610
|
+
const plain =
|
|
611
|
+
!this._isNamedBoxKind(expr) && this._isStringExprType(expr._type);
|
|
612
|
+
if (!plain) {
|
|
613
|
+
expr._ifaceKeying = true;
|
|
614
|
+
try {
|
|
615
|
+
this.useHelper("structKey");
|
|
616
|
+
return `__ik(${this.genExpr(expr)})`;
|
|
617
|
+
} finally {
|
|
618
|
+
expr._ifaceKeying = false;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (this._isNamedIfaceBox(expr)) return this._genNamedIfaceBoxExpr(expr);
|
|
623
|
+
if (expr._ifaceSrc && !expr._fmtDescing && !this._isNamedBoxKind(expr)) {
|
|
624
|
+
const desc = this._fmtDesc(expr._ifaceSrc);
|
|
625
|
+
if (desc) {
|
|
626
|
+
expr._fmtDescing = true;
|
|
627
|
+
try {
|
|
628
|
+
return this._fmtDescWrap(this.genExpr(expr), desc);
|
|
629
|
+
} finally {
|
|
630
|
+
expr._fmtDescing = false;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (expr._ifaceBox && !expr._ifaceBoxing && !this._isNamedBoxKind(expr)) {
|
|
353
635
|
expr._ifaceBoxing = true;
|
|
354
636
|
try {
|
|
355
637
|
this.useHelper("ifaceBox");
|
|
356
|
-
|
|
357
|
-
expr
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
: expr._ifaceBox === "ptr_map"
|
|
362
|
-
? "__ifpm"
|
|
363
|
-
: expr._ifaceBox === "map"
|
|
364
|
-
? "__ifm"
|
|
365
|
-
: expr._ifaceBox === "slice"
|
|
366
|
-
? "__ifs"
|
|
367
|
-
: "__ifv";
|
|
368
|
-
const extra = expr._ifacePtrFields?.length
|
|
369
|
-
? `, ${JSON.stringify(expr._ifacePtrFields)}`
|
|
370
|
-
: "";
|
|
371
|
-
return `${fn}(${this.genExpr(expr)}${extra})`;
|
|
638
|
+
return this._ifaceBoxWrap(
|
|
639
|
+
this.genExpr(expr),
|
|
640
|
+
expr._ifaceBox,
|
|
641
|
+
expr._ifacePtrFields,
|
|
642
|
+
);
|
|
372
643
|
} finally {
|
|
373
644
|
expr._ifaceBoxing = false;
|
|
374
645
|
}
|
|
375
646
|
}
|
|
647
|
+
const folded = this._genFoldedConst(expr);
|
|
648
|
+
if (folded !== null) return folded;
|
|
376
649
|
switch (expr.kind) {
|
|
377
650
|
case "BasicLit":
|
|
378
651
|
if (expr.litKind === "STRING") return JSON.stringify(expr.value);
|
|
652
|
+
if (expr._const?.k === "INT")
|
|
653
|
+
this._warnConstPrecision(expr._const, expr);
|
|
379
654
|
return expr.value; // int, float, bool, nil(null)
|
|
380
655
|
case "ImagLit":
|
|
381
656
|
return `{ re: 0, im: ${expr.value} }`;
|
|
@@ -386,6 +661,7 @@ export const expressionGenMethods = {
|
|
|
386
661
|
case "AwaitExpr":
|
|
387
662
|
return `await ${this.genExpr(expr.expr)}`;
|
|
388
663
|
case "InstantiationExpr":
|
|
664
|
+
return this._genInstantiationExpr(expr);
|
|
389
665
|
case "RangeExpr":
|
|
390
666
|
return this.genExpr(expr.expr);
|
|
391
667
|
default: {
|
|
@@ -396,6 +672,80 @@ export const expressionGenMethods = {
|
|
|
396
672
|
}
|
|
397
673
|
},
|
|
398
674
|
|
|
675
|
+
// The literal for a folded constant expression (`1 << 40` →
|
|
676
|
+
// `1099511627776`), or null. Integers outside ±2⁵³ emit the nearest
|
|
677
|
+
// Number and a precision warning.
|
|
678
|
+
_genFoldedConst(expr) {
|
|
679
|
+
const val = expr._const;
|
|
680
|
+
if (!val) return null;
|
|
681
|
+
// Constant names emit themselves, but an integer use of one still warns
|
|
682
|
+
// (an integer constant declared in this package warned at its declaration).
|
|
683
|
+
if (expr.kind === "Ident" || expr.kind === "SelectorExpr") {
|
|
684
|
+
const declared = expr.kind === "Ident" && val.k === "INT";
|
|
685
|
+
if (expr._intConst && !declared) this._warnConstPrecision(val, expr);
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
if (!FOLDED_CONST_KINDS.has(expr.kind) || isSignedLit(expr)) return null;
|
|
689
|
+
if (val.k === "INT" || expr._intConst) this._warnConstPrecision(val, expr);
|
|
690
|
+
return CONST_LITS[val.k]?.(val.v) ?? null;
|
|
691
|
+
},
|
|
692
|
+
|
|
693
|
+
// Warns when an integer constant outside ±2⁵³ has no exact Number (once
|
|
694
|
+
// per node per generate run: some forms generate the same node more than
|
|
695
|
+
// once, and cached ASTs are generated again by later compiles).
|
|
696
|
+
_warnConstPrecision(val, expr) {
|
|
697
|
+
const i = constIntValue(val);
|
|
698
|
+
if (i === null || (i <= JS_SAFE_INT && i >= -JS_SAFE_INT)) return;
|
|
699
|
+
if (BigInt(Number(i)) !== i && !this._precisionWarned.has(expr)) {
|
|
700
|
+
this._precisionWarned.add(expr);
|
|
701
|
+
this._warn(`constant ${i} exceeds JS Number precision`, expr);
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
// `js` boxed as `box` (a checker _ifaceBoxMeta kind) for an interface.
|
|
706
|
+
_ifaceBoxWrap(js, box, ptrFields) {
|
|
707
|
+
this.useHelper("ifaceBox");
|
|
708
|
+
const fn =
|
|
709
|
+
{
|
|
710
|
+
ptr: "__ifp",
|
|
711
|
+
ptr_slice: "__ifps",
|
|
712
|
+
ptr_map: "__ifpm",
|
|
713
|
+
map: "__ifm",
|
|
714
|
+
slice: "__ifs",
|
|
715
|
+
}[box] ?? "__ifv";
|
|
716
|
+
const extra = ptrFields?.length ? `, ${JSON.stringify(ptrFields)}` : "";
|
|
717
|
+
return `${fn}(${js}${extra})`;
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
// `js` tagged with the fmt descriptor source `desc` (see _fmtDesc).
|
|
721
|
+
_fmtDescWrap(js, desc) {
|
|
722
|
+
this.useHelper("ifd");
|
|
723
|
+
return `__ifd(${js}, ${desc})`;
|
|
724
|
+
},
|
|
725
|
+
|
|
726
|
+
_isNamedBoxKind(expr) {
|
|
727
|
+
return expr._ifaceBox === "named" || expr._ifaceBox === "named_ptr";
|
|
728
|
+
},
|
|
729
|
+
|
|
730
|
+
_isNamedIfaceBox(expr) {
|
|
731
|
+
return (
|
|
732
|
+
this._isNamedBoxKind(expr) && !expr._ifaceBoxing && !expr._noNamedBox
|
|
733
|
+
);
|
|
734
|
+
},
|
|
735
|
+
|
|
736
|
+
_genNamedIfaceBoxExpr(expr) {
|
|
737
|
+
expr._ifaceBoxing = true;
|
|
738
|
+
try {
|
|
739
|
+
return this._genNamedIfaceBox(
|
|
740
|
+
this.genExpr(expr),
|
|
741
|
+
expr._ifaceNamedType,
|
|
742
|
+
expr._ifaceBox === "named_ptr",
|
|
743
|
+
);
|
|
744
|
+
} finally {
|
|
745
|
+
expr._ifaceBoxing = false;
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
|
|
399
749
|
_isStructType(t) {
|
|
400
750
|
if (!t) return false;
|
|
401
751
|
if (t.name && this.structNames.has(t.name)) return true;
|
|
@@ -417,6 +767,29 @@ export const expressionGenMethods = {
|
|
|
417
767
|
return this._isStructType(pt.base);
|
|
418
768
|
},
|
|
419
769
|
|
|
770
|
+
_isScalarPointerType(t) {
|
|
771
|
+
if (!t) return false;
|
|
772
|
+
const pt =
|
|
773
|
+
t.kind === "named"
|
|
774
|
+
? (t.underlying ?? this.checker?.types.get(t.name)?.underlying)
|
|
775
|
+
: t;
|
|
776
|
+
if (pt?.kind !== "pointer") return false;
|
|
777
|
+
return !this._isStructType(pt.base);
|
|
778
|
+
},
|
|
779
|
+
|
|
780
|
+
_genPointerEquality(expr) {
|
|
781
|
+
if (expr.op !== "==" && expr.op !== "!=") return null;
|
|
782
|
+
if (
|
|
783
|
+
!this._isScalarPointerType(expr.left._type) &&
|
|
784
|
+
!this._isScalarPointerType(expr.right._type)
|
|
785
|
+
) {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
this.useHelper("ptreq");
|
|
789
|
+
const cmp = `__ptreq(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
|
|
790
|
+
return expr.op === "==" ? cmp : `!${cmp}`;
|
|
791
|
+
},
|
|
792
|
+
|
|
420
793
|
_isStructExpr(expr) {
|
|
421
794
|
if (!expr) return false;
|
|
422
795
|
if (expr.kind === "CompositeLit") {
|
|
@@ -453,7 +826,20 @@ export const expressionGenMethods = {
|
|
|
453
826
|
return null;
|
|
454
827
|
},
|
|
455
828
|
|
|
829
|
+
// A value of type-parameter type is copied at run time when its type
|
|
830
|
+
// argument is a struct or array (decision: runtime check on the zero-value
|
|
831
|
+
// factory, so one erased function body serves every instantiation).
|
|
832
|
+
_typeParamClone(t, js) {
|
|
833
|
+
if (t?.kind !== "typeParam") return null;
|
|
834
|
+
const factory = this._typeParamZeroFactory(t.name);
|
|
835
|
+
if (!factory) return null;
|
|
836
|
+
this.useHelper("vclone");
|
|
837
|
+
return `__vclone(${factory}, ${js})`;
|
|
838
|
+
},
|
|
839
|
+
|
|
456
840
|
_cloneJs(t, js) {
|
|
841
|
+
const tp = this._typeParamClone(t, js);
|
|
842
|
+
if (tp) return tp;
|
|
457
843
|
const base = this._aggregateBase(t);
|
|
458
844
|
if (!base) return js;
|
|
459
845
|
if (base.kind === "struct") {
|
|
@@ -489,7 +875,12 @@ export const expressionGenMethods = {
|
|
|
489
875
|
genValueExpr(expr) {
|
|
490
876
|
const js = this.genExpr(expr);
|
|
491
877
|
if (expr._ifaceBox) return js;
|
|
492
|
-
if (
|
|
878
|
+
if (
|
|
879
|
+
!(
|
|
880
|
+
this._aggregateBase(expr._type) || this._typeParamClone(expr._type, js)
|
|
881
|
+
) ||
|
|
882
|
+
!this._isAddressableExpr(expr)
|
|
883
|
+
)
|
|
493
884
|
return js;
|
|
494
885
|
return this._cloneJs(expr._type, js);
|
|
495
886
|
},
|
|
@@ -514,16 +905,26 @@ export const expressionGenMethods = {
|
|
|
514
905
|
|
|
515
906
|
_withFnCtx(body, fn) {
|
|
516
907
|
const prev = this._fnCtx;
|
|
908
|
+
const prevLabels = this._functionLabels;
|
|
909
|
+
const labels = collectFunctionLabels(
|
|
910
|
+
body,
|
|
911
|
+
new Set(this._functionLabels ?? []),
|
|
912
|
+
);
|
|
913
|
+
this._functionLabels = Array.from(labels);
|
|
517
914
|
this._fnCtx = {
|
|
518
915
|
body,
|
|
519
916
|
mut: new Map(),
|
|
520
917
|
owned: new Set(),
|
|
521
918
|
borrowed: new Set(),
|
|
919
|
+
// Parameters, receivers and named results, including the enclosing
|
|
920
|
+
// functions' (closures): `x = x[lo:hi]` on them must not copy.
|
|
921
|
+
params: new Set(prev?.params),
|
|
522
922
|
};
|
|
523
923
|
try {
|
|
524
924
|
fn();
|
|
525
925
|
} finally {
|
|
526
926
|
this._fnCtx = prev;
|
|
927
|
+
this._functionLabels = prevLabels;
|
|
527
928
|
}
|
|
528
929
|
},
|
|
529
930
|
|
|
@@ -541,9 +942,23 @@ export const expressionGenMethods = {
|
|
|
541
942
|
// Copies aggregate params/receivers that the function body mutates.
|
|
542
943
|
_emitParamCopies(params) {
|
|
543
944
|
for (const p of params) {
|
|
945
|
+
if (p.name) this._fnCtx?.params.add(p.name);
|
|
946
|
+
if (p.variadic && p.name && p.name !== "_") {
|
|
947
|
+
this.useHelper("va");
|
|
948
|
+
// Only a function that tests the parameter against nil needs an
|
|
949
|
+
// empty call to be nil; others keep an empty slice so stdlib
|
|
950
|
+
// helpers and printing see `[]`.
|
|
951
|
+
const nilTested = comparesToNil(this._fnCtx?.body, p.name);
|
|
952
|
+
this.line(`${p.name} = __va(${p.name}${nilTested ? ", true" : ""});`);
|
|
953
|
+
}
|
|
544
954
|
if (p.variadic || !p.name || p.name === "_") continue;
|
|
955
|
+
if (this._boxedVars.has(p.name)) {
|
|
956
|
+
// Address-taken parameter: rebind it to a { value } box.
|
|
957
|
+
this.line(`${p.name} = { value: ${p.name} };`);
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
545
960
|
const t = this._typeFromNode(p.type);
|
|
546
|
-
if (!t
|
|
961
|
+
if (!t) continue;
|
|
547
962
|
if (this._fnMutates(p.name)) {
|
|
548
963
|
this.line(`${p.name} = ${this._cloneJs(t, p.name)};`);
|
|
549
964
|
this._markOwnership(p.name, true);
|
|
@@ -589,12 +1004,26 @@ export const expressionGenMethods = {
|
|
|
589
1004
|
) {
|
|
590
1005
|
return this.genExpr(expr.operand);
|
|
591
1006
|
}
|
|
592
|
-
if (
|
|
593
|
-
expr.operand.
|
|
594
|
-
expr.operand.
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
1007
|
+
if (expr.operand.kind === "IndexExpr") {
|
|
1008
|
+
const obj = this.genExpr(expr.operand.expr);
|
|
1009
|
+
const idx = this.genExpr(expr.operand.index);
|
|
1010
|
+
const check = this._genStrictIndex(
|
|
1011
|
+
{ ...expr.operand, _lvalue: false },
|
|
1012
|
+
"__o",
|
|
1013
|
+
"__i",
|
|
1014
|
+
);
|
|
1015
|
+
// Erased generic element: a struct instantiation is its own
|
|
1016
|
+
// pointer, anything else gets the get/set wrapper.
|
|
1017
|
+
if (expr.operand._type?.kind === "typeParam")
|
|
1018
|
+
return `((__o, __i) => { ${check ? `${check}; ` : ""}if (${this._typeParamIsStructJs(expr.operand._type.name)}) return __o[__i]; return Object.defineProperties({ get value() { return __o[__i]; }, set value(v) { __o[__i] = v; } }, { __target: { value: __o }, __index: { value: __i } }); })(${obj}, ${idx})`;
|
|
1019
|
+
if (check)
|
|
1020
|
+
return `((__o, __i) => (${check}, Object.defineProperties({ get value() { return __o[__i]; }, set value(v) { __o[__i] = v; } }, { __target: { value: __o }, __index: { value: __i } })))(${obj}, ${idx})`;
|
|
1021
|
+
return `((__o, __i) => Object.defineProperties({ get value() { return __o[__i]; }, set value(v) { __o[__i] = v; } }, { __target: { value: __o }, __index: { value: __i } }))(${obj}, ${idx})`;
|
|
1022
|
+
}
|
|
1023
|
+
if (expr.operand.kind === "SelectorExpr") {
|
|
1024
|
+
const obj = this.genExpr(expr.operand.expr);
|
|
1025
|
+
const field = expr.operand.field;
|
|
1026
|
+
return `((__o) => Object.defineProperties({ get value() { return __o.${field}; }, set value(v) { __o.${field} = v; } }, { __target: { value: __o }, __index: { value: "${field}" } }))(${obj})`;
|
|
598
1027
|
}
|
|
599
1028
|
// For non-boxed vars (primitives, etc.), wrap in { value: x }
|
|
600
1029
|
return `{ value: ${this.genExpr(expr.operand)} }`;
|
|
@@ -602,7 +1031,11 @@ export const expressionGenMethods = {
|
|
|
602
1031
|
// Dereference: *p
|
|
603
1032
|
if (op === "*") {
|
|
604
1033
|
if (this._isStructPointerType(expr.operand._type)) {
|
|
605
|
-
return this.genExpr(expr.operand);
|
|
1034
|
+
return this._genNilChecked(this.genExpr(expr.operand));
|
|
1035
|
+
}
|
|
1036
|
+
if (expr.operand._type?.base?.kind === "typeParam") {
|
|
1037
|
+
if (expr._derefOpTarget) return `${this.genExpr(expr.operand)}.value`;
|
|
1038
|
+
return `((__p) => (${this._typeParamIsStructJs(expr.operand._type.base.name)} ? __p : __p.value))(${this.genExpr(expr.operand)})`;
|
|
606
1039
|
}
|
|
607
1040
|
return `${this.genExpr(expr.operand)}.value`;
|
|
608
1041
|
}
|
|
@@ -613,6 +1046,16 @@ export const expressionGenMethods = {
|
|
|
613
1046
|
return inner;
|
|
614
1047
|
}
|
|
615
1048
|
const code = this.genExpr(expr.operand);
|
|
1049
|
+
const wrap = this.strict
|
|
1050
|
+
? SMALL_UNSIGNED_WRAP[this._typeName(expr._type)]
|
|
1051
|
+
: null;
|
|
1052
|
+
if (wrap && (op === "-" || op === "~")) return `(${op}(${code}) ${wrap})`;
|
|
1053
|
+
// -MinInt wraps back to itself for signed narrow integers.
|
|
1054
|
+
const signedWrap =
|
|
1055
|
+
this.strict && op === "-"
|
|
1056
|
+
? SIGNED_NEG_WRAP[this._typeName(expr._type)]
|
|
1057
|
+
: null;
|
|
1058
|
+
if (signedWrap) return `(-(${code}) ${signedWrap})`;
|
|
616
1059
|
const needsParens =
|
|
617
1060
|
this._emittedBinaryOp(expr.operand) !== null ||
|
|
618
1061
|
((op === "-" || op === "+") && /^[-+]/.test(code));
|
|
@@ -620,9 +1063,16 @@ export const expressionGenMethods = {
|
|
|
620
1063
|
},
|
|
621
1064
|
|
|
622
1065
|
_genSelectorExpr(expr) {
|
|
1066
|
+
// Go-source stdlib member: bundled under its mangled top-level name.
|
|
1067
|
+
if (expr._stdName) return expr._stdName;
|
|
1068
|
+
if (this._isStdlibFuncValue(expr)) return this._genStdlibFuncValue(expr);
|
|
623
1069
|
if (expr._isMethodExpr) {
|
|
1070
|
+
const namedExpr = this._namedMethodExprInfo(expr);
|
|
1071
|
+
if (namedExpr) return `${namedExpr.cls}.$${expr.field}`;
|
|
624
1072
|
return `((recv, ...args) => recv.${expr.field}(...args))`;
|
|
625
1073
|
}
|
|
1074
|
+
const namedValue = this._genNamedMethodValue(expr);
|
|
1075
|
+
if (namedValue !== undefined) return namedValue;
|
|
626
1076
|
if (expr.expr.kind === "Ident") {
|
|
627
1077
|
const nsConst = NS_CONSTANTS[expr.expr.name]?.[expr.field];
|
|
628
1078
|
if (nsConst !== undefined) {
|
|
@@ -643,19 +1093,104 @@ export const expressionGenMethods = {
|
|
|
643
1093
|
this.useHelper("cryptoRand");
|
|
644
1094
|
} else if (expr.expr.name === "sha256") {
|
|
645
1095
|
this.useHelper("sha256");
|
|
1096
|
+
} else if (expr.expr.name === "os") {
|
|
1097
|
+
this.useHelper("os");
|
|
646
1098
|
}
|
|
647
1099
|
return nsConst;
|
|
648
1100
|
}
|
|
649
1101
|
}
|
|
650
1102
|
const base = this.genExpr(expr.expr);
|
|
651
|
-
|
|
1103
|
+
// Bundled GoFront package member; browser-global names carry the `$`
|
|
1104
|
+
// suffix of their declaration (see jsBindingName).
|
|
1105
|
+
if (this.bundledPackages.has(base))
|
|
1106
|
+
return BROWSER_GLOBALS.has(expr.field) ? `${expr.field}$` : expr.field;
|
|
652
1107
|
if (expr.expr._type?.kind === "pointer" && expr.field !== "value")
|
|
653
1108
|
return this._genPointerSelectorField(expr, base);
|
|
654
|
-
const sel = `${base}.${this._jsMemberName(expr)}`;
|
|
655
|
-
if (expr._isMethodValue && !expr._callee)
|
|
1109
|
+
const sel = `${base}${this._promotedPath(expr)}.${this._jsMemberName(expr)}`;
|
|
1110
|
+
if (expr._isMethodValue && !expr._callee) {
|
|
1111
|
+
// A value receiver is copied when the method value is evaluated.
|
|
1112
|
+
const t = expr.expr._type;
|
|
1113
|
+
if (!expr._type?._ptrRecv && this._aggregateBase(t))
|
|
1114
|
+
return `((__r) => __r.${this._jsMemberName(expr)}.bind(__r))(${this._cloneJs(t, base)})`;
|
|
1115
|
+
return `${sel}.bind(${base})`;
|
|
1116
|
+
}
|
|
656
1117
|
return sel;
|
|
657
1118
|
},
|
|
658
1119
|
|
|
1120
|
+
// `pkg.Fn` used as a value (not called): a stdlib function is inlined at
|
|
1121
|
+
// its call sites, so a value is an arrow function that makes that call.
|
|
1122
|
+
_isStdlibFuncValue(expr) {
|
|
1123
|
+
const t = expr._type;
|
|
1124
|
+
if (expr._callee || t?.kind !== "func" || expr._isMethodExpr) return false;
|
|
1125
|
+
const ns = expr.expr;
|
|
1126
|
+
if (ns?.kind !== "Ident" || ns._type?.kind !== "namespace") return false;
|
|
1127
|
+
return !this.bundledPackages.has(ns.name) && !hasTypeParamType(t);
|
|
1128
|
+
},
|
|
1129
|
+
|
|
1130
|
+
_genStdlibFuncValue(expr) {
|
|
1131
|
+
const t = expr._type;
|
|
1132
|
+
const ps = t.params ?? [];
|
|
1133
|
+
const nFixed = t.variadic && ps.length > 0 ? ps.length - 1 : ps.length;
|
|
1134
|
+
const fixed = ps.slice(0, nFixed).map((p, i) => stdIdent(`__a${i}`, p));
|
|
1135
|
+
const names = fixed.map((a) => a.name);
|
|
1136
|
+
const callJs = (args) =>
|
|
1137
|
+
this.genExpr({
|
|
1138
|
+
kind: "CallExpr",
|
|
1139
|
+
func: { ...expr, _callee: true },
|
|
1140
|
+
args,
|
|
1141
|
+
_type: t.returns?.[0] ?? { kind: "basic", name: "void" },
|
|
1142
|
+
});
|
|
1143
|
+
if (nFixed === ps.length)
|
|
1144
|
+
return `((${names.join(", ")}) => ${callJs(fixed)})`;
|
|
1145
|
+
// Stdlib calls are inlined per operand count, so dispatch on it.
|
|
1146
|
+
const rest = `__a${nFixed}`;
|
|
1147
|
+
let body = `(() => { throw new Error("too many arguments in call to ${expr.expr.name}.${expr.field}"); })()`;
|
|
1148
|
+
for (let k = 8; k >= 0; k--) {
|
|
1149
|
+
const extra = Array.from({ length: k }, (_, i) =>
|
|
1150
|
+
stdIdent(`${rest}[${i}]`, ps[nFixed]),
|
|
1151
|
+
);
|
|
1152
|
+
body = `${rest}.length === ${k} ? ${callJs([...fixed, ...extra])} : ${body}`;
|
|
1153
|
+
}
|
|
1154
|
+
return `((${[...names, `...${rest}`].join(", ")}) => ${body})`;
|
|
1155
|
+
},
|
|
1156
|
+
|
|
1157
|
+
// A promoted field (`o.A` with embedded `In`) lives on the embedded value:
|
|
1158
|
+
// the class also carries a flattened copy, so reads and writes go through
|
|
1159
|
+
// the embedded path (`.In`) to stay in sync. Returns "" for direct fields.
|
|
1160
|
+
_promotedPath(expr) {
|
|
1161
|
+
if (expr._isMethodValue || expr._isMethodExpr) return "";
|
|
1162
|
+
let t = expr.expr._type;
|
|
1163
|
+
if (t?.kind === "pointer") t = t.base;
|
|
1164
|
+
const path = this._embedPath(t, expr.field, 0);
|
|
1165
|
+
return path ? path.map((n) => `.${n}`).join("") : "";
|
|
1166
|
+
},
|
|
1167
|
+
|
|
1168
|
+
_embedPath(t, field, depth) {
|
|
1169
|
+
const st = t?.kind === "named" ? t.underlying : t;
|
|
1170
|
+
if (st?.kind !== "struct" || !st.fields?.has(field) || depth > 8)
|
|
1171
|
+
return null;
|
|
1172
|
+
const direct = (st._structFields ?? []).some(
|
|
1173
|
+
(sf) => !sf.embedded && sf.names?.includes(field),
|
|
1174
|
+
);
|
|
1175
|
+
if (direct) return null;
|
|
1176
|
+
for (const sf of st._structFields ?? []) {
|
|
1177
|
+
if (!sf.embedded) continue;
|
|
1178
|
+
const inner = sf.type?.kind === "pointer" ? sf.type.base : sf.type;
|
|
1179
|
+
if (inner?.kind !== "named" || !this.structNames.has(inner.name))
|
|
1180
|
+
continue;
|
|
1181
|
+
const name = inner.name;
|
|
1182
|
+
if (name === field) return null;
|
|
1183
|
+
const ist = inner.underlying;
|
|
1184
|
+
if (!ist?.fields?.has(field)) continue;
|
|
1185
|
+
const sub = this._embedPath(inner, field, depth + 1);
|
|
1186
|
+
const idirect = (ist._structFields ?? []).some(
|
|
1187
|
+
(x) => !x.embedded && x.names?.includes(field),
|
|
1188
|
+
);
|
|
1189
|
+
return idirect ? [name] : sub ? [name, ...sub] : null;
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1192
|
+
},
|
|
1193
|
+
|
|
659
1194
|
// PascalCase aliases of Web API methods (gl.ClearColor) map to the real camelCase member.
|
|
660
1195
|
_jsMemberName(expr) {
|
|
661
1196
|
const f = expr.field;
|
|
@@ -672,10 +1207,167 @@ export const expressionGenMethods = {
|
|
|
672
1207
|
return f;
|
|
673
1208
|
},
|
|
674
1209
|
|
|
1210
|
+
// ── Named non-struct types with methods: raw values, static dispatch ──
|
|
1211
|
+
|
|
1212
|
+
// Boxes a raw named value (or *T) entering an interface so dynamic dispatch works.
|
|
1213
|
+
_genNamedIfaceBox(js, type, isPtr) {
|
|
1214
|
+
const info = this._namedBoxInfo(type);
|
|
1215
|
+
if (!info) return js;
|
|
1216
|
+
return isPtr ? `new ${info.cls}$Ptr(${js})` : `new ${info.cls}(${js})`;
|
|
1217
|
+
},
|
|
1218
|
+
|
|
1219
|
+
// Static target of `x.M` on a named non-struct type: its class and the
|
|
1220
|
+
// receiver argument (the { value } box for pointer receivers).
|
|
1221
|
+
_namedMethodTarget(sel) {
|
|
1222
|
+
const recvType = sel.expr?._type;
|
|
1223
|
+
const viaPtr = recvType?.kind === "pointer";
|
|
1224
|
+
const info = this._namedBoxInfo(viaPtr ? recvType.base : recvType);
|
|
1225
|
+
const method = info?.methods.get(sel.field);
|
|
1226
|
+
if (!method) return null;
|
|
1227
|
+
let recv;
|
|
1228
|
+
if (method._ptrRecv) {
|
|
1229
|
+
recv = viaPtr ? this.genExpr(sel.expr) : this._genAddressOfExpr(sel.expr);
|
|
1230
|
+
} else {
|
|
1231
|
+
recv = viaPtr
|
|
1232
|
+
? `${this.genExpr(sel.expr)}.value`
|
|
1233
|
+
: this.genExpr(sel.expr);
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
cls: info.cls,
|
|
1237
|
+
recv,
|
|
1238
|
+
byValue: !method._ptrRecv,
|
|
1239
|
+
recvType: viaPtr ? recvType.base : recvType,
|
|
1240
|
+
};
|
|
1241
|
+
},
|
|
1242
|
+
|
|
1243
|
+
// `x.M` used as a value: binds the receiver now, as Go does.
|
|
1244
|
+
_genNamedMethodValue(expr) {
|
|
1245
|
+
if (!expr._isMethodValue || expr._callee) return undefined;
|
|
1246
|
+
const target = this._namedMethodTarget(expr);
|
|
1247
|
+
if (!target) return undefined;
|
|
1248
|
+
// A value receiver (a named array) is copied when the value is evaluated.
|
|
1249
|
+
const recv = target.byValue
|
|
1250
|
+
? this._cloneJs(target.recvType, target.recv)
|
|
1251
|
+
: target.recv;
|
|
1252
|
+
return `((__r) => (...__a) => ${target.cls}.$${expr.field}(__r, ...__a))(${recv})`;
|
|
1253
|
+
},
|
|
1254
|
+
|
|
1255
|
+
_genAddressOfExpr(operand) {
|
|
1256
|
+
return this._genUnaryExpr({
|
|
1257
|
+
kind: "UnaryExpr",
|
|
1258
|
+
op: "&",
|
|
1259
|
+
operand,
|
|
1260
|
+
_type: { kind: "pointer", base: operand._type },
|
|
1261
|
+
});
|
|
1262
|
+
},
|
|
1263
|
+
|
|
1264
|
+
// `T.M` method expression on a named non-struct type.
|
|
1265
|
+
_namedMethodExprInfo(sel) {
|
|
1266
|
+
const t =
|
|
1267
|
+
sel.expr?._type?.kind === "named"
|
|
1268
|
+
? sel.expr._type
|
|
1269
|
+
: this.checker?.types.get(sel.expr?.name);
|
|
1270
|
+
const info = this._namedBoxInfo(t);
|
|
1271
|
+
return info?.methods.has(sel.field) ? info : null;
|
|
1272
|
+
},
|
|
1273
|
+
|
|
1274
|
+
_genNamedMethodCall(expr) {
|
|
1275
|
+
const sel = expr.func;
|
|
1276
|
+
if (sel._isMethodExpr) {
|
|
1277
|
+
const info = this._namedMethodExprInfo(sel);
|
|
1278
|
+
if (!info) return undefined;
|
|
1279
|
+
return `${info.cls}.$${sel.field}(${this._genCallArgs(expr)})`;
|
|
1280
|
+
}
|
|
1281
|
+
const target = this._namedMethodTarget(sel);
|
|
1282
|
+
if (!target) return undefined;
|
|
1283
|
+
const args = this._genCallArgs(expr);
|
|
1284
|
+
return `${target.cls}.$${sel.field}(${args ? `${target.recv}, ${args}` : target.recv})`;
|
|
1285
|
+
},
|
|
1286
|
+
|
|
1287
|
+
// `F[int]` as a value is a function that passes F its zero-value factories.
|
|
1288
|
+
_genInstantiationExpr(expr) {
|
|
1289
|
+
const fn = this.genExpr(expr.expr);
|
|
1290
|
+
if (!expr._genericFnType || expr._callee) return fn;
|
|
1291
|
+
const { typeParams } = expr._genericFnType;
|
|
1292
|
+
const zeros = this._typeArgFactories(
|
|
1293
|
+
typeParams,
|
|
1294
|
+
expr._typeArgs,
|
|
1295
|
+
this._genericFnNeedsTypeInfo(expr._genericFnType),
|
|
1296
|
+
);
|
|
1297
|
+
return `((...a) => ${fn}(${zeros.join(", ")}, ...a))`;
|
|
1298
|
+
},
|
|
1299
|
+
|
|
1300
|
+
_genCallArgs(expr) {
|
|
1301
|
+
if (expr._multiForward) return `...${this.genExpr(expr.args[0])}`;
|
|
1302
|
+
return expr.args
|
|
1303
|
+
.map((a, i) =>
|
|
1304
|
+
a._spread
|
|
1305
|
+
? this._genSpreadArg(expr, a)
|
|
1306
|
+
: this._callerCopiesArg(expr, i)
|
|
1307
|
+
? this.genValueExpr(a)
|
|
1308
|
+
: this.genExpr(a),
|
|
1309
|
+
)
|
|
1310
|
+
.join(", ");
|
|
1311
|
+
},
|
|
1312
|
+
|
|
1313
|
+
// `f(xs...)`: a Go-compiled variadic callee receives the slice itself in a
|
|
1314
|
+
// marker (its prologue unwraps it); other callees get a JS spread.
|
|
1315
|
+
_genSpreadArg(call, arg) {
|
|
1316
|
+
const js = this.genExpr(arg);
|
|
1317
|
+
return this._calleeIsGoVariadic(call) ? `{ __sp: ${js} }` : `...${js}`;
|
|
1318
|
+
},
|
|
1319
|
+
|
|
1320
|
+
_calleeIsGoVariadic(call) {
|
|
1321
|
+
const f = call.func;
|
|
1322
|
+
if (f.kind === "Ident") {
|
|
1323
|
+
if (this.variadicFns?.has(f.name)) return true;
|
|
1324
|
+
// A local func value (closure or parameter) compiled from Go source.
|
|
1325
|
+
const imported =
|
|
1326
|
+
this.checker?._externalNames?.has(f.name) ||
|
|
1327
|
+
BROWSER_GLOBALS.has(f.name) ||
|
|
1328
|
+
[...(this.jsImports?.values() ?? [])].some((names) =>
|
|
1329
|
+
names.includes(f.name),
|
|
1330
|
+
);
|
|
1331
|
+
return !imported && f._type?.kind === "func" && !!f._type.variadic;
|
|
1332
|
+
}
|
|
1333
|
+
if (f.kind !== "SelectorExpr" || f._isMethodExpr) return false;
|
|
1334
|
+
const recv = this._resolveRecvName(f.expr._type);
|
|
1335
|
+
return !!recv && !!this.variadicFns?.has(`${recv}.${f.field}`);
|
|
1336
|
+
},
|
|
1337
|
+
|
|
1338
|
+
// A callee copies its mutated array and struct parameters on entry, except
|
|
1339
|
+
// when the parameter is a type parameter (generics are erased in JS) or
|
|
1340
|
+
// part of a variadic rest array. For those the caller passes aggregate
|
|
1341
|
+
// (array and struct) arguments as copies.
|
|
1342
|
+
_callerCopiesArg(expr, i) {
|
|
1343
|
+
const argT = expr.args[i]._type;
|
|
1344
|
+
if (!this._aggregateBase(argT) && !this._typeParamClone(argT, ""))
|
|
1345
|
+
return false;
|
|
1346
|
+
let ft = expr.func?._type ?? expr.func?.expr?._type;
|
|
1347
|
+
if (ft?.kind === "generic") ft = ft.underlying;
|
|
1348
|
+
if (ft?.kind !== "func") return false;
|
|
1349
|
+
const params = ft.params ?? [];
|
|
1350
|
+
if (ft.variadic && i >= params.length - 1) return true;
|
|
1351
|
+
return params[i]?.kind === "typeParam";
|
|
1352
|
+
},
|
|
1353
|
+
|
|
675
1354
|
_genPointerSelectorField(expr, base) {
|
|
676
1355
|
if (this._isStructPointerType(expr.expr._type)) {
|
|
677
|
-
|
|
678
|
-
|
|
1356
|
+
// Strict mode: a field of a nil struct pointer panics as in Go.
|
|
1357
|
+
const recv =
|
|
1358
|
+
this.strict && !expr._isMethodValue ? this._genNilChecked(base) : base;
|
|
1359
|
+
const sel = `${recv}${this._promotedPath(expr)}.${expr.field}`;
|
|
1360
|
+
if (expr._isMethodValue && !expr._callee) {
|
|
1361
|
+
// `p.M` with a value receiver binds a copy of `*p` now.
|
|
1362
|
+
if (expr._derefRecv) {
|
|
1363
|
+
const copy = this._cloneJs(
|
|
1364
|
+
expr.expr._type.base,
|
|
1365
|
+
this.strict ? this._genNilChecked(base) : base,
|
|
1366
|
+
);
|
|
1367
|
+
return `((__r) => __r.${expr.field}.bind(__r))(${copy})`;
|
|
1368
|
+
}
|
|
1369
|
+
return `${sel}.bind(${base})`;
|
|
1370
|
+
}
|
|
679
1371
|
return sel;
|
|
680
1372
|
}
|
|
681
1373
|
const sel = `${base}.value.${expr.field}`;
|
|
@@ -691,13 +1383,48 @@ export const expressionGenMethods = {
|
|
|
691
1383
|
? `${this.genExpr(expr.expr)}.${wrapField}`
|
|
692
1384
|
: this.genExpr(expr.expr);
|
|
693
1385
|
const idx = this.genExpr(expr.index);
|
|
694
|
-
if (expr._mapValueType
|
|
695
|
-
return this._genMapIndexExpr(expr, base, idx);
|
|
1386
|
+
if (expr._mapValueType) {
|
|
1387
|
+
if (!expr._lvalue) return this._genMapIndexExpr(expr, base, idx);
|
|
1388
|
+
return `${this._genMapWriteBase(base)}[${idx}]`;
|
|
1389
|
+
}
|
|
1390
|
+
const checked = this._genStrictIndex(expr, base, idx);
|
|
1391
|
+
if (checked) return checked;
|
|
696
1392
|
if (this._isStringExprType(exprType) && !expr._lvalue)
|
|
697
1393
|
return `${base}.charCodeAt(${idx})`;
|
|
698
1394
|
return `${base}[${idx}]`;
|
|
699
1395
|
},
|
|
700
1396
|
|
|
1397
|
+
// `p` for a field access `p.F`, panicking with Go's message when nil.
|
|
1398
|
+
_genNilChecked(base) {
|
|
1399
|
+
this.useHelper("nilCall");
|
|
1400
|
+
return `__nilcall(${base})`;
|
|
1401
|
+
},
|
|
1402
|
+
|
|
1403
|
+
// The map of a write `m[k] = v`: strict mode panics on a nil map.
|
|
1404
|
+
_genMapWriteBase(base) {
|
|
1405
|
+
if (!this.strict) return base;
|
|
1406
|
+
this.useHelper("nilMap");
|
|
1407
|
+
return `__mw(${base})`;
|
|
1408
|
+
},
|
|
1409
|
+
|
|
1410
|
+
// Strict mode: `xs[i]` on a slice, array or string with Go's bounds check
|
|
1411
|
+
// (null when no check applies). Reads of slices and arrays use `__at`;
|
|
1412
|
+
// lvalues and string reads use `base[__ix(base, i)]`, binding `base` to
|
|
1413
|
+
// `__lv` first when evaluating it twice could repeat a call.
|
|
1414
|
+
_genStrictIndex(expr, base, idx) {
|
|
1415
|
+
if (!this.strict || expr._inBounds) return null;
|
|
1416
|
+
const t = expr.expr._type;
|
|
1417
|
+
const u = t?.kind === "named" ? t.underlying : t;
|
|
1418
|
+
const isStr = this._isStringExprType(t);
|
|
1419
|
+
if (!isStr && u?.kind !== "slice" && u?.kind !== "array") return null;
|
|
1420
|
+
this.useHelper("bounds");
|
|
1421
|
+
if (!expr._lvalue && !isStr) return `__at(${base}, ${idx})`;
|
|
1422
|
+
const once = this._hasCallExpr(expr.expr);
|
|
1423
|
+
const obj = once ? `(__lv = ${base})` : base;
|
|
1424
|
+
const at = `__ix(${once ? "__lv" : base}, ${idx})`;
|
|
1425
|
+
return expr._lvalue ? `${obj}[${at}]` : `${obj}.charCodeAt(${at})`;
|
|
1426
|
+
},
|
|
1427
|
+
|
|
701
1428
|
_genMapIndexExpr(expr, base, idx) {
|
|
702
1429
|
const zero = this.zeroValueForType(expr._mapValueType);
|
|
703
1430
|
if (this._hasCallExpr(expr.expr) || this._hasCallExpr(expr.index))
|
|
@@ -706,25 +1433,35 @@ export const expressionGenMethods = {
|
|
|
706
1433
|
},
|
|
707
1434
|
|
|
708
1435
|
_isStringExprType(exprType) {
|
|
1436
|
+
const t = exprType?.kind === "named" ? exprType.underlying : exprType;
|
|
709
1437
|
return (
|
|
710
|
-
(
|
|
711
|
-
(
|
|
1438
|
+
(t?.kind === "basic" && t.name === "string") ||
|
|
1439
|
+
(t?.kind === "untyped" && t.base === "string")
|
|
712
1440
|
);
|
|
713
1441
|
},
|
|
714
1442
|
|
|
715
1443
|
_genSliceExpr(expr) {
|
|
716
1444
|
const base = this.genExpr(expr.expr);
|
|
717
|
-
const lo = expr.low ? this.genExpr(expr.low) : "";
|
|
718
|
-
const hi = expr.high ? this.genExpr(expr.high) : "";
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1445
|
+
const lo = expr.low ? this.genExpr(expr.low) : "0";
|
|
1446
|
+
const hi = expr.high ? this.genExpr(expr.high) : "undefined";
|
|
1447
|
+
if (this._isStringExprType(expr.expr._type))
|
|
1448
|
+
return expr.high ? `${base}.slice(${lo}, ${hi})` : `${base}.slice(${lo})`;
|
|
1449
|
+
// `b[:]`, `b[0:]` and `a[:]` have the operand's length and capacity:
|
|
1450
|
+
// the result is the operand itself. Other bounds make a view sharing
|
|
1451
|
+
// the backing store (`__slice`, which checks the bounds against cap).
|
|
1452
|
+
const zeroLow = !expr.low || Number(expr.low._const?.v) === 0;
|
|
1453
|
+
if (zeroLow && !expr.high && !expr.max) return base;
|
|
1454
|
+
this.useHelper("slice");
|
|
1455
|
+
// `x = x[lo:hi]` (see `_markSelfReslice`) copies a plain array when
|
|
1456
|
+
// the result stays within `len(x)`; typed arrays always share.
|
|
1457
|
+
if (expr._selfReslice && !isTypedArraySlice(expr.expr._type))
|
|
1458
|
+
return `__reslice(${base}, ${lo}, ${hi})`;
|
|
1459
|
+
const max = expr.max ? `, ${this.genExpr(expr.max)}` : "";
|
|
1460
|
+
return `__slice(${base}, ${lo}, ${hi}${max})`;
|
|
724
1461
|
},
|
|
725
1462
|
|
|
726
1463
|
_genFuncLit(expr) {
|
|
727
|
-
const params = expr.params
|
|
1464
|
+
const params = this._paramNames(expr.params).join(", ");
|
|
728
1465
|
const asyncPrefix = expr.async ? "async " : "";
|
|
729
1466
|
const saved = this.out;
|
|
730
1467
|
const prevBoxed = this._boxedVars;
|
|
@@ -751,22 +1488,176 @@ export const expressionGenMethods = {
|
|
|
751
1488
|
const val = hoist ? "__v" : src;
|
|
752
1489
|
const check = this._typeCheckExpr(expr.type, val);
|
|
753
1490
|
const wrap = (body) => (hoist ? `((__v) => ${body})(${src})` : body);
|
|
1491
|
+
// Named non-struct values sit in interfaces as boxes; the asserted value is raw.
|
|
1492
|
+
const out = this._assertedValueJs(expr.type, val);
|
|
754
1493
|
if (!expr._commaOk) {
|
|
755
1494
|
// plain assertion: panic if check fails (matches Go behavior)
|
|
756
1495
|
if (check === "true") return src; // can't check at runtime — pass through
|
|
757
1496
|
return wrap(
|
|
758
|
-
`(${check} ? ${
|
|
1497
|
+
`(${check} ? ${out} : (() => { throw new Error("interface conversion: type assertion failed"); })())`,
|
|
759
1498
|
);
|
|
760
1499
|
}
|
|
761
|
-
// comma-ok: emit [value-or-zero, runtimeTypeCheck]
|
|
1500
|
+
// comma-ok: emit [value-or-zero, runtimeTypeCheck]; an asserted array
|
|
1501
|
+
// is a copy of the boxed value.
|
|
762
1502
|
const zero = this.zeroValueForTypeNode(expr.type);
|
|
763
|
-
|
|
1503
|
+
const arrT = this._typeFromNode(expr.type);
|
|
1504
|
+
const okOut = arrT?.kind === "array" ? this._cloneJs(arrT, out) : out;
|
|
1505
|
+
return wrap(`(${check} ? [${okOut}, true] : [${zero}, false])`);
|
|
1506
|
+
},
|
|
1507
|
+
|
|
1508
|
+
// `csv.ParseError` names the class of the bundled Go-source package type.
|
|
1509
|
+
_qualifiedStructName(name) {
|
|
1510
|
+
if (!name.includes(".") || this.structNames.has(name)) return name;
|
|
1511
|
+
const cls = this.checker?.types.get(name)?.name;
|
|
1512
|
+
return cls && this.structNames.has(cls) ? cls : name;
|
|
1513
|
+
},
|
|
1514
|
+
|
|
1515
|
+
_typeNameCheckExpr(typeName, val) {
|
|
1516
|
+
// A type parameter: the instantiated type's test travels with its factory.
|
|
1517
|
+
const factory = this._typeParamZeroFactory(typeName);
|
|
1518
|
+
if (factory) return `(${factory}?.is?.(${val}) ?? true)`;
|
|
1519
|
+
const name = this._qualifiedStructName(typeName);
|
|
1520
|
+
if (TYPEOF_NUMBER_NAMES.has(name)) return `typeof ${val} === "number"`;
|
|
1521
|
+
if (name === "string") return `typeof ${val} === "string"`;
|
|
1522
|
+
if (name === "bool") return `typeof ${val} === "boolean"`;
|
|
1523
|
+
if (name === "nil") return `${val} === null`;
|
|
1524
|
+
// F32x4 and Mask32x4 are both frozen 4-element arrays; lanes tell them apart.
|
|
1525
|
+
if (name === "simd.F32x4" || name === "F32x4")
|
|
1526
|
+
return `(Array.isArray(${val}) && ${val}.length === 4 && typeof ${val}[0] === "number")`;
|
|
1527
|
+
if (name === "simd.Mask32x4" || name === "Mask32x4")
|
|
1528
|
+
return `(Array.isArray(${val}) && ${val}.length === 4 && typeof ${val}[0] === "boolean")`;
|
|
1529
|
+
if (name === "error")
|
|
1530
|
+
return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
|
|
1531
|
+
if (this.structNames.has(name))
|
|
1532
|
+
return `(${val} instanceof ${name} && ${val}.__p !== true)`;
|
|
1533
|
+
const named = this.checker?.types.get(name);
|
|
1534
|
+
const namedInfo = this._namedBoxInfo(named);
|
|
1535
|
+
if (namedInfo) return `(${val} instanceof ${namedInfo.cls})`;
|
|
1536
|
+
// User-declared interfaces: check the dynamic value has every method.
|
|
1537
|
+
if (!name.includes(".") && named?.underlying?.kind === "interface")
|
|
1538
|
+
return this._ifaceMethodCheck(
|
|
1539
|
+
val,
|
|
1540
|
+
[...named.underlying.methods.keys()],
|
|
1541
|
+
this._unwrapIsMulti(named.underlying.methods.get("Unwrap")),
|
|
1542
|
+
);
|
|
1543
|
+
return "true"; // unknown type — can't check at runtime
|
|
1544
|
+
},
|
|
1545
|
+
|
|
1546
|
+
// Anonymous interfaces and pointers to named non-struct types; null otherwise.
|
|
1547
|
+
_namedOrIfaceCheckExpr(typeNode, val) {
|
|
1548
|
+
if (typeNode.kind === "InterfaceType" && !(typeNode.embeds?.length > 0))
|
|
1549
|
+
return this._ifaceMethodCheck(
|
|
1550
|
+
val,
|
|
1551
|
+
(typeNode.methods ?? []).map((m) => m.name),
|
|
1552
|
+
this._unwrapIsMulti(
|
|
1553
|
+
(typeNode.methods ?? []).find((m) => m.name === "Unwrap"),
|
|
1554
|
+
),
|
|
1555
|
+
);
|
|
1556
|
+
if (typeNode.kind === "PointerType" && typeNode.base?.kind === "TypeName") {
|
|
1557
|
+
const info = this._namedBoxInfo(
|
|
1558
|
+
this.checker?.types.get(typeNode.base.name),
|
|
1559
|
+
);
|
|
1560
|
+
if (info) return `(${val} instanceof ${info.cls}$Ptr)`;
|
|
1561
|
+
}
|
|
1562
|
+
return null;
|
|
1563
|
+
},
|
|
1564
|
+
|
|
1565
|
+
// `Unwrap() []error` (errors.Join) and `Unwrap() error` share a name: the
|
|
1566
|
+
// JS function of a multi-error `Unwrap` is tagged `__multi`. `method` is
|
|
1567
|
+
// a func type or an interface method AST; null when there is no `Unwrap`.
|
|
1568
|
+
_unwrapIsMulti(method) {
|
|
1569
|
+
if (!method) return null;
|
|
1570
|
+
const ret = method.returnType ?? method.returns?.[0];
|
|
1571
|
+
const u = ret?.kind === "named" ? ret.underlying : ret;
|
|
1572
|
+
return u?.kind === "slice" || u?.kind === "SliceType";
|
|
1573
|
+
},
|
|
1574
|
+
|
|
1575
|
+
_ifaceMethodCheck(val, methodNames, multiUnwrap = null) {
|
|
1576
|
+
const checks = methodNames.map((m) =>
|
|
1577
|
+
m === "Unwrap" && multiUnwrap !== null
|
|
1578
|
+
? `typeof ${val}.${m} === "function" && (${val}.${m}.__multi === true) === ${multiUnwrap}`
|
|
1579
|
+
: `typeof ${val}.${m} === "function"`,
|
|
1580
|
+
);
|
|
1581
|
+
return `(${[`${val} != null`, ...checks].join(" && ")})`;
|
|
1582
|
+
},
|
|
1583
|
+
|
|
1584
|
+
// The value an assertion to `typeNode` yields: raw, unboxed from the
|
|
1585
|
+
// interface's box for a named non-struct type (known only at run time
|
|
1586
|
+
// for a type parameter).
|
|
1587
|
+
_assertedValueJs(typeNode, val) {
|
|
1588
|
+
if (this._isNamedBoxTypeNode(typeNode)) return `${val}.__gfUnbox()`;
|
|
1589
|
+
const factory =
|
|
1590
|
+
typeNode?.kind === "TypeName"
|
|
1591
|
+
? this._typeParamZeroFactory(typeNode.name)
|
|
1592
|
+
: null;
|
|
1593
|
+
return factory ? `(${factory}?.u ? ${val}.__gfUnbox() : ${val})` : val;
|
|
1594
|
+
},
|
|
1595
|
+
|
|
1596
|
+
_isNamedBoxTypeNode(typeNode) {
|
|
1597
|
+
if (typeNode?.kind === "TypeName")
|
|
1598
|
+
return Boolean(
|
|
1599
|
+
this._namedBoxInfo(this.checker?.types.get(typeNode.name)),
|
|
1600
|
+
);
|
|
1601
|
+
if (typeNode?.kind === "PointerType" && typeNode.base?.kind === "TypeName")
|
|
1602
|
+
return Boolean(
|
|
1603
|
+
this._namedBoxInfo(this.checker?.types.get(typeNode.base.name)),
|
|
1604
|
+
);
|
|
1605
|
+
return false;
|
|
1606
|
+
},
|
|
1607
|
+
|
|
1608
|
+
// Converting a struct to a different defined struct type builds a copy in
|
|
1609
|
+
// the target's class (so its methods exist); null when not applicable.
|
|
1610
|
+
_genStructClassConversion(targetName, srcType, inner) {
|
|
1611
|
+
if (!targetName || targetName.includes("[")) return null;
|
|
1612
|
+
const target = this.checker?.types.get(targetName);
|
|
1613
|
+
if (target?.kind !== "named" || target.underlying?.kind !== "struct")
|
|
1614
|
+
return null;
|
|
1615
|
+
if (srcType?.name === targetName) return null;
|
|
1616
|
+
const srcU = srcType?.kind === "named" ? srcType.underlying : srcType;
|
|
1617
|
+
if (srcU?.kind !== "struct") return null;
|
|
1618
|
+
const cls = this._dequalify(targetName);
|
|
1619
|
+
return `Object.assign(new ${cls}(), ${this._cloneJs(srcType, inner)})`;
|
|
764
1620
|
},
|
|
765
1621
|
|
|
766
1622
|
_genTypeConversionCall(expr) {
|
|
767
|
-
const
|
|
1623
|
+
const targetType = expr._conversionTargetType;
|
|
768
1624
|
const arg = this.genExpr(expr.args[0]);
|
|
769
|
-
|
|
1625
|
+
const viaParam = this._genTypeParamConversion(targetType?.name, arg);
|
|
1626
|
+
if (targetType?.kind === "typeParam" && viaParam) return viaParam;
|
|
1627
|
+
const structConv = this._genStructClassConversion(
|
|
1628
|
+
targetType?.kind === "named" ? targetType.name : undefined,
|
|
1629
|
+
expr.args[0]?._type,
|
|
1630
|
+
arg,
|
|
1631
|
+
);
|
|
1632
|
+
if (structConv) return structConv;
|
|
1633
|
+
const isRaw =
|
|
1634
|
+
targetType?.name === "json.RawMessage" ||
|
|
1635
|
+
targetType?.name === "encoding/json.RawMessage";
|
|
1636
|
+
const base =
|
|
1637
|
+
targetType?.kind === "named" ? targetType.underlying : targetType;
|
|
1638
|
+
if (
|
|
1639
|
+
targetType?.kind === "named" &&
|
|
1640
|
+
base?.kind === "basic" &&
|
|
1641
|
+
this._namedBoxInfo(targetType)
|
|
1642
|
+
)
|
|
1643
|
+
return this._genPrimitiveConversion(
|
|
1644
|
+
{ expr: expr.args[0] },
|
|
1645
|
+
arg,
|
|
1646
|
+
base.name,
|
|
1647
|
+
);
|
|
1648
|
+
if (base?.kind === "slice") {
|
|
1649
|
+
const elem = base.elem?.name;
|
|
1650
|
+
if (
|
|
1651
|
+
(elem === "byte" || elem === "uint8") &&
|
|
1652
|
+
this._isStringExprType(expr.args[0]?._type)
|
|
1653
|
+
) {
|
|
1654
|
+
const enc = `new TextEncoder().encode(${arg})`;
|
|
1655
|
+
return isRaw ? `((u) => { u.__raw = true; return u; })(${enc})` : enc;
|
|
1656
|
+
}
|
|
1657
|
+
if (isRaw) {
|
|
1658
|
+
return `((u) => { u.__raw = true; return u; })(${arg})`;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
770
1661
|
return arg;
|
|
771
1662
|
},
|
|
772
1663
|
|
|
@@ -785,14 +1676,50 @@ export const expressionGenMethods = {
|
|
|
785
1676
|
if (result !== undefined) return result;
|
|
786
1677
|
}
|
|
787
1678
|
|
|
1679
|
+
const nilSafe = this._nilSafeMethodClass(expr.func);
|
|
1680
|
+
if (nilSafe) {
|
|
1681
|
+
// The method tests its receiver for nil: call it through the
|
|
1682
|
+
// prototype so a nil `this` reaches it instead of a TypeError.
|
|
1683
|
+
const recv = this.genExpr(expr.func.expr);
|
|
1684
|
+
const rest = this._genCallArgs(expr);
|
|
1685
|
+
return `${nilSafe}.prototype.${expr.func.field}.call(${rest ? `${recv}, ${rest}` : recv})`;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
788
1688
|
expr.func._callee = true;
|
|
789
1689
|
const rawFn = this.genExpr(expr.func);
|
|
790
1690
|
const fn = expr.func.kind === "FuncLit" ? `(${rawFn})` : rawFn;
|
|
791
|
-
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1691
|
+
const args = this._genCallArgs(expr);
|
|
1692
|
+
const zeros = this._genericCallZeros(expr);
|
|
1693
|
+
if (!zeros) return `${fn}(${args})`;
|
|
1694
|
+
return `${fn}(${args ? `${zeros}, ${args}` : zeros})`;
|
|
1695
|
+
},
|
|
1696
|
+
|
|
1697
|
+
// The class of a pointer-receiver struct method that accepts a nil receiver
|
|
1698
|
+
// when `func` selects it on a pointer of exactly that struct, else null.
|
|
1699
|
+
_nilSafeMethodClass(func) {
|
|
1700
|
+
if (func?.kind !== "SelectorExpr" || func._isMethodExpr) return null;
|
|
1701
|
+
const t = func.expr?._type;
|
|
1702
|
+
if (t?.kind !== "pointer" || t.base?.kind !== "named") return null;
|
|
1703
|
+
const cls = t.base.name;
|
|
1704
|
+
if (t.base.typeArgs?.length) return null;
|
|
1705
|
+
return this.nilSafeMethods?.has(`${cls}.${func.field}`) ? cls : null;
|
|
1706
|
+
},
|
|
1707
|
+
|
|
1708
|
+
// The leading zero-value factories of a call to a generic function
|
|
1709
|
+
// (`F(x)` with inferred, `F[int](x)` with explicit type arguments), or null.
|
|
1710
|
+
_genericCallZeros(expr) {
|
|
1711
|
+
const site = expr._genericFnType
|
|
1712
|
+
? expr
|
|
1713
|
+
: expr.func.kind === "InstantiationExpr" && expr.func._genericFnType
|
|
1714
|
+
? expr.func
|
|
1715
|
+
: null;
|
|
1716
|
+
if (!site) return null;
|
|
1717
|
+
const { typeParams } = site._genericFnType;
|
|
1718
|
+
return this._typeArgFactories(
|
|
1719
|
+
typeParams,
|
|
1720
|
+
site._typeArgs,
|
|
1721
|
+
this._genericFnNeedsTypeInfo(site._genericFnType),
|
|
1722
|
+
).join(", ");
|
|
796
1723
|
},
|
|
797
1724
|
|
|
798
1725
|
_isErrorInterfaceType(t) {
|
|
@@ -814,7 +1741,35 @@ export const expressionGenMethods = {
|
|
|
814
1741
|
return recvType?.name ?? recvType?.base?.name;
|
|
815
1742
|
},
|
|
816
1743
|
|
|
1744
|
+
_genJsonStreamCall(recvName, expr) {
|
|
1745
|
+
const isEnc = this._isJsonEncoderName(recvName);
|
|
1746
|
+
if (isEnc && expr.func.field === "Encode") {
|
|
1747
|
+
this.useHelper("json");
|
|
1748
|
+
const enc = this.genExpr(expr.func.expr);
|
|
1749
|
+
this._markJsonAnyEncoders();
|
|
1750
|
+
const argType = expr.args[0]?._type;
|
|
1751
|
+
const codec = this._getJsonEncoder(argType);
|
|
1752
|
+
const arg = this.genExpr(expr.args[0]);
|
|
1753
|
+
return `__json_encoder_encode(${enc}, ${codec}, ${arg})`;
|
|
1754
|
+
}
|
|
1755
|
+
const isDec =
|
|
1756
|
+
recvName === "json.Decoder" ||
|
|
1757
|
+
recvName === "encoding/json.Decoder" ||
|
|
1758
|
+
recvName?.startsWith("Decoder$$");
|
|
1759
|
+
if (isDec && expr.func.field === "Decode") {
|
|
1760
|
+
this.useHelper("json");
|
|
1761
|
+
const dec = this.genExpr(expr.func.expr);
|
|
1762
|
+
const targetType = expr.args[0]?._type;
|
|
1763
|
+
const codec = this._getJsonDecoder(targetType);
|
|
1764
|
+
const arg = this.genExpr(expr.args[0]);
|
|
1765
|
+
return `__json_decoder_decode(${dec}, ${codec}, ${arg}, ${this._jsonTargetArgs(targetType)})`;
|
|
1766
|
+
}
|
|
1767
|
+
return undefined;
|
|
1768
|
+
},
|
|
1769
|
+
|
|
817
1770
|
_genReceiverTypeCall(recvName, expr) {
|
|
1771
|
+
const jsonStream = this._genJsonStreamCall(recvName, expr);
|
|
1772
|
+
if (jsonStream !== undefined) return jsonStream;
|
|
818
1773
|
if (recvName === "strings.Builder" || recvName === "bytes.Buffer")
|
|
819
1774
|
return this._genBuilderCall(recvName, expr.func.field, expr);
|
|
820
1775
|
if (recvName === "regexp.Regexp")
|
|
@@ -823,10 +1778,39 @@ export const expressionGenMethods = {
|
|
|
823
1778
|
return this._genTimeMethodCall(expr.func.field, expr);
|
|
824
1779
|
if (recvName === "time.Duration")
|
|
825
1780
|
return this._genDurationMethodCall(expr.func.field, expr);
|
|
826
|
-
if (recvName === "
|
|
1781
|
+
if (recvName === "time.Month") {
|
|
1782
|
+
this.useHelper("duration");
|
|
1783
|
+
return `__monthString(${this.genExpr(expr.func.expr)})`;
|
|
1784
|
+
}
|
|
1785
|
+
if (recvName === "time.Weekday") {
|
|
1786
|
+
this.useHelper("duration");
|
|
1787
|
+
return `__weekdayString(${this.genExpr(expr.func.expr)})`;
|
|
1788
|
+
}
|
|
1789
|
+
if (recvName === "time.Location") {
|
|
1790
|
+
const recv = this.genExpr(expr.func.expr);
|
|
1791
|
+
return `(${recv} ? ${recv}.String() : "UTC")`;
|
|
1792
|
+
}
|
|
1793
|
+
if (recvName === "time.Timer") {
|
|
1794
|
+
const recv = this.genExpr(expr.func.expr);
|
|
1795
|
+
if (expr.func.field === "Stop")
|
|
1796
|
+
return `(${recv} ? ${recv}.Stop() : false)`;
|
|
1797
|
+
if (expr.func.field === "Reset")
|
|
1798
|
+
return `(${recv} ? ${recv}.Reset(${this.genExpr(expr.args[0])}) : false)`;
|
|
1799
|
+
}
|
|
1800
|
+
if (
|
|
1801
|
+
recvName === "testing.T" ||
|
|
1802
|
+
recvName === "testing.B" ||
|
|
1803
|
+
recvName === "testing.M" ||
|
|
1804
|
+
recvName === "testing.TB"
|
|
1805
|
+
)
|
|
827
1806
|
return this._genTestingMethodCall(expr.func.field, expr);
|
|
828
1807
|
if (recvName === "strings.Replacer") {
|
|
829
1808
|
const recv = this.genExpr(expr.func.expr);
|
|
1809
|
+
if (expr.func.field === "WriteString") {
|
|
1810
|
+
const w = this.genExpr(expr.args[0]);
|
|
1811
|
+
const s = this.genExpr(expr.args[1]);
|
|
1812
|
+
return `((r,w,s) => { s = r ? r.Replace(s) : s; const x = w?.value ?? w; if (typeof x.WriteString === "function") return x.WriteString(s); if (typeof x._buf === "string") { x._buf += s; return [s.length, null]; } if (Array.isArray(x._buf)) { const bytes = new TextEncoder().encode(s); for (let i = 0; i < bytes.length; i++) x._buf.push(bytes[i]); return [bytes.length, null]; } if (typeof x.Write === "function") return x.Write(new TextEncoder().encode(s)); return [0, "strings.Replacer.WriteString: unsupported writer"]; })(${recv}, ${w}, ${s})`;
|
|
1813
|
+
}
|
|
830
1814
|
const arg = this.genExpr(expr.args[0]);
|
|
831
1815
|
return `(${recv} ? ${recv}.Replace(${arg}) : ${arg})`;
|
|
832
1816
|
}
|
|
@@ -834,9 +1818,13 @@ export const expressionGenMethods = {
|
|
|
834
1818
|
const args = expr.args.map((a) => this.genExpr(a)).join(", ");
|
|
835
1819
|
return `${this.genExpr(expr.func.expr)}.subarray(${args})`;
|
|
836
1820
|
}
|
|
1821
|
+
if (/^(simd\.)?(F32x4|Mask32x4)$/.test(recvName ?? "")) {
|
|
1822
|
+
return this._genSimdReceiverCall(expr, recvName.endsWith("Mask32x4"));
|
|
1823
|
+
}
|
|
837
1824
|
if (recvName === "sync.Mutex" || recvName === "sync.RWMutex") {
|
|
838
1825
|
if (expr.func.field === "TryLock" || expr.func.field === "TryRLock")
|
|
839
1826
|
return "true";
|
|
1827
|
+
if (expr.func.field === "RLocker") return "({ Lock() {}, Unlock() {} })";
|
|
840
1828
|
return "(void 0)";
|
|
841
1829
|
}
|
|
842
1830
|
if (recvName === "sync.Once" && expr.func.field === "Do") {
|
|
@@ -879,6 +1867,17 @@ export const expressionGenMethods = {
|
|
|
879
1867
|
return undefined;
|
|
880
1868
|
},
|
|
881
1869
|
|
|
1870
|
+
_genSimdReceiverCall(expr, isMask = false) {
|
|
1871
|
+
this.useHelper("simd");
|
|
1872
|
+
const method = expr.func.field;
|
|
1873
|
+
const recv = this.genExpr(expr.func.expr);
|
|
1874
|
+
const args = expr.args.map((a) => this.genExpr(a));
|
|
1875
|
+
if (method === "Lane") return `${recv}[${args[0]}]`;
|
|
1876
|
+
if (method === "Splat" && !isMask) return `__simd_splat(${args[0]})`;
|
|
1877
|
+
const helper = (isMask ? SIMD_MASK_HELPERS : SIMD_VECTOR_HELPERS)[method];
|
|
1878
|
+
return helper ? `${helper}(${[recv, ...args].join(", ")})` : undefined;
|
|
1879
|
+
},
|
|
1880
|
+
|
|
882
1881
|
_genSelectorCall(expr) {
|
|
883
1882
|
if (this._isErrorMethodCall(expr)) {
|
|
884
1883
|
const recv = this.genExpr(expr.func.expr);
|
|
@@ -896,6 +1895,8 @@ export const expressionGenMethods = {
|
|
|
896
1895
|
const recvName = this._resolveRecvName(recvType);
|
|
897
1896
|
const typed = this._genReceiverTypeCall(recvName, expr);
|
|
898
1897
|
if (typed !== undefined) return typed;
|
|
1898
|
+
const namedCall = this._genNamedMethodCall(expr);
|
|
1899
|
+
if (namedCall !== undefined) return namedCall;
|
|
899
1900
|
if (this.strict && this._isInterfaceType(recvType)) {
|
|
900
1901
|
// Go panics with a nil-dereference runtime error; JS would throw a
|
|
901
1902
|
// TypeError with a different message.
|
|
@@ -936,7 +1937,12 @@ export const expressionGenMethods = {
|
|
|
936
1937
|
(t?.kind === "named" && t.underlying?.kind === "map")
|
|
937
1938
|
)
|
|
938
1939
|
return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
|
|
939
|
-
|
|
1940
|
+
// A slice: every element goes back to its zero value (the length stays).
|
|
1941
|
+
const st = t?.kind === "named" ? t.underlying : t;
|
|
1942
|
+
const zero = this.zeroValueForType(st?.elem);
|
|
1943
|
+
if (/^(new |\{|\[|Array|__nm\()|\$z_/.test(zero))
|
|
1944
|
+
return `((__s) => { if (__s) for (let __i = 0; __i < __s.length; __i++) __s[__i] = ${zero}; })(${js})`;
|
|
1945
|
+
return `(${js})?.fill(${zero})`;
|
|
940
1946
|
},
|
|
941
1947
|
|
|
942
1948
|
_genBuiltinIdent(name, expr) {
|
|
@@ -944,6 +1950,15 @@ export const expressionGenMethods = {
|
|
|
944
1950
|
return gen ? gen(this, expr) : undefined;
|
|
945
1951
|
},
|
|
946
1952
|
|
|
1953
|
+
// The source operand of `append(b, x...)` / `copy(b, x)`: a string
|
|
1954
|
+
// contributes its UTF-8 bytes.
|
|
1955
|
+
_genSliceSource(node) {
|
|
1956
|
+
const js = this.genExpr(node);
|
|
1957
|
+
return this._isStringExprType(node._type)
|
|
1958
|
+
? `new TextEncoder().encode(${js})`
|
|
1959
|
+
: js;
|
|
1960
|
+
},
|
|
1961
|
+
|
|
947
1962
|
genAppend(expr) {
|
|
948
1963
|
const retType = expr._type;
|
|
949
1964
|
const wrapField = this._namedWrapperField(retType, expr.args[0]);
|
|
@@ -953,16 +1968,25 @@ export const expressionGenMethods = {
|
|
|
953
1968
|
const elems = expr.args
|
|
954
1969
|
.slice(1)
|
|
955
1970
|
.map((a) =>
|
|
956
|
-
a._spread
|
|
1971
|
+
a._spread
|
|
1972
|
+
? `...(${this._genSliceSource(a)} ?? [])`
|
|
1973
|
+
: this.genValueExpr(a),
|
|
957
1974
|
);
|
|
958
1975
|
if (elems.length === 0) return this.genExpr(expr.args[0]);
|
|
959
1976
|
this.useHelper("append");
|
|
960
1977
|
return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
|
|
961
1978
|
}
|
|
962
1979
|
const slice = this.genExpr(expr.args[0]);
|
|
1980
|
+
// Spread elements of array type are values: append copies them.
|
|
1981
|
+
const st = expr._type ?? expr.args[0]._type;
|
|
1982
|
+
const elemT = (st?.kind === "named" ? st.underlying : st)?.elem;
|
|
1983
|
+
const spreadOf = (a) =>
|
|
1984
|
+
this._aggregateBase(elemT)?.kind === "array"
|
|
1985
|
+
? `...(${this.genExpr(a)} ?? []).map((__e) => ${this._cloneJs(elemT, "__e")})`
|
|
1986
|
+
: `...(${this._genSliceSource(a)} ?? [])`;
|
|
963
1987
|
const elems = expr.args
|
|
964
1988
|
.slice(1)
|
|
965
|
-
.map((a) => (a._spread ?
|
|
1989
|
+
.map((a) => (a._spread ? spreadOf(a) : this.genValueExpr(a)));
|
|
966
1990
|
if (elems.length === 0) return slice;
|
|
967
1991
|
this.useHelper("append");
|
|
968
1992
|
const sliceType = expr._type ?? expr.args[0]._type;
|
|
@@ -981,12 +2005,16 @@ export const expressionGenMethods = {
|
|
|
981
2005
|
// make(map[K]V) → {}
|
|
982
2006
|
const typeArg = expr.args[0];
|
|
983
2007
|
const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
|
|
984
|
-
|
|
2008
|
+
// A defined type (`type S []int`) makes like its underlying type.
|
|
2009
|
+
const madeType =
|
|
2010
|
+
typeArg._type?.kind === "named"
|
|
2011
|
+
? typeArg._type.underlying
|
|
2012
|
+
: typeArg._type;
|
|
2013
|
+
const resolvedKind = madeType?.kind;
|
|
985
2014
|
if (typeNode.kind === "SliceType" || resolvedKind === "slice") {
|
|
986
2015
|
const n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
|
|
987
2016
|
const elemNode = typeNode.kind === "SliceType" ? typeNode.elem : null;
|
|
988
|
-
const elemResolved =
|
|
989
|
-
typeArg._type?.kind === "slice" ? typeArg._type.elem : null;
|
|
2017
|
+
const elemResolved = madeType?.kind === "slice" ? madeType.elem : null;
|
|
990
2018
|
const typedCtor =
|
|
991
2019
|
typedArrayConstructorForElem(elemResolved) ??
|
|
992
2020
|
(elemNode?.name
|
|
@@ -995,8 +2023,14 @@ export const expressionGenMethods = {
|
|
|
995
2023
|
name: elemNode.name,
|
|
996
2024
|
})
|
|
997
2025
|
: null);
|
|
2026
|
+
// make([]T, n, cap) allocates cap elements and returns a view of
|
|
2027
|
+
// the first n (`__mkslice`), so append within cap shares them.
|
|
2028
|
+
const capArg = expr.args[2] ? this.genExpr(expr.args[2]) : null;
|
|
2029
|
+
if (capArg) this.useHelper("slice");
|
|
2030
|
+
const withCap = (alloc) =>
|
|
2031
|
+
capArg ? `__mkslice(${n}, ${alloc(capArg)})` : alloc(n);
|
|
998
2032
|
if (typedCtor) {
|
|
999
|
-
return `new ${typedCtor}(${
|
|
2033
|
+
return withCap((k) => `new ${typedCtor}(${k})`);
|
|
1000
2034
|
}
|
|
1001
2035
|
// Use the proper zero value for the element type (e.g. new Point() not 0)
|
|
1002
2036
|
let zero = "null";
|
|
@@ -1006,10 +2040,10 @@ export const expressionGenMethods = {
|
|
|
1006
2040
|
zero = this.zeroValueForTypeNode(elemNode);
|
|
1007
2041
|
}
|
|
1008
2042
|
// If zero value is a constructor call, use a factory function so each element is distinct
|
|
1009
|
-
if (zero.startsWith("new ")) {
|
|
1010
|
-
return `Array.from({length: ${
|
|
2043
|
+
if (zero.startsWith("new ") || zero.includes("$z_")) {
|
|
2044
|
+
return withCap((k) => `Array.from({length: ${k}}, () => ${zero})`);
|
|
1011
2045
|
}
|
|
1012
|
-
return `new Array(${
|
|
2046
|
+
return withCap((k) => `new Array(${k}).fill(${zero})`);
|
|
1013
2047
|
}
|
|
1014
2048
|
// map or fallback
|
|
1015
2049
|
return "{}";
|
|
@@ -1039,22 +2073,42 @@ export const expressionGenMethods = {
|
|
|
1039
2073
|
return t?.kind === "MapType";
|
|
1040
2074
|
},
|
|
1041
2075
|
|
|
2076
|
+
// The one place a map literal key becomes a JS property name: a plain
|
|
2077
|
+
// string literal stays as is, any other key is computed so that the key
|
|
2078
|
+
// encoding of `genExpr` (`__ik`, struct keys) applies.
|
|
2079
|
+
_genMapLitKey(key) {
|
|
2080
|
+
return key.litKind === "STRING"
|
|
2081
|
+
? JSON.stringify(key.value)
|
|
2082
|
+
: `[${this.genExpr(key)}]`;
|
|
2083
|
+
},
|
|
2084
|
+
|
|
1042
2085
|
_genMapLitEntries(elems) {
|
|
1043
2086
|
return elems
|
|
1044
|
-
.map((e) =>
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
: `[${this.genExpr(e.key)}]`;
|
|
1050
|
-
return `${k}: ${this.genValueExpr(e.value)}`;
|
|
1051
|
-
}
|
|
1052
|
-
return this.genValueExpr(e);
|
|
1053
|
-
})
|
|
2087
|
+
.map((e) =>
|
|
2088
|
+
e.kind === "KeyValueExpr"
|
|
2089
|
+
? `${this._genMapLitKey(e.key)}: ${this.genValueExpr(e.value)}`
|
|
2090
|
+
: this.genValueExpr(e),
|
|
2091
|
+
)
|
|
1054
2092
|
.join(", ");
|
|
1055
2093
|
},
|
|
1056
2094
|
|
|
2095
|
+
// A literal whose (possibly elided or named) type is a map type.
|
|
2096
|
+
_isMapLit(expr) {
|
|
2097
|
+
let t = expr._type;
|
|
2098
|
+
for (let i = 0; t?.kind === "named" && i < 32; i++) t = t.underlying;
|
|
2099
|
+
return t?.kind === "map";
|
|
2100
|
+
},
|
|
2101
|
+
|
|
1057
2102
|
_genFallbackCompositeLit(expr) {
|
|
2103
|
+
if (this._isMapLit(expr))
|
|
2104
|
+
return `{ ${this._genMapLitEntries(expr.elems)} }`;
|
|
2105
|
+
const at = this._litArrayType(expr);
|
|
2106
|
+
if (at) {
|
|
2107
|
+
// A named array type without methods (`type V [3]int; V{1}`).
|
|
2108
|
+
const elems = this._genArrayLitElems(at, expr);
|
|
2109
|
+
const ctor = typedArrayConstructorForElem(at.elem);
|
|
2110
|
+
return ctor ? `new ${ctor}([${elems}])` : `[${elems}]`;
|
|
2111
|
+
}
|
|
1058
2112
|
if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
|
|
1059
2113
|
const fields = expr.elems
|
|
1060
2114
|
.map(
|
|
@@ -1095,10 +2149,12 @@ export const expressionGenMethods = {
|
|
|
1095
2149
|
(ce) => (ce._positionalField ?? ce.key?.name) === ef,
|
|
1096
2150
|
);
|
|
1097
2151
|
values[idx] = match
|
|
1098
|
-
? this.genValueExpr(match.value
|
|
2152
|
+
? this.genValueExpr(match.kind === "KeyValueExpr" ? match.value : match)
|
|
1099
2153
|
: fields[idx].zero;
|
|
1100
2154
|
} else {
|
|
1101
|
-
|
|
2155
|
+
const valExpr = this.genExpr(e.value);
|
|
2156
|
+
values[idx] =
|
|
2157
|
+
`(${valExpr} != null ? ${valExpr}.${ef} : ${fields[idx].zero})`;
|
|
1102
2158
|
}
|
|
1103
2159
|
return idx;
|
|
1104
2160
|
},
|
|
@@ -1116,6 +2172,11 @@ export const expressionGenMethods = {
|
|
|
1116
2172
|
const idx = this._fillEmbedField(fields, ef, e, values);
|
|
1117
2173
|
if (idx > maxIdx) maxIdx = idx;
|
|
1118
2174
|
}
|
|
2175
|
+
const embedIdx = fields.findIndex((f) => f.name === k);
|
|
2176
|
+
if (embedIdx >= 0) {
|
|
2177
|
+
values[embedIdx] = this.genValueExpr(e.value);
|
|
2178
|
+
if (embedIdx > maxIdx) maxIdx = embedIdx;
|
|
2179
|
+
}
|
|
1119
2180
|
} else {
|
|
1120
2181
|
const idx = fields.findIndex((f) => f.name === k);
|
|
1121
2182
|
if (idx >= 0) {
|
|
@@ -1129,6 +2190,8 @@ export const expressionGenMethods = {
|
|
|
1129
2190
|
|
|
1130
2191
|
_genPositionalStructLit(typeName, expr) {
|
|
1131
2192
|
const fields = this.getStructFields(typeName);
|
|
2193
|
+
const generic = this._genGenericStructLit(fields, expr);
|
|
2194
|
+
if (generic) return generic;
|
|
1132
2195
|
if (!fields || fields.length === 0 || expr.elems.length === 0) {
|
|
1133
2196
|
return `new ${typeName}()`;
|
|
1134
2197
|
}
|
|
@@ -1139,17 +2202,102 @@ export const expressionGenMethods = {
|
|
|
1139
2202
|
const maxExplicitIdx = hasPositional
|
|
1140
2203
|
? this._fillPositionalStructElems(fields, expr, values)
|
|
1141
2204
|
: this._fillKeyedStructElems(fields, expr, values);
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
return
|
|
2205
|
+
const inst =
|
|
2206
|
+
maxExplicitIdx < 0
|
|
2207
|
+
? `new ${typeName}()`
|
|
2208
|
+
: `new ${typeName}(${values.slice(0, maxExplicitIdx + 1).join(", ")})`;
|
|
2209
|
+
return inst;
|
|
2210
|
+
},
|
|
2211
|
+
|
|
2212
|
+
// `Box[int]{…}`: every field is passed (unset ones as undefined, so the
|
|
2213
|
+
// constructor starts them at their zero value) followed by the factories.
|
|
2214
|
+
_genGenericStructLit(fields, expr) {
|
|
2215
|
+
const t = expr._type;
|
|
2216
|
+
if (t?.kind !== "named" || !t.typeArgs?.length) return null;
|
|
2217
|
+
const values = fields.map(() => undefined);
|
|
2218
|
+
if (expr.elems.length > 0) {
|
|
2219
|
+
const hasPositional = expr.elems.some(
|
|
2220
|
+
(e) => e._positionalField || e.kind !== "KeyValueExpr",
|
|
2221
|
+
);
|
|
2222
|
+
if (hasPositional) this._fillPositionalStructElems(fields, expr, values);
|
|
2223
|
+
else this._fillKeyedStructElems(fields, expr, values);
|
|
2224
|
+
}
|
|
2225
|
+
return this._genericStructNew(t.name, t.typeArgs, values);
|
|
2226
|
+
},
|
|
2227
|
+
|
|
2228
|
+
// The array type of a literal (through named types), or null.
|
|
2229
|
+
_litArrayType(expr) {
|
|
2230
|
+
let t = expr._type;
|
|
2231
|
+
while (t?.kind === "named") t = t.underlying;
|
|
2232
|
+
return t?.kind === "array" && typeof t.size === "number" ? t : null;
|
|
2233
|
+
},
|
|
2234
|
+
|
|
2235
|
+
// Elements of a literal of array type `at` (or `{ size, elem }` for a
|
|
2236
|
+
// slice), placed at their (keyed) indices, with every other slot the
|
|
2237
|
+
// element zero value: `[3]int{1}` is `[1, 0, 0]` and `[5]int{2: 7}` is
|
|
2238
|
+
// `[0, 0, 7, 0, 0]`, as in Go. Keys out of ascending order would
|
|
2239
|
+
// evaluate the elements out of source order, so the values are then
|
|
2240
|
+
// computed first and spread into place.
|
|
2241
|
+
_genArrayLitElems(at, expr) {
|
|
2242
|
+
const values = new Array(at.size).fill(null);
|
|
2243
|
+
const indices = compositeLitIndices(expr.elems);
|
|
2244
|
+
const src = expr.elems.map((e) =>
|
|
2245
|
+
this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e),
|
|
2246
|
+
);
|
|
2247
|
+
const ascending = indices.every((v, i) => i === 0 || v > indices[i - 1]);
|
|
2248
|
+
indices.forEach((idx, i) => {
|
|
2249
|
+
values[idx] = ascending ? src[i] : `v$[${i}]`;
|
|
2250
|
+
});
|
|
2251
|
+
for (let i = 0; i < values.length; i++)
|
|
2252
|
+
values[i] ??= this.zeroValueForType(at.elem);
|
|
2253
|
+
if (ascending) return values.join(", ");
|
|
2254
|
+
return `...((v$) => [${values.join(", ")}])([${src.join(", ")}])`;
|
|
2255
|
+
},
|
|
2256
|
+
|
|
2257
|
+
// `{ size, elem }` for a slice or array literal (through named types).
|
|
2258
|
+
_litIndexedShape(expr) {
|
|
2259
|
+
const at = this._litArrayType(expr);
|
|
2260
|
+
if (at) return at;
|
|
2261
|
+
let t = expr._type;
|
|
2262
|
+
while (t?.kind === "named") t = t.underlying;
|
|
2263
|
+
if (t?.kind !== "slice") return null;
|
|
2264
|
+
// A loop, not a spread: huge literals would overflow the call stack.
|
|
2265
|
+
let max = -1;
|
|
2266
|
+
for (const i of compositeLitIndices(expr.elems)) if (i > max) max = i;
|
|
2267
|
+
const size = max + 1;
|
|
2268
|
+
return { size, elem: t.elem };
|
|
2269
|
+
},
|
|
2270
|
+
|
|
2271
|
+
// A large array literal that leaves most slots zero (`[100000]byte{}`,
|
|
2272
|
+
// `[65536]int{1: 2}`) starts from the zero array, as `var a [N]T` does,
|
|
2273
|
+
// and assigns only the given elements instead of spelling out every
|
|
2274
|
+
// slot. Small or mostly-filled literals stay inline (null).
|
|
2275
|
+
_genSparseArrayLit(expr) {
|
|
2276
|
+
const at = this._litArrayType(expr);
|
|
2277
|
+
if (!at || at.size <= SPARSE_ARRAY_MIN) return null;
|
|
2278
|
+
if (expr.elems.length * 2 >= at.size) return null;
|
|
2279
|
+
const zero = this.zeroValueForType(at);
|
|
2280
|
+
if (expr.elems.length === 0) return zero;
|
|
2281
|
+
const indices = compositeLitIndices(expr.elems);
|
|
2282
|
+
const sets = expr.elems.map((e, i) => {
|
|
2283
|
+
const v = this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e);
|
|
2284
|
+
return `a$[${indices[i]}] = (${v})`;
|
|
2285
|
+
});
|
|
2286
|
+
return `((a$) => (${sets.join(", ")}, a$))(${zero})`;
|
|
2287
|
+
},
|
|
2288
|
+
|
|
2289
|
+
_genLitElems(expr) {
|
|
2290
|
+
const at = this._litIndexedShape(expr);
|
|
2291
|
+
if (at) return this._genArrayLitElems(at, expr);
|
|
2292
|
+
return expr.elems
|
|
2293
|
+
.map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
|
|
2294
|
+
.join(", ");
|
|
1147
2295
|
},
|
|
1148
2296
|
|
|
1149
2297
|
_genSliceOrArrayLit(t, expr) {
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
|
|
2298
|
+
const sparse = this._genSparseArrayLit(expr);
|
|
2299
|
+
if (sparse) return sparse;
|
|
2300
|
+
const elems = this._genLitElems(expr);
|
|
1153
2301
|
const elemNode =
|
|
1154
2302
|
t.kind === "SliceType" || t.kind === "ArrayType" ? t.elem : null;
|
|
1155
2303
|
const elemResolved =
|
|
@@ -1178,6 +2326,18 @@ export const expressionGenMethods = {
|
|
|
1178
2326
|
return "{ _buf: [] }";
|
|
1179
2327
|
if (typeName === "strings.Builder" || typeName === "Builder")
|
|
1180
2328
|
return '{ _buf: "" }';
|
|
2329
|
+
if (typeName === "simd.Mask32x4" || typeName === "Mask32x4")
|
|
2330
|
+
return "Object.freeze([false, false, false, false])";
|
|
2331
|
+
if (typeName === "simd.F32x4" || typeName === "F32x4") {
|
|
2332
|
+
this.useHelper("simd");
|
|
2333
|
+
const a = expr.elems[0] ? this.genExpr(expr.elems[0]) : "0";
|
|
2334
|
+
const b = expr.elems[1] ? this.genExpr(expr.elems[1]) : "0";
|
|
2335
|
+
const c = expr.elems[2] ? this.genExpr(expr.elems[2]) : "0";
|
|
2336
|
+
const d = expr.elems[3] ? this.genExpr(expr.elems[3]) : "0";
|
|
2337
|
+
return `__simd_f32x4(${a}, ${b}, ${c}, ${d})`;
|
|
2338
|
+
}
|
|
2339
|
+
const aliasLit = this._genAliasLit(t, typeName, expr);
|
|
2340
|
+
if (aliasLit !== null) return aliasLit;
|
|
1181
2341
|
if (typeName && this.structNames.has(typeName))
|
|
1182
2342
|
return this._genPositionalStructLit(typeName, expr);
|
|
1183
2343
|
if (typeName && this.namedWrapperNames.has(typeName))
|
|
@@ -1191,14 +2351,68 @@ export const expressionGenMethods = {
|
|
|
1191
2351
|
return this._genFallbackCompositeLit(expr);
|
|
1192
2352
|
},
|
|
1193
2353
|
|
|
2354
|
+
// Literals whose type expression is not the class to construct: a generic
|
|
2355
|
+
// alias `A[X]{…}`, an anonymous `struct{…}{…}`, or `P{…}` for
|
|
2356
|
+
// `type P = struct{…}`. Null for every other literal.
|
|
2357
|
+
_genAliasLit(t, typeName, expr) {
|
|
2358
|
+
if (t.kind === "GenericTypeName" || t.kind === "InstantiationExpr")
|
|
2359
|
+
return this._genGenericAliasLit(typeName, expr);
|
|
2360
|
+
const anon =
|
|
2361
|
+
t.kind === "StructType" ||
|
|
2362
|
+
((t.kind === "TypeName" || t.kind === "Ident") &&
|
|
2363
|
+
this.checker?.types.get(t.name)?.kind === "struct");
|
|
2364
|
+
return anon ? this._genAnonStructLit(expr) : null;
|
|
2365
|
+
},
|
|
2366
|
+
|
|
2367
|
+
// `A[X]{…}` for a generic alias `type A[P any] = T`: the literal has the
|
|
2368
|
+
// aliased type (expr._type), so dispatch on that instead of on `A`.
|
|
2369
|
+
_genGenericAliasLit(typeName, expr) {
|
|
2370
|
+
if (!this.checker?.types.get(typeName)?._generic?.isAlias) return null;
|
|
2371
|
+
const rt = expr._type;
|
|
2372
|
+
const base = rt?.kind === "named" ? rt.underlying : rt;
|
|
2373
|
+
if (rt?.kind === "named" && this.structNames.has(rt.name))
|
|
2374
|
+
return this._genPositionalStructLit(rt.name, expr);
|
|
2375
|
+
if (base?.kind === "map")
|
|
2376
|
+
return `{ ${this._genMapLitEntries(expr.elems)} }`;
|
|
2377
|
+
if (base?.kind === "struct") return this._genAnonStructLit(expr);
|
|
2378
|
+
return this._genImplicitCompositeLit(expr);
|
|
2379
|
+
},
|
|
2380
|
+
|
|
2381
|
+
// A literal of an anonymous struct type: a plain object with every field,
|
|
2382
|
+
// the omitted ones set to their zero value.
|
|
2383
|
+
_genAnonStructLit(expr) {
|
|
2384
|
+
const st =
|
|
2385
|
+
expr._type?.kind === "named" ? expr._type.underlying : expr._type;
|
|
2386
|
+
if (st?.kind !== "struct" || !(st.fields instanceof Map))
|
|
2387
|
+
return this._genFallbackCompositeLit(expr);
|
|
2388
|
+
const values = new Map();
|
|
2389
|
+
for (const e of expr.elems) {
|
|
2390
|
+
if (e._positionalField)
|
|
2391
|
+
values.set(e._positionalField, this.genValueExpr(e));
|
|
2392
|
+
else if (e.kind === "KeyValueExpr")
|
|
2393
|
+
values.set(e.key.name, this.genValueExpr(e.value));
|
|
2394
|
+
}
|
|
2395
|
+
const fields = [...st.fields].map(
|
|
2396
|
+
([name, ft]) =>
|
|
2397
|
+
`${name}: ${values.get(name) ?? this.zeroValueForType(ft)}`,
|
|
2398
|
+
);
|
|
2399
|
+
return `{ ${fields.join(", ")} }`;
|
|
2400
|
+
},
|
|
2401
|
+
|
|
1194
2402
|
_genImplicitCompositeLit(expr) {
|
|
1195
|
-
|
|
2403
|
+
// An elided `&T` (`[]*T{{1}}`) builds the struct; a struct pointer is the object.
|
|
2404
|
+
const lit = expr._type?.kind === "pointer" ? expr._type.base : expr._type;
|
|
2405
|
+
const typeName = lit?.name ?? lit?.underlying?.name;
|
|
1196
2406
|
if (typeName && this.structNames.has(typeName)) {
|
|
1197
2407
|
return this._genPositionalStructLit(typeName, expr);
|
|
1198
2408
|
}
|
|
2409
|
+
if (this._isMapLit(expr))
|
|
2410
|
+
return `{ ${this._genMapLitEntries(expr.elems)} }`;
|
|
1199
2411
|
const hasPositional = expr.elems.some((e) => e._positionalField);
|
|
1200
2412
|
const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
|
|
1201
|
-
|
|
2413
|
+
// `{2: 5}` inside `[2][3]int{…}` is an array, not an object.
|
|
2414
|
+
const indexed = this._litIndexedShape(expr) !== null;
|
|
2415
|
+
if (hasPositional || (hasKeyed && !indexed)) {
|
|
1202
2416
|
const fields = expr.elems
|
|
1203
2417
|
.map((e) => {
|
|
1204
2418
|
if (e._positionalField)
|
|
@@ -1208,7 +2422,9 @@ export const expressionGenMethods = {
|
|
|
1208
2422
|
.join(", ");
|
|
1209
2423
|
return `{ ${fields} }`;
|
|
1210
2424
|
}
|
|
1211
|
-
const
|
|
2425
|
+
const sparse = this._genSparseArrayLit(expr);
|
|
2426
|
+
if (sparse) return sparse;
|
|
2427
|
+
const elems = this._genLitElems(expr);
|
|
1212
2428
|
if (isTypedArraySlice(expr._type)) {
|
|
1213
2429
|
const typedCtor = typedArrayConstructorForElem(expr._type.elem);
|
|
1214
2430
|
if (typedCtor) return `new ${typedCtor}([${elems}])`;
|
|
@@ -1220,13 +2436,10 @@ export const expressionGenMethods = {
|
|
|
1220
2436
|
const namedType = this.checker?.types.get(typeName);
|
|
1221
2437
|
const u = namedType?.underlying;
|
|
1222
2438
|
if (u?.kind === "map") {
|
|
1223
|
-
return `
|
|
2439
|
+
return `{ ${this._genMapLitEntries(expr.elems)} }`;
|
|
1224
2440
|
}
|
|
1225
|
-
// Default: slice
|
|
1226
|
-
|
|
1227
|
-
.map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
|
|
1228
|
-
.join(", ");
|
|
1229
|
-
return `new ${typeName}([${elems}])`;
|
|
2441
|
+
// Default: slice or array
|
|
2442
|
+
return `[${this._genLitElems(expr)}]`;
|
|
1230
2443
|
},
|
|
1231
2444
|
|
|
1232
2445
|
// ── Helpers ───────────────────────────────────────────────────
|
|
@@ -1238,6 +2451,7 @@ export const expressionGenMethods = {
|
|
|
1238
2451
|
|
|
1239
2452
|
getTypeName(typeNode) {
|
|
1240
2453
|
if (!typeNode) return null;
|
|
2454
|
+
if (typeNode._stdName) return typeNode._stdName;
|
|
1241
2455
|
if (typeNode.kind === "TypeName") return this._dequalify(typeNode.name);
|
|
1242
2456
|
if (typeNode.kind === "GenericTypeName") return typeNode.name;
|
|
1243
2457
|
if (typeNode.kind === "Ident") return typeNode.name;
|
|
@@ -1268,15 +2482,105 @@ export const expressionGenMethods = {
|
|
|
1268
2482
|
},
|
|
1269
2483
|
|
|
1270
2484
|
// Returns true if the AST node contains a function call (side-effect risk).
|
|
1271
|
-
_hasCallExpr(node) {
|
|
2485
|
+
_hasCallExpr(node, seen = new Set()) {
|
|
1272
2486
|
if (!node || typeof node !== "object") return false;
|
|
2487
|
+
if (seen.has(node)) return false;
|
|
2488
|
+
seen.add(node);
|
|
1273
2489
|
if (node.kind === "CallExpr") return true;
|
|
1274
|
-
for (const v of Object.
|
|
1275
|
-
if (
|
|
2490
|
+
for (const [k, v] of Object.entries(node)) {
|
|
2491
|
+
if (k.startsWith("_") || k === "scope") continue;
|
|
2492
|
+
if (v && typeof v === "object" && this._hasCallExpr(v, seen)) return true;
|
|
1276
2493
|
}
|
|
1277
2494
|
return false;
|
|
1278
2495
|
},
|
|
1279
2496
|
|
|
2497
|
+
// ── Generic zero values (see HELPER_GENERICS) ─────────────────
|
|
2498
|
+
|
|
2499
|
+
// JS condition: type parameter `name` was instantiated with a struct (its
|
|
2500
|
+
// zero value is a struct object, which is its own pointer when erased; a
|
|
2501
|
+
// map's zero is a bare `{}`, so only a class instance or a populated
|
|
2502
|
+
// object counts).
|
|
2503
|
+
_typeParamIsStructJs(name) {
|
|
2504
|
+
const f = this._typeParamZeroFactory(name);
|
|
2505
|
+
return f
|
|
2506
|
+
? `((__z) => __z !== null && typeof __z === "object" && !Array.isArray(__z) && !ArrayBuffer.isView(__z) && (Object.getPrototypeOf(Object.getPrototypeOf(__z)) !== null || Object.keys(__z).length > 0))(${f}())`
|
|
2507
|
+
: "false";
|
|
2508
|
+
},
|
|
2509
|
+
|
|
2510
|
+
// The zero-value factory bound to type parameter `name` in the code being
|
|
2511
|
+
// emitted (`$z_T`), or null outside a generic body.
|
|
2512
|
+
_typeParamZeroFactory(name) {
|
|
2513
|
+
return this._typeParamZeros?.get(name) ?? null;
|
|
2514
|
+
},
|
|
2515
|
+
|
|
2516
|
+
// Runs `fn` with type parameters `names` bound to the factories `$z_<name>`.
|
|
2517
|
+
_withTypeParamZeros(names, fn) {
|
|
2518
|
+
const prev = this._typeParamZeros;
|
|
2519
|
+
this._typeParamZeros = names
|
|
2520
|
+
? new Map(names.map((n) => [n, `$z_${n}`]))
|
|
2521
|
+
: null;
|
|
2522
|
+
try {
|
|
2523
|
+
return fn();
|
|
2524
|
+
} finally {
|
|
2525
|
+
this._typeParamZeros = prev;
|
|
2526
|
+
}
|
|
2527
|
+
},
|
|
2528
|
+
|
|
2529
|
+
// A factory `() => zero` for type `t`: what a generic callee receives for
|
|
2530
|
+
// one of its type parameters.
|
|
2531
|
+
zeroFactoryFor(t) {
|
|
2532
|
+
if (t?.kind === "typeParam") {
|
|
2533
|
+
const bound = this._typeParamZeroFactory(t.name);
|
|
2534
|
+
if (bound) return bound;
|
|
2535
|
+
}
|
|
2536
|
+
this.useHelper("generics");
|
|
2537
|
+
const zero = this.zeroValueForType(t);
|
|
2538
|
+
return Object.hasOwn(SHARED_ZERO_FACTORIES, zero)
|
|
2539
|
+
? SHARED_ZERO_FACTORIES[zero]
|
|
2540
|
+
: `() => (${zero})`;
|
|
2541
|
+
},
|
|
2542
|
+
|
|
2543
|
+
// One factory per type parameter of generic function or type `generic`.
|
|
2544
|
+
// `withInfo`: the callee needs the instantiated types (see type-info.js).
|
|
2545
|
+
_typeArgFactories(typeParams, typeArgs, withInfo = false) {
|
|
2546
|
+
return typeParams.map((_, i) =>
|
|
2547
|
+
withInfo
|
|
2548
|
+
? this._typeInfoFactory(typeArgs?.[i])
|
|
2549
|
+
: this.zeroFactoryFor(typeArgs?.[i]),
|
|
2550
|
+
);
|
|
2551
|
+
},
|
|
2552
|
+
|
|
2553
|
+
// `new Box(args…, [factories])` for an instance of generic struct `name`
|
|
2554
|
+
// with `typeArgs`; fields without an argument start at their zero value.
|
|
2555
|
+
// Null when `name` is not a generic struct.
|
|
2556
|
+
_genericStructNew(name, typeArgs, args = []) {
|
|
2557
|
+
const generic = this.checker?.types.get(name);
|
|
2558
|
+
const st = generic?.underlying;
|
|
2559
|
+
const cls = this._dequalify(name);
|
|
2560
|
+
if (!generic?._generic || generic._generic.isAlias) return null;
|
|
2561
|
+
if (st?.kind !== "struct") return null;
|
|
2562
|
+
if (!this.structNames.has(cls)) return null;
|
|
2563
|
+
const tps = generic._generic.typeParams;
|
|
2564
|
+
const vals = Array.from(
|
|
2565
|
+
{ length: st.fields.size },
|
|
2566
|
+
(_, i) => args[i] ?? "undefined",
|
|
2567
|
+
);
|
|
2568
|
+
const tz = this._typeArgFactories(
|
|
2569
|
+
tps,
|
|
2570
|
+
typeArgs,
|
|
2571
|
+
this._genericTypeNeedsTypeInfo(generic) ||
|
|
2572
|
+
typeArgs?.some((t) => mentionsTypeParam(t)),
|
|
2573
|
+
);
|
|
2574
|
+
const id = instanceId(typeArgs);
|
|
2575
|
+
if (id !== null) tz.push(JSON.stringify(id));
|
|
2576
|
+
else {
|
|
2577
|
+
const runtimeId = this._runtimeInstanceId(typeArgs);
|
|
2578
|
+
if (runtimeId) tz.push(runtimeId);
|
|
2579
|
+
}
|
|
2580
|
+
vals.push(`[${tz.join(", ")}]`);
|
|
2581
|
+
return `new ${cls}(${vals.join(", ")})`;
|
|
2582
|
+
},
|
|
2583
|
+
|
|
1280
2584
|
// Returns the JS zero-value literal for a basic type name, or null if not a basic type.
|
|
1281
2585
|
_zeroForBasicName(name) {
|
|
1282
2586
|
return Object.hasOwn(ZERO_FOR_BASIC, name) ? ZERO_FOR_BASIC[name] : null;
|
|
@@ -1288,6 +2592,19 @@ export const expressionGenMethods = {
|
|
|
1288
2592
|
if (entry[0]) this.useHelper(entry[0]);
|
|
1289
2593
|
return entry[1];
|
|
1290
2594
|
}
|
|
2595
|
+
const named = this.checker?.types.get(name);
|
|
2596
|
+
const u = named?.kind === "named" ? named.underlying : null;
|
|
2597
|
+
if (
|
|
2598
|
+
u &&
|
|
2599
|
+
u.kind !== "struct" &&
|
|
2600
|
+
u.kind !== "interface" &&
|
|
2601
|
+
u.kind !== "named"
|
|
2602
|
+
)
|
|
2603
|
+
return this.zeroValueForType(u);
|
|
2604
|
+
if (named?.kind === "named" && u?.kind === "struct") {
|
|
2605
|
+
const clsName = named.name ?? this._dequalify(name);
|
|
2606
|
+
if (this.structNames.has(clsName)) return `new ${clsName}()`;
|
|
2607
|
+
}
|
|
1291
2608
|
name = this._dequalify(name);
|
|
1292
2609
|
if (this.structNames.has(name)) return `new ${name}()`;
|
|
1293
2610
|
return "null";
|
|
@@ -1296,7 +2613,10 @@ export const expressionGenMethods = {
|
|
|
1296
2613
|
zeroValueForTypeNode(typeNode) {
|
|
1297
2614
|
if (!typeNode) return "null";
|
|
1298
2615
|
switch (typeNode.kind) {
|
|
2616
|
+
case "Ident":
|
|
1299
2617
|
case "TypeName": {
|
|
2618
|
+
const factory = this._typeParamZeroFactory(typeNode.name);
|
|
2619
|
+
if (factory) return `${factory}()`;
|
|
1300
2620
|
const basic = this._zeroForBasicName(typeNode.name);
|
|
1301
2621
|
if (basic !== null) return basic;
|
|
1302
2622
|
return this._zeroForNamedType(typeNode.name);
|
|
@@ -1318,7 +2638,7 @@ export const expressionGenMethods = {
|
|
|
1318
2638
|
);
|
|
1319
2639
|
}
|
|
1320
2640
|
case "MapType":
|
|
1321
|
-
return
|
|
2641
|
+
return this._nilMapZero();
|
|
1322
2642
|
case "PointerType":
|
|
1323
2643
|
return "null";
|
|
1324
2644
|
case "StructType": {
|
|
@@ -1331,16 +2651,31 @@ export const expressionGenMethods = {
|
|
|
1331
2651
|
.join(", ");
|
|
1332
2652
|
return `{ ${fields} }`;
|
|
1333
2653
|
}
|
|
2654
|
+
case "GenericTypeName":
|
|
2655
|
+
case "InstantiationExpr":
|
|
2656
|
+
return (
|
|
2657
|
+
this._genericStructNew(
|
|
2658
|
+
this.getTypeName(typeNode),
|
|
2659
|
+
typeNode._typeArgs,
|
|
2660
|
+
) ?? "null"
|
|
2661
|
+
);
|
|
1334
2662
|
default:
|
|
1335
2663
|
return "null";
|
|
1336
2664
|
}
|
|
1337
2665
|
},
|
|
1338
2666
|
|
|
2667
|
+
// A nil map: a fresh tagged `{}` in js packages (`m == nil` holds while it
|
|
2668
|
+
// stays empty); in strict mode the frozen `__nilmap`, which panics on write.
|
|
2669
|
+
_nilMapZero() {
|
|
2670
|
+
this.useHelper(this.strict ? "nilMap" : "nm");
|
|
2671
|
+
return this.strict ? "__nilmap" : "__nm()";
|
|
2672
|
+
},
|
|
2673
|
+
|
|
1339
2674
|
_arrayZero(n, ctor, elemZero) {
|
|
1340
2675
|
if (ctor) return `new ${ctor}(${n})`;
|
|
1341
2676
|
if (n === 0) return "[]";
|
|
1342
|
-
if (/^(new |\{|\[|Array)/.test(elemZero))
|
|
1343
|
-
return `Array.from({ length: ${n} }, () => ${elemZero})`;
|
|
2677
|
+
if (/^(new |\{|\[|Array|__nm\()|\$z_/.test(elemZero))
|
|
2678
|
+
return `Array.from({ length: ${n} }, () => (${elemZero}))`;
|
|
1344
2679
|
return `new Array(${n}).fill(${elemZero})`;
|
|
1345
2680
|
},
|
|
1346
2681
|
|
|
@@ -1359,7 +2694,7 @@ export const expressionGenMethods = {
|
|
|
1359
2694
|
this.zeroValueForType(t.elem),
|
|
1360
2695
|
);
|
|
1361
2696
|
case "map":
|
|
1362
|
-
return
|
|
2697
|
+
return this._nilMapZero();
|
|
1363
2698
|
case "struct": {
|
|
1364
2699
|
const fields = [...t.fields.entries()]
|
|
1365
2700
|
.map(([name, ft]) => `${name}: ${this.zeroValueForType(ft)}`)
|
|
@@ -1367,7 +2702,14 @@ export const expressionGenMethods = {
|
|
|
1367
2702
|
return `{ ${fields} }`;
|
|
1368
2703
|
}
|
|
1369
2704
|
case "named":
|
|
1370
|
-
return
|
|
2705
|
+
return (
|
|
2706
|
+
this._genericStructNew(t.name, t.typeArgs) ??
|
|
2707
|
+
this._zeroForNamedType(t.name)
|
|
2708
|
+
);
|
|
2709
|
+
case "typeParam": {
|
|
2710
|
+
const factory = this._typeParamZeroFactory(t.name);
|
|
2711
|
+
return factory ? `${factory}()` : "null";
|
|
2712
|
+
}
|
|
1371
2713
|
default:
|
|
1372
2714
|
return "null";
|
|
1373
2715
|
}
|
|
@@ -1376,45 +2718,59 @@ export const expressionGenMethods = {
|
|
|
1376
2718
|
// Emit a JS boolean expression that checks whether `val` matches type node `t`.
|
|
1377
2719
|
_typeCheckExpr(typeNode, val) {
|
|
1378
2720
|
if (!typeNode) return "true";
|
|
1379
|
-
if (typeNode.kind === "TypeName")
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
if (name === "error")
|
|
1386
|
-
return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
|
|
1387
|
-
if (this.structNames.has(name))
|
|
1388
|
-
return `(${val} instanceof ${name} && ${val}.__p !== true)`;
|
|
1389
|
-
return "true"; // unknown type — can't check at runtime
|
|
1390
|
-
}
|
|
2721
|
+
if (typeNode.kind === "TypeName")
|
|
2722
|
+
return this._typeNameCheckExpr(typeNode.name, val);
|
|
2723
|
+
const generic = this._genericInstanceCheck(typeNode, val);
|
|
2724
|
+
if (generic) return generic;
|
|
2725
|
+
const extended = this._namedOrIfaceCheckExpr(typeNode, val);
|
|
2726
|
+
if (extended) return extended;
|
|
1391
2727
|
if (
|
|
1392
2728
|
typeNode.kind === "PointerType" &&
|
|
1393
2729
|
typeNode.base?.kind === "TypeName" &&
|
|
1394
|
-
this.structNames.has(typeNode.base.name)
|
|
2730
|
+
this.structNames.has(this._qualifiedStructName(typeNode.base.name))
|
|
1395
2731
|
) {
|
|
1396
|
-
const name = typeNode.base.name;
|
|
2732
|
+
const name = this._qualifiedStructName(typeNode.base.name);
|
|
1397
2733
|
return `(${val} instanceof ${name} && ${val}.__v !== true)`;
|
|
1398
2734
|
}
|
|
1399
2735
|
return "true";
|
|
1400
2736
|
},
|
|
1401
2737
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
2738
|
+
// `Stack[int]` / `*Stack[int]`: the class test plus the instantiation
|
|
2739
|
+
// recorded on the instance (an instance without one matches any).
|
|
2740
|
+
_genericInstanceCheck(typeNode, val) {
|
|
2741
|
+
const isPtr = typeNode.kind === "PointerType";
|
|
2742
|
+
const base = isPtr ? typeNode.base : typeNode;
|
|
2743
|
+
if (base?.kind !== "GenericTypeName" || !base._typeArgs) return null;
|
|
2744
|
+
const cls = this._dequalify(base.name);
|
|
2745
|
+
if (!this.structNames.has(cls)) return null;
|
|
2746
|
+
const generic = this.checker?.types.get(base.name)?._generic;
|
|
2747
|
+
const id = instanceId(base._typeArgs);
|
|
2748
|
+
let check = isPtr
|
|
2749
|
+
? `${val} instanceof ${cls} && ${val}.__v !== true`
|
|
2750
|
+
: `${val} instanceof ${cls} && ${val}.__p !== true`;
|
|
2751
|
+
if (id !== null && generic) {
|
|
2752
|
+
this.useHelper("generics");
|
|
2753
|
+
const at = generic.typeParams.length;
|
|
2754
|
+
check += ` && (${val}[__tz]?.[${at}] ?? ${JSON.stringify(id)}) === ${JSON.stringify(id)}`;
|
|
1416
2755
|
}
|
|
1417
|
-
return
|
|
2756
|
+
return `(${check})`;
|
|
2757
|
+
},
|
|
2758
|
+
|
|
2759
|
+
zeroValueForExpr(expr) {
|
|
2760
|
+
return this.zeroValueForTypeNode(expr);
|
|
2761
|
+
},
|
|
2762
|
+
|
|
2763
|
+
_genSimdEquality(expr) {
|
|
2764
|
+
if (expr.op !== "==" && expr.op !== "!=") return null;
|
|
2765
|
+
if (!this._isSimdValueType(expr.left._type)) return null;
|
|
2766
|
+
this.useHelper("simd");
|
|
2767
|
+
const cmp = `__simd_equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
|
|
2768
|
+
return expr.op === "==" ? cmp : `!${cmp}`;
|
|
2769
|
+
},
|
|
2770
|
+
|
|
2771
|
+
_isSimdValueType(t) {
|
|
2772
|
+
const base = t?.kind === "named" ? t.underlying : t;
|
|
2773
|
+
return base?.kind === "simd";
|
|
1418
2774
|
},
|
|
1419
2775
|
|
|
1420
2776
|
// Returns true if the type is a struct or array (uses value comparison with __equal).
|
|
@@ -1453,12 +2809,30 @@ export const expressionGenMethods = {
|
|
|
1453
2809
|
)
|
|
1454
2810
|
return this._genComplexBinary(expr);
|
|
1455
2811
|
|
|
2812
|
+
const simdEq = this._genSimdEquality(expr);
|
|
2813
|
+
if (simdEq !== null) return simdEq;
|
|
2814
|
+
|
|
1456
2815
|
if (this._isStructEquality(expr)) {
|
|
1457
2816
|
this.useHelper("equal");
|
|
1458
2817
|
const cmp = `__equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
|
|
1459
2818
|
return expr.op === "==" ? cmp : `!${cmp}`;
|
|
1460
2819
|
}
|
|
1461
2820
|
|
|
2821
|
+
const nilMap = this._genNilMapCompare(expr);
|
|
2822
|
+
if (nilMap) return nilMap;
|
|
2823
|
+
|
|
2824
|
+
const tpEq = this._genTypeParamEquality(expr);
|
|
2825
|
+
if (tpEq) return tpEq;
|
|
2826
|
+
|
|
2827
|
+
if (this._isIfaceEquality(expr)) {
|
|
2828
|
+
this.useHelper("ifeq");
|
|
2829
|
+
const cmp = `__ifeq(${this._genIfaceOperand(expr.left)}, ${this._genIfaceOperand(expr.right)})`;
|
|
2830
|
+
return expr.op === "==" ? cmp : `!${cmp}`;
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
const ptrCmp = this._genPointerEquality(expr);
|
|
2834
|
+
if (ptrCmp !== null) return ptrCmp;
|
|
2835
|
+
|
|
1462
2836
|
if (this.strict && this._needsStrictOp(expr.op, expr._type)) {
|
|
1463
2837
|
const l = this.genExpr(expr.left);
|
|
1464
2838
|
const r = this.genExpr(expr.right);
|
|
@@ -1486,6 +2860,43 @@ export const expressionGenMethods = {
|
|
|
1486
2860
|
return `${l} ${jsOp} ${r}`;
|
|
1487
2861
|
},
|
|
1488
2862
|
|
|
2863
|
+
// `m == nil` / `m != nil` on a map: true for a nil map (null, the strict
|
|
2864
|
+
// sentinel, or an untouched js-package zero map), false for `map[K]V{}`.
|
|
2865
|
+
_nilMapOperand(expr) {
|
|
2866
|
+
if (expr.op !== "==" && expr.op !== "!=") return null;
|
|
2867
|
+
const other = this._isNilLiteral(expr.right)
|
|
2868
|
+
? expr.left
|
|
2869
|
+
: this._isNilLiteral(expr.left)
|
|
2870
|
+
? expr.right
|
|
2871
|
+
: null;
|
|
2872
|
+
if (!other) return null;
|
|
2873
|
+
const t =
|
|
2874
|
+
other._type?.kind === "named" ? other._type.underlying : other._type;
|
|
2875
|
+
return t?.kind === "map" ? other : null;
|
|
2876
|
+
},
|
|
2877
|
+
|
|
2878
|
+
_genNilMapCompare(expr) {
|
|
2879
|
+
const operand = this._nilMapOperand(expr);
|
|
2880
|
+
if (!operand) return null;
|
|
2881
|
+
this.useHelper(this.strict ? "nilMap" : "nm");
|
|
2882
|
+
const cmp = `__isnm(${this.genExpr(operand)})`;
|
|
2883
|
+
return expr.op === "==" ? cmp : `!${cmp}`;
|
|
2884
|
+
},
|
|
2885
|
+
|
|
2886
|
+
// `a == b` on type-parameter operands: __geq compares structs and arrays by value.
|
|
2887
|
+
_genTypeParamEquality(expr) {
|
|
2888
|
+
if (expr.op !== "==" && expr.op !== "!=") return null;
|
|
2889
|
+
const t = expr.left._type;
|
|
2890
|
+
if (t?.kind !== "typeParam" || expr.right._type?.kind !== "typeParam")
|
|
2891
|
+
return null;
|
|
2892
|
+
const factory = this._typeParamZeroFactory(t.name);
|
|
2893
|
+
if (!factory) return null;
|
|
2894
|
+
this.useHelper("equal");
|
|
2895
|
+
this.useHelper("geq");
|
|
2896
|
+
const cmp = `__geq(${factory}, ${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
|
|
2897
|
+
return expr.op === "==" ? cmp : `!${cmp}`;
|
|
2898
|
+
},
|
|
2899
|
+
|
|
1489
2900
|
_isStructEquality(expr) {
|
|
1490
2901
|
return (
|
|
1491
2902
|
(expr.op === "==" || expr.op === "!=") &&
|
|
@@ -1493,6 +2904,31 @@ export const expressionGenMethods = {
|
|
|
1493
2904
|
);
|
|
1494
2905
|
},
|
|
1495
2906
|
|
|
2907
|
+
_isIfaceType(t) {
|
|
2908
|
+
return isAny(t) || this._isInterfaceType(t);
|
|
2909
|
+
},
|
|
2910
|
+
|
|
2911
|
+
// Interface comparisons go through __ifeq so boxed named values compare by
|
|
2912
|
+
// dynamic type and value, as in Go (`err == ErrNotFound`).
|
|
2913
|
+
_isIfaceEquality(expr) {
|
|
2914
|
+
if (expr.op !== "==" && expr.op !== "!=") return false;
|
|
2915
|
+
if (this._isNilLiteral(expr.left) || this._isNilLiteral(expr.right))
|
|
2916
|
+
return false;
|
|
2917
|
+
return (
|
|
2918
|
+
this._isIfaceType(expr.left._type) || this._isIfaceType(expr.right._type)
|
|
2919
|
+
);
|
|
2920
|
+
},
|
|
2921
|
+
|
|
2922
|
+
// A concrete operand compared with an interface is converted to an interface first.
|
|
2923
|
+
_genIfaceOperand(e) {
|
|
2924
|
+
if (e._ifaceBox || this._isIfaceType(e._type)) return this.genExpr(e);
|
|
2925
|
+
const isPtr = e._type?.kind === "pointer";
|
|
2926
|
+
const named = isPtr ? e._type.base : e._type;
|
|
2927
|
+
if (this._namedBoxInfo(named))
|
|
2928
|
+
return this._genNamedIfaceBox(this.genExpr(e), named, isPtr);
|
|
2929
|
+
return this.genExpr(e);
|
|
2930
|
+
},
|
|
2931
|
+
|
|
1496
2932
|
_isIntDivision(expr) {
|
|
1497
2933
|
return (
|
|
1498
2934
|
expr.op === "/" &&
|
|
@@ -1504,7 +2940,7 @@ export const expressionGenMethods = {
|
|
|
1504
2940
|
_isNilLiteral(expr) {
|
|
1505
2941
|
return (
|
|
1506
2942
|
(expr.kind === "Ident" && expr.name === "nil") ||
|
|
1507
|
-
(expr.kind === "BasicLit" && expr.
|
|
2943
|
+
(expr.kind === "BasicLit" && expr.litKind === "NIL")
|
|
1508
2944
|
);
|
|
1509
2945
|
},
|
|
1510
2946
|
|
|
@@ -1518,6 +2954,8 @@ export const expressionGenMethods = {
|
|
|
1518
2954
|
)
|
|
1519
2955
|
return null;
|
|
1520
2956
|
if (this._isStructEquality(expr) || this._isIntDivision(expr)) return null;
|
|
2957
|
+
if (this._nilMapOperand(expr)) return null;
|
|
2958
|
+
if (this._isIfaceEquality(expr)) return null;
|
|
1521
2959
|
if (expr.op === "&^") return "&";
|
|
1522
2960
|
if (expr.op === "==" || expr.op === "!=") {
|
|
1523
2961
|
if (this._isNilLiteral(expr.left) || this._isNilLiteral(expr.right))
|
|
@@ -1527,9 +2965,19 @@ export const expressionGenMethods = {
|
|
|
1527
2965
|
return expr.op;
|
|
1528
2966
|
},
|
|
1529
2967
|
|
|
2968
|
+
// `T(x)` with T a type parameter: converts by the instantiated type.
|
|
2969
|
+
_genTypeParamConversion(name, inner) {
|
|
2970
|
+
const factory = name ? this._typeParamZeroFactory(name) : null;
|
|
2971
|
+
if (!factory) return null;
|
|
2972
|
+
this.useHelper("generics");
|
|
2973
|
+
return `__tconv(${factory}, ${inner})`;
|
|
2974
|
+
},
|
|
2975
|
+
|
|
1530
2976
|
_genTypeConversion(expr) {
|
|
1531
2977
|
const inner = this.genExpr(expr.expr);
|
|
1532
2978
|
const t = expr.targetType;
|
|
2979
|
+
const viaParam = this._genTypeParamConversion(t?.name, inner);
|
|
2980
|
+
if (viaParam) return viaParam;
|
|
1533
2981
|
if (t?.name === "complex128" || t?.name === "complex64") {
|
|
1534
2982
|
return isComplex(expr.expr._type) ? inner : `{ re: ${inner}, im: 0 }`;
|
|
1535
2983
|
}
|
|
@@ -1538,12 +2986,25 @@ export const expressionGenMethods = {
|
|
|
1538
2986
|
if (t?.kind === "SliceType")
|
|
1539
2987
|
return this._genSliceTypeConversion(expr, inner, t);
|
|
1540
2988
|
const target = t?.name;
|
|
2989
|
+
const structConv = this._genStructClassConversion(
|
|
2990
|
+
target,
|
|
2991
|
+
expr.expr._type,
|
|
2992
|
+
inner,
|
|
2993
|
+
);
|
|
2994
|
+
if (structConv) return structConv;
|
|
1541
2995
|
if (target === "error") {
|
|
2996
|
+
// A boxed named value that implements error is already an error.
|
|
2997
|
+
if (this._isNamedBoxKind(expr.expr)) return inner;
|
|
1542
2998
|
this.useHelper("error");
|
|
1543
2999
|
return `__error(${inner})`;
|
|
1544
3000
|
}
|
|
1545
|
-
if (target && this.namedWrapperNames.has(target))
|
|
1546
|
-
|
|
3001
|
+
if (target && this.namedWrapperNames.has(target)) {
|
|
3002
|
+
const named = this.checker?.types.get(target);
|
|
3003
|
+
const u = named?.underlying;
|
|
3004
|
+
return u?.kind === "basic"
|
|
3005
|
+
? this._genPrimitiveConversion(expr, inner, u.name)
|
|
3006
|
+
: inner;
|
|
3007
|
+
}
|
|
1547
3008
|
return this._genPrimitiveConversion(expr, inner, target);
|
|
1548
3009
|
},
|
|
1549
3010
|
|
|
@@ -1556,14 +3017,19 @@ export const expressionGenMethods = {
|
|
|
1556
3017
|
const size = t.size?.value !== undefined ? Number(t.size.value) : t.size;
|
|
1557
3018
|
return `${inner}.slice(0, ${size})`;
|
|
1558
3019
|
}
|
|
1559
|
-
|
|
3020
|
+
// Array-to-array conversion yields a new array value.
|
|
3021
|
+
return this._cloneJs(expr.expr._type, inner);
|
|
1560
3022
|
},
|
|
1561
3023
|
|
|
1562
3024
|
_genSliceTypeConversion(expr, inner, t) {
|
|
3025
|
+
// `[]T(nil)` is the nil slice, not a copy of anything.
|
|
3026
|
+
if (this._isNilLiteral(expr.expr)) return "null";
|
|
1563
3027
|
const elem = t.elem?.name;
|
|
1564
3028
|
if (elem === "byte" || elem === "uint8") {
|
|
1565
3029
|
if (this._isStringExprType(expr.expr._type))
|
|
1566
3030
|
return `new TextEncoder().encode(${inner})`;
|
|
3031
|
+
if (isAny(expr.expr._type) || !expr.expr._type)
|
|
3032
|
+
return `(typeof ${inner} === "string" ? new TextEncoder().encode(${inner}) : new Uint8Array(${inner}))`;
|
|
1567
3033
|
return `new Uint8Array(${inner})`;
|
|
1568
3034
|
}
|
|
1569
3035
|
const typedCtor = typedArrayConstructorForElem(
|
|
@@ -1581,10 +3047,10 @@ export const expressionGenMethods = {
|
|
|
1581
3047
|
},
|
|
1582
3048
|
|
|
1583
3049
|
_genStringConversion(srcType, inner) {
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
if (
|
|
1587
|
-
const elem =
|
|
3050
|
+
const base = srcType?.kind === "named" ? srcType.underlying : srcType;
|
|
3051
|
+
if (base && this.isIntType(base)) return `String.fromCodePoint(${inner})`;
|
|
3052
|
+
if (base?.kind === "slice") {
|
|
3053
|
+
const elem = base.elem?.name;
|
|
1588
3054
|
if (elem === "byte" || elem === "uint8")
|
|
1589
3055
|
return `new TextDecoder().decode(new Uint8Array(${inner}))`;
|
|
1590
3056
|
if (elem === "rune" || elem === "int" || elem === "int32")
|
|
@@ -1643,6 +3109,8 @@ export const expressionGenMethods = {
|
|
|
1643
3109
|
_needsStrictOp(op, type) {
|
|
1644
3110
|
if (!STRICT_OPS.has(op)) return false;
|
|
1645
3111
|
if (this._isStrictAtomicOp(op, this._typeName(type))) return true;
|
|
3112
|
+
if (op === "<<" || op === ">>")
|
|
3113
|
+
return WIDE_INT_NAMES.has(this._typeName(type));
|
|
1646
3114
|
return this.isIntType(type) && (op === "/" || op === "%");
|
|
1647
3115
|
},
|
|
1648
3116
|
|
|
@@ -1692,7 +3160,10 @@ export const expressionGenMethods = {
|
|
|
1692
3160
|
if (tName === "int32" || tName === "rune" || tName === "uint32") {
|
|
1693
3161
|
const signed = tName !== "uint32";
|
|
1694
3162
|
const cast = signed ? "| 0" : ">>> 0";
|
|
1695
|
-
if (
|
|
3163
|
+
if (op === "*")
|
|
3164
|
+
return signed
|
|
3165
|
+
? `Math.imul(${l}, ${r})`
|
|
3166
|
+
: `(Math.imul(${l}, ${r}) >>> 0)`;
|
|
1696
3167
|
if (op === "/")
|
|
1697
3168
|
return `((${r}) === 0 ? ${divZero} : (Math.trunc((${l}) / (${r})) ${cast}))`;
|
|
1698
3169
|
if (op === "%")
|
|
@@ -1700,9 +3171,9 @@ export const expressionGenMethods = {
|
|
|
1700
3171
|
if (op === "<<" || op === ">>") {
|
|
1701
3172
|
const ovf = signed && op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0";
|
|
1702
3173
|
const jsOp = !signed && op === ">>" ? ">>>" : op;
|
|
1703
|
-
return `((${r}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast}`;
|
|
3174
|
+
return `(((${r}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast})`;
|
|
1704
3175
|
}
|
|
1705
|
-
if (op === "&^") return `((${l}) & ~(${r})) ${cast}`;
|
|
3176
|
+
if (op === "&^") return `(((${l}) & ~(${r})) ${cast})`;
|
|
1706
3177
|
return `((${l} ${op} ${r}) ${cast})`;
|
|
1707
3178
|
}
|
|
1708
3179
|
if (tName === "int8" || tName === "int16") {
|
|
@@ -1732,6 +3203,14 @@ export const expressionGenMethods = {
|
|
|
1732
3203
|
if (op === "&^") return `(((${l}) & ~(${r})) & ${mask})`;
|
|
1733
3204
|
return `((${l} ${op} ${r}) & ${mask})`;
|
|
1734
3205
|
}
|
|
3206
|
+
if (op === "<<" || op === ">>") {
|
|
3207
|
+
// 64-bit semantics: operand first, then count (Go order); a negative
|
|
3208
|
+
// count panics and the result wraps to 64 bits.
|
|
3209
|
+
this.useHelper("shift");
|
|
3210
|
+
const unsigned = this._typeName(type).startsWith("u");
|
|
3211
|
+
if (op === "<<") return `__shl64(${l}, ${r}, ${unsigned})`;
|
|
3212
|
+
return `__shr64(${l}, ${r}, ${unsigned})`;
|
|
3213
|
+
}
|
|
1735
3214
|
if (this.isIntType(type)) {
|
|
1736
3215
|
if (op === "/")
|
|
1737
3216
|
return `((${r}) === 0 ? ${divZero} : Math.trunc((${l}) / (${r})))`;
|