gofront 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/package.json +8 -37
- package/src/backend/js/expressions.js +1341 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +375 -57
- package/src/backend/js/runtime.js +1460 -133
- package/src/backend/js/statements.js +900 -86
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +2 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +8 -3
- package/src/backend/js/stdlib/json.js +247 -52
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +10 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +436 -80
- package/src/backend/wasm/emit-builtins.js +303 -13
- package/src/backend/wasm/emit-exprs.js +1397 -203
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +512 -77
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +0 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +200 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +107 -269
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +176 -32
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +69 -9
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +176 -17
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +931 -122
- package/src/typechecker/index.js +459 -67
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +451 -56
- package/src/typechecker/statements.js +90 -9
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +322 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +149 -25
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
|
@@ -4,17 +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,
|
|
10
14
|
isAny,
|
|
11
15
|
isComplex,
|
|
12
16
|
isTypedArraySlice,
|
|
13
17
|
typedArrayConstructorForElem,
|
|
18
|
+
typeStr,
|
|
14
19
|
} from "../../typechecker/types.js";
|
|
15
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
|
+
|
|
16
40
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
17
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
|
+
|
|
18
83
|
const STRICT_NUMERIC_TYPES = new Set([
|
|
19
84
|
"float32",
|
|
20
85
|
"int8",
|
|
@@ -27,6 +92,25 @@ const STRICT_NUMERIC_TYPES = new Set([
|
|
|
27
92
|
"uint32",
|
|
28
93
|
]);
|
|
29
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
|
+
|
|
30
114
|
const STRICT_OPS = new Set([
|
|
31
115
|
"+",
|
|
32
116
|
"-",
|
|
@@ -41,6 +125,21 @@ const STRICT_OPS = new Set([
|
|
|
41
125
|
">>",
|
|
42
126
|
]);
|
|
43
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
|
+
|
|
44
143
|
const NAMED_ZERO_TABLE = {
|
|
45
144
|
"time.Time": [null, "{ _d: new Date(0), _isZero: true }"],
|
|
46
145
|
"strings.Builder": [null, '{ _buf: "" }'],
|
|
@@ -58,6 +157,48 @@ const NAMED_ZERO_TABLE = {
|
|
|
58
157
|
"encoding/base64.Encoding": ["base64", "__base64_std"],
|
|
59
158
|
"simd.F32x4": [null, "Object.freeze([0, 0, 0, 0])"],
|
|
60
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",
|
|
61
202
|
};
|
|
62
203
|
|
|
63
204
|
// Splits a generated JS lvalue at its outermost trailing access:
|
|
@@ -131,6 +272,9 @@ const NS_CONSTANTS = {
|
|
|
131
272
|
ErrSyntax: "__strconv_err_syntax",
|
|
132
273
|
ErrRange: "__strconv_err_range",
|
|
133
274
|
},
|
|
275
|
+
bits: {
|
|
276
|
+
UintSize: "64",
|
|
277
|
+
},
|
|
134
278
|
path: {
|
|
135
279
|
ErrBadPattern: "__path_err_bad_pattern",
|
|
136
280
|
},
|
|
@@ -164,7 +308,12 @@ const NS_CONSTANTS = {
|
|
|
164
308
|
rand: {
|
|
165
309
|
Reader: "__crypto_rand_reader",
|
|
166
310
|
},
|
|
167
|
-
os: {
|
|
311
|
+
os: {
|
|
312
|
+
Args: "process.argv",
|
|
313
|
+
Stdout: "__os_stdout",
|
|
314
|
+
Stderr: "__os_stderr",
|
|
315
|
+
Stdin: "__os_stdin",
|
|
316
|
+
},
|
|
168
317
|
time: {
|
|
169
318
|
Nanosecond: "1",
|
|
170
319
|
Microsecond: "1000",
|
|
@@ -258,6 +407,14 @@ const INT_TYPE_NAMES = new Set([
|
|
|
258
407
|
"rune",
|
|
259
408
|
]);
|
|
260
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
|
+
|
|
261
418
|
// Zero-value JS literals for basic Go types.
|
|
262
419
|
const ZERO_FOR_BASIC = {
|
|
263
420
|
int: "0",
|
|
@@ -288,18 +445,38 @@ const TYPEOF_NUMBER_NAMES = new Set([...INT_TYPE_NAMES, "float32", "float64"]);
|
|
|
288
445
|
const BUILTIN_GEN = {
|
|
289
446
|
append: (s, e) => s.genAppend(e),
|
|
290
447
|
len: (s, e) => s._genBuiltinLen(e),
|
|
291
|
-
cap: (s, e) =>
|
|
448
|
+
cap: (s, e) => {
|
|
449
|
+
s.useHelper("slice");
|
|
450
|
+
return `__cap(${s.genExpr(e.args[0])})`;
|
|
451
|
+
},
|
|
292
452
|
make: (s, e) => s.genMake(e),
|
|
293
453
|
delete: (s, e) => {
|
|
294
454
|
const [m, k] = e.args.map((a) => s.genExpr(a));
|
|
295
455
|
return `(delete ${m}[${k}])`;
|
|
296
456
|
},
|
|
297
457
|
copy: (s, e) => {
|
|
298
|
-
const
|
|
299
|
-
|
|
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})`;
|
|
300
472
|
},
|
|
301
473
|
new: (s, e) => {
|
|
302
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
|
+
}
|
|
303
480
|
const typeName = arg.name ?? s.getTypeName(arg);
|
|
304
481
|
if (typeName && s.structNames.has(typeName)) {
|
|
305
482
|
return `new ${typeName}()`;
|
|
@@ -309,7 +486,12 @@ const BUILTIN_GEN = {
|
|
|
309
486
|
const name = t.name ?? (t.kind === "named" ? t.name : null);
|
|
310
487
|
if (name) return `new ${name}()`;
|
|
311
488
|
}
|
|
312
|
-
|
|
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} }`;
|
|
313
495
|
},
|
|
314
496
|
// `print` writes without a trailing newline (Go semantics); the wasm glue
|
|
315
497
|
// buffers the same way, so output parity holds across backends.
|
|
@@ -326,8 +508,8 @@ const BUILTIN_GEN = {
|
|
|
326
508
|
s.useHelper("error");
|
|
327
509
|
return `__error(${s.genExpr(e.args[0])})`;
|
|
328
510
|
},
|
|
329
|
-
min: (s, e) =>
|
|
330
|
-
max: (s, e) =>
|
|
511
|
+
min: (s, e) => genMinMax(s, e, "min", "<"),
|
|
512
|
+
max: (s, e) => genMinMax(s, e, "max", ">"),
|
|
331
513
|
clear: (s, e) => s._genBuiltinClear(e),
|
|
332
514
|
complex: (s, e) =>
|
|
333
515
|
`{ re: ${s.genExpr(e.args[0])}, im: ${s.genExpr(e.args[1])} }`,
|
|
@@ -335,6 +517,53 @@ const BUILTIN_GEN = {
|
|
|
335
517
|
imag: (s, e) => `(${s.genExpr(e.args[0])}).im`,
|
|
336
518
|
};
|
|
337
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
|
+
|
|
338
567
|
// Method-name dispatch for genExpr — string values add no static call edges.
|
|
339
568
|
const EXPR_GEN_DELEGATE = {
|
|
340
569
|
UnaryExpr: "_genUnaryExpr",
|
|
@@ -350,36 +579,78 @@ const EXPR_GEN_DELEGATE = {
|
|
|
350
579
|
};
|
|
351
580
|
|
|
352
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
|
+
|
|
353
591
|
export const expressionGenMethods = {
|
|
354
592
|
genExpr(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
|
+
}
|
|
355
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
|
+
}
|
|
356
634
|
if (expr._ifaceBox && !expr._ifaceBoxing && !this._isNamedBoxKind(expr)) {
|
|
357
635
|
expr._ifaceBoxing = true;
|
|
358
636
|
try {
|
|
359
637
|
this.useHelper("ifaceBox");
|
|
360
|
-
|
|
361
|
-
expr
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
: expr._ifaceBox === "ptr_map"
|
|
366
|
-
? "__ifpm"
|
|
367
|
-
: expr._ifaceBox === "map"
|
|
368
|
-
? "__ifm"
|
|
369
|
-
: expr._ifaceBox === "slice"
|
|
370
|
-
? "__ifs"
|
|
371
|
-
: "__ifv";
|
|
372
|
-
const extra = expr._ifacePtrFields?.length
|
|
373
|
-
? `, ${JSON.stringify(expr._ifacePtrFields)}`
|
|
374
|
-
: "";
|
|
375
|
-
return `${fn}(${this.genExpr(expr)}${extra})`;
|
|
638
|
+
return this._ifaceBoxWrap(
|
|
639
|
+
this.genExpr(expr),
|
|
640
|
+
expr._ifaceBox,
|
|
641
|
+
expr._ifacePtrFields,
|
|
642
|
+
);
|
|
376
643
|
} finally {
|
|
377
644
|
expr._ifaceBoxing = false;
|
|
378
645
|
}
|
|
379
646
|
}
|
|
647
|
+
const folded = this._genFoldedConst(expr);
|
|
648
|
+
if (folded !== null) return folded;
|
|
380
649
|
switch (expr.kind) {
|
|
381
650
|
case "BasicLit":
|
|
382
651
|
if (expr.litKind === "STRING") return JSON.stringify(expr.value);
|
|
652
|
+
if (expr._const?.k === "INT")
|
|
653
|
+
this._warnConstPrecision(expr._const, expr);
|
|
383
654
|
return expr.value; // int, float, bool, nil(null)
|
|
384
655
|
case "ImagLit":
|
|
385
656
|
return `{ re: 0, im: ${expr.value} }`;
|
|
@@ -390,6 +661,7 @@ export const expressionGenMethods = {
|
|
|
390
661
|
case "AwaitExpr":
|
|
391
662
|
return `await ${this.genExpr(expr.expr)}`;
|
|
392
663
|
case "InstantiationExpr":
|
|
664
|
+
return this._genInstantiationExpr(expr);
|
|
393
665
|
case "RangeExpr":
|
|
394
666
|
return this.genExpr(expr.expr);
|
|
395
667
|
default: {
|
|
@@ -400,6 +672,57 @@ export const expressionGenMethods = {
|
|
|
400
672
|
}
|
|
401
673
|
},
|
|
402
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
|
+
|
|
403
726
|
_isNamedBoxKind(expr) {
|
|
404
727
|
return expr._ifaceBox === "named" || expr._ifaceBox === "named_ptr";
|
|
405
728
|
},
|
|
@@ -444,6 +767,29 @@ export const expressionGenMethods = {
|
|
|
444
767
|
return this._isStructType(pt.base);
|
|
445
768
|
},
|
|
446
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
|
+
|
|
447
793
|
_isStructExpr(expr) {
|
|
448
794
|
if (!expr) return false;
|
|
449
795
|
if (expr.kind === "CompositeLit") {
|
|
@@ -480,7 +826,20 @@ export const expressionGenMethods = {
|
|
|
480
826
|
return null;
|
|
481
827
|
},
|
|
482
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
|
+
|
|
483
840
|
_cloneJs(t, js) {
|
|
841
|
+
const tp = this._typeParamClone(t, js);
|
|
842
|
+
if (tp) return tp;
|
|
484
843
|
const base = this._aggregateBase(t);
|
|
485
844
|
if (!base) return js;
|
|
486
845
|
if (base.kind === "struct") {
|
|
@@ -516,7 +875,12 @@ export const expressionGenMethods = {
|
|
|
516
875
|
genValueExpr(expr) {
|
|
517
876
|
const js = this.genExpr(expr);
|
|
518
877
|
if (expr._ifaceBox) return js;
|
|
519
|
-
if (
|
|
878
|
+
if (
|
|
879
|
+
!(
|
|
880
|
+
this._aggregateBase(expr._type) || this._typeParamClone(expr._type, js)
|
|
881
|
+
) ||
|
|
882
|
+
!this._isAddressableExpr(expr)
|
|
883
|
+
)
|
|
520
884
|
return js;
|
|
521
885
|
return this._cloneJs(expr._type, js);
|
|
522
886
|
},
|
|
@@ -541,16 +905,26 @@ export const expressionGenMethods = {
|
|
|
541
905
|
|
|
542
906
|
_withFnCtx(body, fn) {
|
|
543
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);
|
|
544
914
|
this._fnCtx = {
|
|
545
915
|
body,
|
|
546
916
|
mut: new Map(),
|
|
547
917
|
owned: new Set(),
|
|
548
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),
|
|
549
922
|
};
|
|
550
923
|
try {
|
|
551
924
|
fn();
|
|
552
925
|
} finally {
|
|
553
926
|
this._fnCtx = prev;
|
|
927
|
+
this._functionLabels = prevLabels;
|
|
554
928
|
}
|
|
555
929
|
},
|
|
556
930
|
|
|
@@ -568,6 +942,15 @@ export const expressionGenMethods = {
|
|
|
568
942
|
// Copies aggregate params/receivers that the function body mutates.
|
|
569
943
|
_emitParamCopies(params) {
|
|
570
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
|
+
}
|
|
571
954
|
if (p.variadic || !p.name || p.name === "_") continue;
|
|
572
955
|
if (this._boxedVars.has(p.name)) {
|
|
573
956
|
// Address-taken parameter: rebind it to a { value } box.
|
|
@@ -621,12 +1004,26 @@ export const expressionGenMethods = {
|
|
|
621
1004
|
) {
|
|
622
1005
|
return this.genExpr(expr.operand);
|
|
623
1006
|
}
|
|
624
|
-
if (
|
|
625
|
-
expr.operand.
|
|
626
|
-
expr.operand.
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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})`;
|
|
630
1027
|
}
|
|
631
1028
|
// For non-boxed vars (primitives, etc.), wrap in { value: x }
|
|
632
1029
|
return `{ value: ${this.genExpr(expr.operand)} }`;
|
|
@@ -634,7 +1031,11 @@ export const expressionGenMethods = {
|
|
|
634
1031
|
// Dereference: *p
|
|
635
1032
|
if (op === "*") {
|
|
636
1033
|
if (this._isStructPointerType(expr.operand._type)) {
|
|
637
|
-
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)})`;
|
|
638
1039
|
}
|
|
639
1040
|
return `${this.genExpr(expr.operand)}.value`;
|
|
640
1041
|
}
|
|
@@ -645,6 +1046,16 @@ export const expressionGenMethods = {
|
|
|
645
1046
|
return inner;
|
|
646
1047
|
}
|
|
647
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})`;
|
|
648
1059
|
const needsParens =
|
|
649
1060
|
this._emittedBinaryOp(expr.operand) !== null ||
|
|
650
1061
|
((op === "-" || op === "+") && /^[-+]/.test(code));
|
|
@@ -652,6 +1063,9 @@ export const expressionGenMethods = {
|
|
|
652
1063
|
},
|
|
653
1064
|
|
|
654
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);
|
|
655
1069
|
if (expr._isMethodExpr) {
|
|
656
1070
|
const namedExpr = this._namedMethodExprInfo(expr);
|
|
657
1071
|
if (namedExpr) return `${namedExpr.cls}.$${expr.field}`;
|
|
@@ -679,19 +1093,104 @@ export const expressionGenMethods = {
|
|
|
679
1093
|
this.useHelper("cryptoRand");
|
|
680
1094
|
} else if (expr.expr.name === "sha256") {
|
|
681
1095
|
this.useHelper("sha256");
|
|
1096
|
+
} else if (expr.expr.name === "os") {
|
|
1097
|
+
this.useHelper("os");
|
|
682
1098
|
}
|
|
683
1099
|
return nsConst;
|
|
684
1100
|
}
|
|
685
1101
|
}
|
|
686
1102
|
const base = this.genExpr(expr.expr);
|
|
687
|
-
|
|
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;
|
|
688
1107
|
if (expr.expr._type?.kind === "pointer" && expr.field !== "value")
|
|
689
1108
|
return this._genPointerSelectorField(expr, base);
|
|
690
|
-
const sel = `${base}.${this._jsMemberName(expr)}`;
|
|
691
|
-
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
|
+
}
|
|
692
1117
|
return sel;
|
|
693
1118
|
},
|
|
694
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
|
+
|
|
695
1194
|
// PascalCase aliases of Web API methods (gl.ClearColor) map to the real camelCase member.
|
|
696
1195
|
_jsMemberName(expr) {
|
|
697
1196
|
const f = expr.field;
|
|
@@ -733,7 +1232,12 @@ export const expressionGenMethods = {
|
|
|
733
1232
|
? `${this.genExpr(sel.expr)}.value`
|
|
734
1233
|
: this.genExpr(sel.expr);
|
|
735
1234
|
}
|
|
736
|
-
return {
|
|
1235
|
+
return {
|
|
1236
|
+
cls: info.cls,
|
|
1237
|
+
recv,
|
|
1238
|
+
byValue: !method._ptrRecv,
|
|
1239
|
+
recvType: viaPtr ? recvType.base : recvType,
|
|
1240
|
+
};
|
|
737
1241
|
},
|
|
738
1242
|
|
|
739
1243
|
// `x.M` used as a value: binds the receiver now, as Go does.
|
|
@@ -741,7 +1245,11 @@ export const expressionGenMethods = {
|
|
|
741
1245
|
if (!expr._isMethodValue || expr._callee) return undefined;
|
|
742
1246
|
const target = this._namedMethodTarget(expr);
|
|
743
1247
|
if (!target) return undefined;
|
|
744
|
-
|
|
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})`;
|
|
745
1253
|
},
|
|
746
1254
|
|
|
747
1255
|
_genAddressOfExpr(operand) {
|
|
@@ -776,17 +1284,90 @@ export const expressionGenMethods = {
|
|
|
776
1284
|
return `${target.cls}.$${sel.field}(${args ? `${target.recv}, ${args}` : target.recv})`;
|
|
777
1285
|
},
|
|
778
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
|
+
|
|
779
1300
|
_genCallArgs(expr) {
|
|
780
1301
|
if (expr._multiForward) return `...${this.genExpr(expr.args[0])}`;
|
|
781
1302
|
return expr.args
|
|
782
|
-
.map((a) =>
|
|
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
|
+
)
|
|
783
1310
|
.join(", ");
|
|
784
1311
|
},
|
|
785
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
|
+
|
|
786
1354
|
_genPointerSelectorField(expr, base) {
|
|
787
1355
|
if (this._isStructPointerType(expr.expr._type)) {
|
|
788
|
-
|
|
789
|
-
|
|
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
|
+
}
|
|
790
1371
|
return sel;
|
|
791
1372
|
}
|
|
792
1373
|
const sel = `${base}.value.${expr.field}`;
|
|
@@ -802,13 +1383,48 @@ export const expressionGenMethods = {
|
|
|
802
1383
|
? `${this.genExpr(expr.expr)}.${wrapField}`
|
|
803
1384
|
: this.genExpr(expr.expr);
|
|
804
1385
|
const idx = this.genExpr(expr.index);
|
|
805
|
-
if (expr._mapValueType
|
|
806
|
-
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;
|
|
807
1392
|
if (this._isStringExprType(exprType) && !expr._lvalue)
|
|
808
1393
|
return `${base}.charCodeAt(${idx})`;
|
|
809
1394
|
return `${base}[${idx}]`;
|
|
810
1395
|
},
|
|
811
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
|
+
|
|
812
1428
|
_genMapIndexExpr(expr, base, idx) {
|
|
813
1429
|
const zero = this.zeroValueForType(expr._mapValueType);
|
|
814
1430
|
if (this._hasCallExpr(expr.expr) || this._hasCallExpr(expr.index))
|
|
@@ -826,17 +1442,26 @@ export const expressionGenMethods = {
|
|
|
826
1442
|
|
|
827
1443
|
_genSliceExpr(expr) {
|
|
828
1444
|
const base = this.genExpr(expr.expr);
|
|
829
|
-
const lo = expr.low ? this.genExpr(expr.low) : "";
|
|
830
|
-
const hi = expr.high ? this.genExpr(expr.high) : "";
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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})`;
|
|
836
1461
|
},
|
|
837
1462
|
|
|
838
1463
|
_genFuncLit(expr) {
|
|
839
|
-
const params = expr.params
|
|
1464
|
+
const params = this._paramNames(expr.params).join(", ");
|
|
840
1465
|
const asyncPrefix = expr.async ? "async " : "";
|
|
841
1466
|
const saved = this.out;
|
|
842
1467
|
const prevBoxed = this._boxedVars;
|
|
@@ -864,9 +1489,7 @@ export const expressionGenMethods = {
|
|
|
864
1489
|
const check = this._typeCheckExpr(expr.type, val);
|
|
865
1490
|
const wrap = (body) => (hoist ? `((__v) => ${body})(${src})` : body);
|
|
866
1491
|
// Named non-struct values sit in interfaces as boxes; the asserted value is raw.
|
|
867
|
-
const out = this.
|
|
868
|
-
? `${val}.__gfUnbox()`
|
|
869
|
-
: val;
|
|
1492
|
+
const out = this._assertedValueJs(expr.type, val);
|
|
870
1493
|
if (!expr._commaOk) {
|
|
871
1494
|
// plain assertion: panic if check fails (matches Go behavior)
|
|
872
1495
|
if (check === "true") return src; // can't check at runtime — pass through
|
|
@@ -874,16 +1497,35 @@ export const expressionGenMethods = {
|
|
|
874
1497
|
`(${check} ? ${out} : (() => { throw new Error("interface conversion: type assertion failed"); })())`,
|
|
875
1498
|
);
|
|
876
1499
|
}
|
|
877
|
-
// 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.
|
|
878
1502
|
const zero = this.zeroValueForTypeNode(expr.type);
|
|
879
|
-
|
|
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])`);
|
|
880
1506
|
},
|
|
881
1507
|
|
|
882
|
-
|
|
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);
|
|
883
1520
|
if (TYPEOF_NUMBER_NAMES.has(name)) return `typeof ${val} === "number"`;
|
|
884
1521
|
if (name === "string") return `typeof ${val} === "string"`;
|
|
885
1522
|
if (name === "bool") return `typeof ${val} === "boolean"`;
|
|
886
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")`;
|
|
887
1529
|
if (name === "error")
|
|
888
1530
|
return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
|
|
889
1531
|
if (this.structNames.has(name))
|
|
@@ -893,7 +1535,11 @@ export const expressionGenMethods = {
|
|
|
893
1535
|
if (namedInfo) return `(${val} instanceof ${namedInfo.cls})`;
|
|
894
1536
|
// User-declared interfaces: check the dynamic value has every method.
|
|
895
1537
|
if (!name.includes(".") && named?.underlying?.kind === "interface")
|
|
896
|
-
return this._ifaceMethodCheck(
|
|
1538
|
+
return this._ifaceMethodCheck(
|
|
1539
|
+
val,
|
|
1540
|
+
[...named.underlying.methods.keys()],
|
|
1541
|
+
this._unwrapIsMulti(named.underlying.methods.get("Unwrap")),
|
|
1542
|
+
);
|
|
897
1543
|
return "true"; // unknown type — can't check at runtime
|
|
898
1544
|
},
|
|
899
1545
|
|
|
@@ -903,6 +1549,9 @@ export const expressionGenMethods = {
|
|
|
903
1549
|
return this._ifaceMethodCheck(
|
|
904
1550
|
val,
|
|
905
1551
|
(typeNode.methods ?? []).map((m) => m.name),
|
|
1552
|
+
this._unwrapIsMulti(
|
|
1553
|
+
(typeNode.methods ?? []).find((m) => m.name === "Unwrap"),
|
|
1554
|
+
),
|
|
906
1555
|
);
|
|
907
1556
|
if (typeNode.kind === "PointerType" && typeNode.base?.kind === "TypeName") {
|
|
908
1557
|
const info = this._namedBoxInfo(
|
|
@@ -913,11 +1562,37 @@ export const expressionGenMethods = {
|
|
|
913
1562
|
return null;
|
|
914
1563
|
},
|
|
915
1564
|
|
|
916
|
-
|
|
917
|
-
|
|
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
|
+
);
|
|
918
1581
|
return `(${[`${val} != null`, ...checks].join(" && ")})`;
|
|
919
1582
|
},
|
|
920
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
|
+
|
|
921
1596
|
_isNamedBoxTypeNode(typeNode) {
|
|
922
1597
|
if (typeNode?.kind === "TypeName")
|
|
923
1598
|
return Boolean(
|
|
@@ -930,9 +1605,31 @@ export const expressionGenMethods = {
|
|
|
930
1605
|
return false;
|
|
931
1606
|
},
|
|
932
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)})`;
|
|
1620
|
+
},
|
|
1621
|
+
|
|
933
1622
|
_genTypeConversionCall(expr) {
|
|
934
1623
|
const targetType = expr._conversionTargetType;
|
|
935
1624
|
const arg = this.genExpr(expr.args[0]);
|
|
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;
|
|
936
1633
|
const isRaw =
|
|
937
1634
|
targetType?.name === "json.RawMessage" ||
|
|
938
1635
|
targetType?.name === "encoding/json.RawMessage";
|
|
@@ -979,14 +1676,50 @@ export const expressionGenMethods = {
|
|
|
979
1676
|
if (result !== undefined) return result;
|
|
980
1677
|
}
|
|
981
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
|
+
|
|
982
1688
|
expr.func._callee = true;
|
|
983
1689
|
const rawFn = this.genExpr(expr.func);
|
|
984
1690
|
const fn = expr.func.kind === "FuncLit" ? `(${rawFn})` : rawFn;
|
|
985
|
-
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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(", ");
|
|
990
1723
|
},
|
|
991
1724
|
|
|
992
1725
|
_isErrorInterfaceType(t) {
|
|
@@ -1008,7 +1741,35 @@ export const expressionGenMethods = {
|
|
|
1008
1741
|
return recvType?.name ?? recvType?.base?.name;
|
|
1009
1742
|
},
|
|
1010
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
|
+
|
|
1011
1770
|
_genReceiverTypeCall(recvName, expr) {
|
|
1771
|
+
const jsonStream = this._genJsonStreamCall(recvName, expr);
|
|
1772
|
+
if (jsonStream !== undefined) return jsonStream;
|
|
1012
1773
|
if (recvName === "strings.Builder" || recvName === "bytes.Buffer")
|
|
1013
1774
|
return this._genBuilderCall(recvName, expr.func.field, expr);
|
|
1014
1775
|
if (recvName === "regexp.Regexp")
|
|
@@ -1017,10 +1778,39 @@ export const expressionGenMethods = {
|
|
|
1017
1778
|
return this._genTimeMethodCall(expr.func.field, expr);
|
|
1018
1779
|
if (recvName === "time.Duration")
|
|
1019
1780
|
return this._genDurationMethodCall(expr.func.field, expr);
|
|
1020
|
-
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
|
+
)
|
|
1021
1806
|
return this._genTestingMethodCall(expr.func.field, expr);
|
|
1022
1807
|
if (recvName === "strings.Replacer") {
|
|
1023
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
|
+
}
|
|
1024
1814
|
const arg = this.genExpr(expr.args[0]);
|
|
1025
1815
|
return `(${recv} ? ${recv}.Replace(${arg}) : ${arg})`;
|
|
1026
1816
|
}
|
|
@@ -1028,12 +1818,13 @@ export const expressionGenMethods = {
|
|
|
1028
1818
|
const args = expr.args.map((a) => this.genExpr(a)).join(", ");
|
|
1029
1819
|
return `${this.genExpr(expr.func.expr)}.subarray(${args})`;
|
|
1030
1820
|
}
|
|
1031
|
-
if (
|
|
1032
|
-
return this._genSimdReceiverCall(expr);
|
|
1821
|
+
if (/^(simd\.)?(F32x4|Mask32x4)$/.test(recvName ?? "")) {
|
|
1822
|
+
return this._genSimdReceiverCall(expr, recvName.endsWith("Mask32x4"));
|
|
1033
1823
|
}
|
|
1034
1824
|
if (recvName === "sync.Mutex" || recvName === "sync.RWMutex") {
|
|
1035
1825
|
if (expr.func.field === "TryLock" || expr.func.field === "TryRLock")
|
|
1036
1826
|
return "true";
|
|
1827
|
+
if (expr.func.field === "RLocker") return "({ Lock() {}, Unlock() {} })";
|
|
1037
1828
|
return "(void 0)";
|
|
1038
1829
|
}
|
|
1039
1830
|
if (recvName === "sync.Once" && expr.func.field === "Do") {
|
|
@@ -1076,33 +1867,15 @@ export const expressionGenMethods = {
|
|
|
1076
1867
|
return undefined;
|
|
1077
1868
|
},
|
|
1078
1869
|
|
|
1079
|
-
_genSimdReceiverCall(expr) {
|
|
1870
|
+
_genSimdReceiverCall(expr, isMask = false) {
|
|
1080
1871
|
this.useHelper("simd");
|
|
1081
1872
|
const method = expr.func.field;
|
|
1082
1873
|
const recv = this.genExpr(expr.func.expr);
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
Min: "__simd_min",
|
|
1089
|
-
Max: "__simd_max",
|
|
1090
|
-
Dot3: "__simd_dot3",
|
|
1091
|
-
Dot4: "__simd_dot4",
|
|
1092
|
-
};
|
|
1093
|
-
if (helpers[method]) {
|
|
1094
|
-
return `${helpers[method]}(${recv}, ${this.genExpr(expr.args[0])})`;
|
|
1095
|
-
}
|
|
1096
|
-
if (method === "Lane") {
|
|
1097
|
-
return `${recv}[${this.genExpr(expr.args[0])}]`;
|
|
1098
|
-
}
|
|
1099
|
-
if (method === "WithLane") {
|
|
1100
|
-
return `__simd_with_lane(${recv}, ${this.genExpr(expr.args[0])}, ${this.genExpr(expr.args[1])})`;
|
|
1101
|
-
}
|
|
1102
|
-
if (method === "Splat") {
|
|
1103
|
-
return `__simd_splat(${this.genExpr(expr.args[0])})`;
|
|
1104
|
-
}
|
|
1105
|
-
return undefined;
|
|
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;
|
|
1106
1879
|
},
|
|
1107
1880
|
|
|
1108
1881
|
_genSelectorCall(expr) {
|
|
@@ -1164,7 +1937,12 @@ export const expressionGenMethods = {
|
|
|
1164
1937
|
(t?.kind === "named" && t.underlying?.kind === "map")
|
|
1165
1938
|
)
|
|
1166
1939
|
return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
|
|
1167
|
-
|
|
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})`;
|
|
1168
1946
|
},
|
|
1169
1947
|
|
|
1170
1948
|
_genBuiltinIdent(name, expr) {
|
|
@@ -1172,6 +1950,15 @@ export const expressionGenMethods = {
|
|
|
1172
1950
|
return gen ? gen(this, expr) : undefined;
|
|
1173
1951
|
},
|
|
1174
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
|
+
|
|
1175
1962
|
genAppend(expr) {
|
|
1176
1963
|
const retType = expr._type;
|
|
1177
1964
|
const wrapField = this._namedWrapperField(retType, expr.args[0]);
|
|
@@ -1181,16 +1968,25 @@ export const expressionGenMethods = {
|
|
|
1181
1968
|
const elems = expr.args
|
|
1182
1969
|
.slice(1)
|
|
1183
1970
|
.map((a) =>
|
|
1184
|
-
a._spread
|
|
1971
|
+
a._spread
|
|
1972
|
+
? `...(${this._genSliceSource(a)} ?? [])`
|
|
1973
|
+
: this.genValueExpr(a),
|
|
1185
1974
|
);
|
|
1186
1975
|
if (elems.length === 0) return this.genExpr(expr.args[0]);
|
|
1187
1976
|
this.useHelper("append");
|
|
1188
1977
|
return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
|
|
1189
1978
|
}
|
|
1190
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)} ?? [])`;
|
|
1191
1987
|
const elems = expr.args
|
|
1192
1988
|
.slice(1)
|
|
1193
|
-
.map((a) => (a._spread ?
|
|
1989
|
+
.map((a) => (a._spread ? spreadOf(a) : this.genValueExpr(a)));
|
|
1194
1990
|
if (elems.length === 0) return slice;
|
|
1195
1991
|
this.useHelper("append");
|
|
1196
1992
|
const sliceType = expr._type ?? expr.args[0]._type;
|
|
@@ -1209,12 +2005,16 @@ export const expressionGenMethods = {
|
|
|
1209
2005
|
// make(map[K]V) → {}
|
|
1210
2006
|
const typeArg = expr.args[0];
|
|
1211
2007
|
const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
|
|
1212
|
-
|
|
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;
|
|
1213
2014
|
if (typeNode.kind === "SliceType" || resolvedKind === "slice") {
|
|
1214
2015
|
const n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
|
|
1215
2016
|
const elemNode = typeNode.kind === "SliceType" ? typeNode.elem : null;
|
|
1216
|
-
const elemResolved =
|
|
1217
|
-
typeArg._type?.kind === "slice" ? typeArg._type.elem : null;
|
|
2017
|
+
const elemResolved = madeType?.kind === "slice" ? madeType.elem : null;
|
|
1218
2018
|
const typedCtor =
|
|
1219
2019
|
typedArrayConstructorForElem(elemResolved) ??
|
|
1220
2020
|
(elemNode?.name
|
|
@@ -1223,8 +2023,14 @@ export const expressionGenMethods = {
|
|
|
1223
2023
|
name: elemNode.name,
|
|
1224
2024
|
})
|
|
1225
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);
|
|
1226
2032
|
if (typedCtor) {
|
|
1227
|
-
return `new ${typedCtor}(${
|
|
2033
|
+
return withCap((k) => `new ${typedCtor}(${k})`);
|
|
1228
2034
|
}
|
|
1229
2035
|
// Use the proper zero value for the element type (e.g. new Point() not 0)
|
|
1230
2036
|
let zero = "null";
|
|
@@ -1234,10 +2040,10 @@ export const expressionGenMethods = {
|
|
|
1234
2040
|
zero = this.zeroValueForTypeNode(elemNode);
|
|
1235
2041
|
}
|
|
1236
2042
|
// If zero value is a constructor call, use a factory function so each element is distinct
|
|
1237
|
-
if (zero.startsWith("new ")) {
|
|
1238
|
-
return `Array.from({length: ${
|
|
2043
|
+
if (zero.startsWith("new ") || zero.includes("$z_")) {
|
|
2044
|
+
return withCap((k) => `Array.from({length: ${k}}, () => ${zero})`);
|
|
1239
2045
|
}
|
|
1240
|
-
return `new Array(${
|
|
2046
|
+
return withCap((k) => `new Array(${k}).fill(${zero})`);
|
|
1241
2047
|
}
|
|
1242
2048
|
// map or fallback
|
|
1243
2049
|
return "{}";
|
|
@@ -1267,22 +2073,42 @@ export const expressionGenMethods = {
|
|
|
1267
2073
|
return t?.kind === "MapType";
|
|
1268
2074
|
},
|
|
1269
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
|
+
|
|
1270
2085
|
_genMapLitEntries(elems) {
|
|
1271
2086
|
return elems
|
|
1272
|
-
.map((e) =>
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
: `[${this.genExpr(e.key)}]`;
|
|
1278
|
-
return `${k}: ${this.genValueExpr(e.value)}`;
|
|
1279
|
-
}
|
|
1280
|
-
return this.genValueExpr(e);
|
|
1281
|
-
})
|
|
2087
|
+
.map((e) =>
|
|
2088
|
+
e.kind === "KeyValueExpr"
|
|
2089
|
+
? `${this._genMapLitKey(e.key)}: ${this.genValueExpr(e.value)}`
|
|
2090
|
+
: this.genValueExpr(e),
|
|
2091
|
+
)
|
|
1282
2092
|
.join(", ");
|
|
1283
2093
|
},
|
|
1284
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
|
+
|
|
1285
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
|
+
}
|
|
1286
2112
|
if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
|
|
1287
2113
|
const fields = expr.elems
|
|
1288
2114
|
.map(
|
|
@@ -1323,7 +2149,7 @@ export const expressionGenMethods = {
|
|
|
1323
2149
|
(ce) => (ce._positionalField ?? ce.key?.name) === ef,
|
|
1324
2150
|
);
|
|
1325
2151
|
values[idx] = match
|
|
1326
|
-
? this.genValueExpr(match.value
|
|
2152
|
+
? this.genValueExpr(match.kind === "KeyValueExpr" ? match.value : match)
|
|
1327
2153
|
: fields[idx].zero;
|
|
1328
2154
|
} else {
|
|
1329
2155
|
const valExpr = this.genExpr(e.value);
|
|
@@ -1364,6 +2190,8 @@ export const expressionGenMethods = {
|
|
|
1364
2190
|
|
|
1365
2191
|
_genPositionalStructLit(typeName, expr) {
|
|
1366
2192
|
const fields = this.getStructFields(typeName);
|
|
2193
|
+
const generic = this._genGenericStructLit(fields, expr);
|
|
2194
|
+
if (generic) return generic;
|
|
1367
2195
|
if (!fields || fields.length === 0 || expr.elems.length === 0) {
|
|
1368
2196
|
return `new ${typeName}()`;
|
|
1369
2197
|
}
|
|
@@ -1381,10 +2209,95 @@ export const expressionGenMethods = {
|
|
|
1381
2209
|
return inst;
|
|
1382
2210
|
},
|
|
1383
2211
|
|
|
1384
|
-
|
|
1385
|
-
|
|
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
|
|
1386
2293
|
.map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
|
|
1387
2294
|
.join(", ");
|
|
2295
|
+
},
|
|
2296
|
+
|
|
2297
|
+
_genSliceOrArrayLit(t, expr) {
|
|
2298
|
+
const sparse = this._genSparseArrayLit(expr);
|
|
2299
|
+
if (sparse) return sparse;
|
|
2300
|
+
const elems = this._genLitElems(expr);
|
|
1388
2301
|
const elemNode =
|
|
1389
2302
|
t.kind === "SliceType" || t.kind === "ArrayType" ? t.elem : null;
|
|
1390
2303
|
const elemResolved =
|
|
@@ -1413,6 +2326,8 @@ export const expressionGenMethods = {
|
|
|
1413
2326
|
return "{ _buf: [] }";
|
|
1414
2327
|
if (typeName === "strings.Builder" || typeName === "Builder")
|
|
1415
2328
|
return '{ _buf: "" }';
|
|
2329
|
+
if (typeName === "simd.Mask32x4" || typeName === "Mask32x4")
|
|
2330
|
+
return "Object.freeze([false, false, false, false])";
|
|
1416
2331
|
if (typeName === "simd.F32x4" || typeName === "F32x4") {
|
|
1417
2332
|
this.useHelper("simd");
|
|
1418
2333
|
const a = expr.elems[0] ? this.genExpr(expr.elems[0]) : "0";
|
|
@@ -1421,6 +2336,8 @@ export const expressionGenMethods = {
|
|
|
1421
2336
|
const d = expr.elems[3] ? this.genExpr(expr.elems[3]) : "0";
|
|
1422
2337
|
return `__simd_f32x4(${a}, ${b}, ${c}, ${d})`;
|
|
1423
2338
|
}
|
|
2339
|
+
const aliasLit = this._genAliasLit(t, typeName, expr);
|
|
2340
|
+
if (aliasLit !== null) return aliasLit;
|
|
1424
2341
|
if (typeName && this.structNames.has(typeName))
|
|
1425
2342
|
return this._genPositionalStructLit(typeName, expr);
|
|
1426
2343
|
if (typeName && this.namedWrapperNames.has(typeName))
|
|
@@ -1434,14 +2351,68 @@ export const expressionGenMethods = {
|
|
|
1434
2351
|
return this._genFallbackCompositeLit(expr);
|
|
1435
2352
|
},
|
|
1436
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
|
+
|
|
1437
2402
|
_genImplicitCompositeLit(expr) {
|
|
1438
|
-
|
|
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;
|
|
1439
2406
|
if (typeName && this.structNames.has(typeName)) {
|
|
1440
2407
|
return this._genPositionalStructLit(typeName, expr);
|
|
1441
2408
|
}
|
|
2409
|
+
if (this._isMapLit(expr))
|
|
2410
|
+
return `{ ${this._genMapLitEntries(expr.elems)} }`;
|
|
1442
2411
|
const hasPositional = expr.elems.some((e) => e._positionalField);
|
|
1443
2412
|
const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
|
|
1444
|
-
|
|
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)) {
|
|
1445
2416
|
const fields = expr.elems
|
|
1446
2417
|
.map((e) => {
|
|
1447
2418
|
if (e._positionalField)
|
|
@@ -1451,7 +2422,9 @@ export const expressionGenMethods = {
|
|
|
1451
2422
|
.join(", ");
|
|
1452
2423
|
return `{ ${fields} }`;
|
|
1453
2424
|
}
|
|
1454
|
-
const
|
|
2425
|
+
const sparse = this._genSparseArrayLit(expr);
|
|
2426
|
+
if (sparse) return sparse;
|
|
2427
|
+
const elems = this._genLitElems(expr);
|
|
1455
2428
|
if (isTypedArraySlice(expr._type)) {
|
|
1456
2429
|
const typedCtor = typedArrayConstructorForElem(expr._type.elem);
|
|
1457
2430
|
if (typedCtor) return `new ${typedCtor}([${elems}])`;
|
|
@@ -1465,11 +2438,8 @@ export const expressionGenMethods = {
|
|
|
1465
2438
|
if (u?.kind === "map") {
|
|
1466
2439
|
return `{ ${this._genMapLitEntries(expr.elems)} }`;
|
|
1467
2440
|
}
|
|
1468
|
-
// Default: slice
|
|
1469
|
-
|
|
1470
|
-
.map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
|
|
1471
|
-
.join(", ");
|
|
1472
|
-
return `[${elems}]`;
|
|
2441
|
+
// Default: slice or array
|
|
2442
|
+
return `[${this._genLitElems(expr)}]`;
|
|
1473
2443
|
},
|
|
1474
2444
|
|
|
1475
2445
|
// ── Helpers ───────────────────────────────────────────────────
|
|
@@ -1481,6 +2451,7 @@ export const expressionGenMethods = {
|
|
|
1481
2451
|
|
|
1482
2452
|
getTypeName(typeNode) {
|
|
1483
2453
|
if (!typeNode) return null;
|
|
2454
|
+
if (typeNode._stdName) return typeNode._stdName;
|
|
1484
2455
|
if (typeNode.kind === "TypeName") return this._dequalify(typeNode.name);
|
|
1485
2456
|
if (typeNode.kind === "GenericTypeName") return typeNode.name;
|
|
1486
2457
|
if (typeNode.kind === "Ident") return typeNode.name;
|
|
@@ -1523,6 +2494,93 @@ export const expressionGenMethods = {
|
|
|
1523
2494
|
return false;
|
|
1524
2495
|
},
|
|
1525
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
|
+
|
|
1526
2584
|
// Returns the JS zero-value literal for a basic type name, or null if not a basic type.
|
|
1527
2585
|
_zeroForBasicName(name) {
|
|
1528
2586
|
return Object.hasOwn(ZERO_FOR_BASIC, name) ? ZERO_FOR_BASIC[name] : null;
|
|
@@ -1543,6 +2601,10 @@ export const expressionGenMethods = {
|
|
|
1543
2601
|
u.kind !== "named"
|
|
1544
2602
|
)
|
|
1545
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
|
+
}
|
|
1546
2608
|
name = this._dequalify(name);
|
|
1547
2609
|
if (this.structNames.has(name)) return `new ${name}()`;
|
|
1548
2610
|
return "null";
|
|
@@ -1551,7 +2613,10 @@ export const expressionGenMethods = {
|
|
|
1551
2613
|
zeroValueForTypeNode(typeNode) {
|
|
1552
2614
|
if (!typeNode) return "null";
|
|
1553
2615
|
switch (typeNode.kind) {
|
|
2616
|
+
case "Ident":
|
|
1554
2617
|
case "TypeName": {
|
|
2618
|
+
const factory = this._typeParamZeroFactory(typeNode.name);
|
|
2619
|
+
if (factory) return `${factory}()`;
|
|
1555
2620
|
const basic = this._zeroForBasicName(typeNode.name);
|
|
1556
2621
|
if (basic !== null) return basic;
|
|
1557
2622
|
return this._zeroForNamedType(typeNode.name);
|
|
@@ -1573,7 +2638,7 @@ export const expressionGenMethods = {
|
|
|
1573
2638
|
);
|
|
1574
2639
|
}
|
|
1575
2640
|
case "MapType":
|
|
1576
|
-
return
|
|
2641
|
+
return this._nilMapZero();
|
|
1577
2642
|
case "PointerType":
|
|
1578
2643
|
return "null";
|
|
1579
2644
|
case "StructType": {
|
|
@@ -1586,16 +2651,31 @@ export const expressionGenMethods = {
|
|
|
1586
2651
|
.join(", ");
|
|
1587
2652
|
return `{ ${fields} }`;
|
|
1588
2653
|
}
|
|
2654
|
+
case "GenericTypeName":
|
|
2655
|
+
case "InstantiationExpr":
|
|
2656
|
+
return (
|
|
2657
|
+
this._genericStructNew(
|
|
2658
|
+
this.getTypeName(typeNode),
|
|
2659
|
+
typeNode._typeArgs,
|
|
2660
|
+
) ?? "null"
|
|
2661
|
+
);
|
|
1589
2662
|
default:
|
|
1590
2663
|
return "null";
|
|
1591
2664
|
}
|
|
1592
2665
|
},
|
|
1593
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
|
+
|
|
1594
2674
|
_arrayZero(n, ctor, elemZero) {
|
|
1595
2675
|
if (ctor) return `new ${ctor}(${n})`;
|
|
1596
2676
|
if (n === 0) return "[]";
|
|
1597
|
-
if (/^(new |\{|\[|Array)/.test(elemZero))
|
|
1598
|
-
return `Array.from({ length: ${n} }, () => ${elemZero})`;
|
|
2677
|
+
if (/^(new |\{|\[|Array|__nm\()|\$z_/.test(elemZero))
|
|
2678
|
+
return `Array.from({ length: ${n} }, () => (${elemZero}))`;
|
|
1599
2679
|
return `new Array(${n}).fill(${elemZero})`;
|
|
1600
2680
|
},
|
|
1601
2681
|
|
|
@@ -1614,7 +2694,7 @@ export const expressionGenMethods = {
|
|
|
1614
2694
|
this.zeroValueForType(t.elem),
|
|
1615
2695
|
);
|
|
1616
2696
|
case "map":
|
|
1617
|
-
return
|
|
2697
|
+
return this._nilMapZero();
|
|
1618
2698
|
case "struct": {
|
|
1619
2699
|
const fields = [...t.fields.entries()]
|
|
1620
2700
|
.map(([name, ft]) => `${name}: ${this.zeroValueForType(ft)}`)
|
|
@@ -1622,7 +2702,14 @@ export const expressionGenMethods = {
|
|
|
1622
2702
|
return `{ ${fields} }`;
|
|
1623
2703
|
}
|
|
1624
2704
|
case "named":
|
|
1625
|
-
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
|
+
}
|
|
1626
2713
|
default:
|
|
1627
2714
|
return "null";
|
|
1628
2715
|
}
|
|
@@ -1633,35 +2720,57 @@ export const expressionGenMethods = {
|
|
|
1633
2720
|
if (!typeNode) return "true";
|
|
1634
2721
|
if (typeNode.kind === "TypeName")
|
|
1635
2722
|
return this._typeNameCheckExpr(typeNode.name, val);
|
|
2723
|
+
const generic = this._genericInstanceCheck(typeNode, val);
|
|
2724
|
+
if (generic) return generic;
|
|
1636
2725
|
const extended = this._namedOrIfaceCheckExpr(typeNode, val);
|
|
1637
2726
|
if (extended) return extended;
|
|
1638
2727
|
if (
|
|
1639
2728
|
typeNode.kind === "PointerType" &&
|
|
1640
2729
|
typeNode.base?.kind === "TypeName" &&
|
|
1641
|
-
this.structNames.has(typeNode.base.name)
|
|
2730
|
+
this.structNames.has(this._qualifiedStructName(typeNode.base.name))
|
|
1642
2731
|
) {
|
|
1643
|
-
const name = typeNode.base.name;
|
|
2732
|
+
const name = this._qualifiedStructName(typeNode.base.name);
|
|
1644
2733
|
return `(${val} instanceof ${name} && ${val}.__v !== true)`;
|
|
1645
2734
|
}
|
|
1646
2735
|
return "true";
|
|
1647
2736
|
},
|
|
1648
2737
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
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)}`;
|
|
1663
2755
|
}
|
|
1664
|
-
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";
|
|
1665
2774
|
},
|
|
1666
2775
|
|
|
1667
2776
|
// Returns true if the type is a struct or array (uses value comparison with __equal).
|
|
@@ -1700,18 +2809,30 @@ export const expressionGenMethods = {
|
|
|
1700
2809
|
)
|
|
1701
2810
|
return this._genComplexBinary(expr);
|
|
1702
2811
|
|
|
2812
|
+
const simdEq = this._genSimdEquality(expr);
|
|
2813
|
+
if (simdEq !== null) return simdEq;
|
|
2814
|
+
|
|
1703
2815
|
if (this._isStructEquality(expr)) {
|
|
1704
2816
|
this.useHelper("equal");
|
|
1705
2817
|
const cmp = `__equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
|
|
1706
2818
|
return expr.op === "==" ? cmp : `!${cmp}`;
|
|
1707
2819
|
}
|
|
1708
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
|
+
|
|
1709
2827
|
if (this._isIfaceEquality(expr)) {
|
|
1710
2828
|
this.useHelper("ifeq");
|
|
1711
2829
|
const cmp = `__ifeq(${this._genIfaceOperand(expr.left)}, ${this._genIfaceOperand(expr.right)})`;
|
|
1712
2830
|
return expr.op === "==" ? cmp : `!${cmp}`;
|
|
1713
2831
|
}
|
|
1714
2832
|
|
|
2833
|
+
const ptrCmp = this._genPointerEquality(expr);
|
|
2834
|
+
if (ptrCmp !== null) return ptrCmp;
|
|
2835
|
+
|
|
1715
2836
|
if (this.strict && this._needsStrictOp(expr.op, expr._type)) {
|
|
1716
2837
|
const l = this.genExpr(expr.left);
|
|
1717
2838
|
const r = this.genExpr(expr.right);
|
|
@@ -1739,6 +2860,43 @@ export const expressionGenMethods = {
|
|
|
1739
2860
|
return `${l} ${jsOp} ${r}`;
|
|
1740
2861
|
},
|
|
1741
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
|
+
|
|
1742
2900
|
_isStructEquality(expr) {
|
|
1743
2901
|
return (
|
|
1744
2902
|
(expr.op === "==" || expr.op === "!=") &&
|
|
@@ -1782,7 +2940,7 @@ export const expressionGenMethods = {
|
|
|
1782
2940
|
_isNilLiteral(expr) {
|
|
1783
2941
|
return (
|
|
1784
2942
|
(expr.kind === "Ident" && expr.name === "nil") ||
|
|
1785
|
-
(expr.kind === "BasicLit" && expr.
|
|
2943
|
+
(expr.kind === "BasicLit" && expr.litKind === "NIL")
|
|
1786
2944
|
);
|
|
1787
2945
|
},
|
|
1788
2946
|
|
|
@@ -1796,6 +2954,7 @@ export const expressionGenMethods = {
|
|
|
1796
2954
|
)
|
|
1797
2955
|
return null;
|
|
1798
2956
|
if (this._isStructEquality(expr) || this._isIntDivision(expr)) return null;
|
|
2957
|
+
if (this._nilMapOperand(expr)) return null;
|
|
1799
2958
|
if (this._isIfaceEquality(expr)) return null;
|
|
1800
2959
|
if (expr.op === "&^") return "&";
|
|
1801
2960
|
if (expr.op === "==" || expr.op === "!=") {
|
|
@@ -1806,9 +2965,19 @@ export const expressionGenMethods = {
|
|
|
1806
2965
|
return expr.op;
|
|
1807
2966
|
},
|
|
1808
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
|
+
|
|
1809
2976
|
_genTypeConversion(expr) {
|
|
1810
2977
|
const inner = this.genExpr(expr.expr);
|
|
1811
2978
|
const t = expr.targetType;
|
|
2979
|
+
const viaParam = this._genTypeParamConversion(t?.name, inner);
|
|
2980
|
+
if (viaParam) return viaParam;
|
|
1812
2981
|
if (t?.name === "complex128" || t?.name === "complex64") {
|
|
1813
2982
|
return isComplex(expr.expr._type) ? inner : `{ re: ${inner}, im: 0 }`;
|
|
1814
2983
|
}
|
|
@@ -1817,7 +2986,15 @@ export const expressionGenMethods = {
|
|
|
1817
2986
|
if (t?.kind === "SliceType")
|
|
1818
2987
|
return this._genSliceTypeConversion(expr, inner, t);
|
|
1819
2988
|
const target = t?.name;
|
|
2989
|
+
const structConv = this._genStructClassConversion(
|
|
2990
|
+
target,
|
|
2991
|
+
expr.expr._type,
|
|
2992
|
+
inner,
|
|
2993
|
+
);
|
|
2994
|
+
if (structConv) return structConv;
|
|
1820
2995
|
if (target === "error") {
|
|
2996
|
+
// A boxed named value that implements error is already an error.
|
|
2997
|
+
if (this._isNamedBoxKind(expr.expr)) return inner;
|
|
1821
2998
|
this.useHelper("error");
|
|
1822
2999
|
return `__error(${inner})`;
|
|
1823
3000
|
}
|
|
@@ -1840,10 +3017,13 @@ export const expressionGenMethods = {
|
|
|
1840
3017
|
const size = t.size?.value !== undefined ? Number(t.size.value) : t.size;
|
|
1841
3018
|
return `${inner}.slice(0, ${size})`;
|
|
1842
3019
|
}
|
|
1843
|
-
|
|
3020
|
+
// Array-to-array conversion yields a new array value.
|
|
3021
|
+
return this._cloneJs(expr.expr._type, inner);
|
|
1844
3022
|
},
|
|
1845
3023
|
|
|
1846
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";
|
|
1847
3027
|
const elem = t.elem?.name;
|
|
1848
3028
|
if (elem === "byte" || elem === "uint8") {
|
|
1849
3029
|
if (this._isStringExprType(expr.expr._type))
|
|
@@ -1929,6 +3109,8 @@ export const expressionGenMethods = {
|
|
|
1929
3109
|
_needsStrictOp(op, type) {
|
|
1930
3110
|
if (!STRICT_OPS.has(op)) return false;
|
|
1931
3111
|
if (this._isStrictAtomicOp(op, this._typeName(type))) return true;
|
|
3112
|
+
if (op === "<<" || op === ">>")
|
|
3113
|
+
return WIDE_INT_NAMES.has(this._typeName(type));
|
|
1932
3114
|
return this.isIntType(type) && (op === "/" || op === "%");
|
|
1933
3115
|
},
|
|
1934
3116
|
|
|
@@ -1978,7 +3160,10 @@ export const expressionGenMethods = {
|
|
|
1978
3160
|
if (tName === "int32" || tName === "rune" || tName === "uint32") {
|
|
1979
3161
|
const signed = tName !== "uint32";
|
|
1980
3162
|
const cast = signed ? "| 0" : ">>> 0";
|
|
1981
|
-
if (
|
|
3163
|
+
if (op === "*")
|
|
3164
|
+
return signed
|
|
3165
|
+
? `Math.imul(${l}, ${r})`
|
|
3166
|
+
: `(Math.imul(${l}, ${r}) >>> 0)`;
|
|
1982
3167
|
if (op === "/")
|
|
1983
3168
|
return `((${r}) === 0 ? ${divZero} : (Math.trunc((${l}) / (${r})) ${cast}))`;
|
|
1984
3169
|
if (op === "%")
|
|
@@ -1986,9 +3171,9 @@ export const expressionGenMethods = {
|
|
|
1986
3171
|
if (op === "<<" || op === ">>") {
|
|
1987
3172
|
const ovf = signed && op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0";
|
|
1988
3173
|
const jsOp = !signed && op === ">>" ? ">>>" : op;
|
|
1989
|
-
return `((${r}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast}`;
|
|
3174
|
+
return `(((${r}) >= 32 ? ${ovf} : ((${l}) ${jsOp} (${r}))) ${cast})`;
|
|
1990
3175
|
}
|
|
1991
|
-
if (op === "&^") return `((${l}) & ~(${r})) ${cast}`;
|
|
3176
|
+
if (op === "&^") return `(((${l}) & ~(${r})) ${cast})`;
|
|
1992
3177
|
return `((${l} ${op} ${r}) ${cast})`;
|
|
1993
3178
|
}
|
|
1994
3179
|
if (tName === "int8" || tName === "int16") {
|
|
@@ -2018,6 +3203,14 @@ export const expressionGenMethods = {
|
|
|
2018
3203
|
if (op === "&^") return `(((${l}) & ~(${r})) & ${mask})`;
|
|
2019
3204
|
return `((${l} ${op} ${r}) & ${mask})`;
|
|
2020
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
|
+
}
|
|
2021
3214
|
if (this.isIntType(type)) {
|
|
2022
3215
|
if (op === "/")
|
|
2023
3216
|
return `((${r}) === 0 ? ${divZero} : Math.trunc((${l}) / (${r})))`;
|