gofront 1.8.0 → 1.9.1
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 +3 -3
- package/package.json +6 -5
- package/src/backend/js/expressions.js +1157 -408
- package/src/backend/js/index.js +327 -153
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +559 -2552
- package/src/backend/js/statements.js +670 -158
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +298 -19
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +91 -170
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +884 -256
- package/src/backend/wasm/emit-exprs.js +185 -77
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +524 -240
- package/src/backend/wasm/emit.js +176 -38
- package/src/backend/wasm/globals.js +48 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +44 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +299 -770
- package/src/backend/wasm/monomorph.js +139 -8
- package/src/backend/wasm/natives.js +600 -0
- package/src/backend/wasm/prescan.js +5 -153
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -32
- package/src/backend/wasm/types.js +97 -20
- package/src/backend/wasm/variadic.js +9 -4
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +113 -130
- package/src/lexer/lexer.js +106 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +355 -132
- package/src/link/type-names.js +204 -0
- package/src/link/wasm.js +30 -6
- package/src/lower/bounds.js +153 -0
- package/src/lower/captures.js +92 -33
- package/src/lower/embedding.js +62 -5
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +929 -0
- package/src/lower/functions.js +225 -7
- package/src/lower/gen-types.js +293 -0
- package/src/lower/index.js +14 -2
- package/src/lower/json-codecs.js +334 -245
- package/src/lower/ownership.js +25 -7
- package/src/lower/reachability.js +69 -51
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +30 -5
- package/src/parser/expressions.js +111 -57
- package/src/parser/statements.js +108 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +12 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +214 -19
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +156 -8
- package/src/typechecker/expressions.js +490 -108
- package/src/typechecker/index.js +346 -42
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +41 -0
- package/src/typechecker/resolve.js +490 -65
- package/src/typechecker/statements.js +288 -27
- package/src/typechecker/stdlib/excluded.js +9 -5
- package/src/typechecker/stdlib/index.js +25 -9
- package/src/typechecker/termination.js +10 -1
- package/src/typechecker/types.js +287 -575
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +171 -38
- package/stdlib/internal/jsonrt/enc.go +186 -36
- package/stdlib/internal/jsonrt/scanner.go +17 -8
- package/stdlib/io/io.go +157 -1
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +16 -14
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +391 -452
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +235 -50
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +141 -3
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/fmt-runtime.js +0 -573
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -119
- package/src/backend/js/stdlib/builder.js +0 -98
- package/src/backend/js/stdlib/bytes.js +0 -368
- package/src/backend/js/stdlib/errors.js +0 -45
- package/src/backend/js/stdlib/fmt.js +0 -517
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -1167
- package/src/backend/js/stdlib/maps.js +0 -37
- package/src/backend/js/stdlib/math.js +0 -85
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -34
- package/src/backend/js/stdlib/slices.js +0 -107
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -79
- package/src/backend/js/stdlib/strings.js +0 -301
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -41
- package/src/backend/js/stdlib/time.js +0 -152
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/stdlib/bytes.js +0 -6716
- package/src/backend/wasm/stdlib/core.js +0 -10278
- package/src/backend/wasm/stdlib/io.js +0 -3578
- package/src/backend/wasm/stdlib/packages.js +0 -2402
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -2229
- package/src/typechecker/stdlib/core.js +0 -1079
- package/src/typechecker/stdlib/extended.js +0 -1210
- package/stdlib/bytes/iter.go +0 -192
- package/stdlib/bytes/search.go +0 -240
- package/stdlib/bytes/split.go +0 -218
- package/stdlib/errors/overlay.go +0 -12
- package/stdlib/maps/iter.go +0 -65
- package/stdlib/slices/extra.go +0 -73
- package/stdlib/slices/iter.go +0 -113
- package/stdlib/strings/extra.go +0 -35
- package/stdlib/strings/iter.go +0 -122
- package/stdlib/sync/extra.go +0 -105
- package/stdlib/unicode/utf8/bytes.go +0 -239
package/src/lower/functions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/lower/functions.js
|
|
2
2
|
// Analyzes function signatures, named returns and defer structures.
|
|
3
3
|
|
|
4
|
-
import { forEachChild, someChild } from "./walk.js";
|
|
4
|
+
import { forEachChild, hasCallOrReceive, someChild } from "./walk.js";
|
|
5
5
|
|
|
6
6
|
export function extractNamedReturns(decl) {
|
|
7
7
|
const named = decl?.returnType?._namedReturns;
|
|
@@ -146,6 +146,80 @@ function deferCalleeMayRecover(funcExpr, ctx) {
|
|
|
146
146
|
return true;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
// A constant argument (`2.5`, `"a"`, `nil`, `k` for a constant `k`, `-1`)
|
|
150
|
+
// has no value to capture at the `defer` statement, so it stays in the
|
|
151
|
+
// deferred call, where it takes the callee's parameter type exactly as in a
|
|
152
|
+
// direct call. Hoisting it into a temporary would default its type (`float64`,
|
|
153
|
+
// `int`) and reject a defined-type, interface, pointer or generic parameter.
|
|
154
|
+
function isDeferConstant(arg) {
|
|
155
|
+
if (arg?.kind === "BasicLit" && arg.litKind === "NIL") return true;
|
|
156
|
+
if (arg?._type?.kind !== "untyped") return false;
|
|
157
|
+
if (arg._const) return true;
|
|
158
|
+
switch (arg.kind) {
|
|
159
|
+
case "BasicLit":
|
|
160
|
+
case "ImagLit":
|
|
161
|
+
return true;
|
|
162
|
+
case "ParenExpr":
|
|
163
|
+
return isDeferConstant(arg.expr);
|
|
164
|
+
case "UnaryExpr":
|
|
165
|
+
return isDeferConstant(arg.operand);
|
|
166
|
+
case "BinaryExpr":
|
|
167
|
+
return isDeferConstant(arg.left) && isDeferConstant(arg.right);
|
|
168
|
+
default:
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// A package-level basic or defined type a temporary can be declared with by
|
|
174
|
+
// its name; other parameter types (qualified, generic, composite) are not
|
|
175
|
+
// hoisted with a declared type.
|
|
176
|
+
function isNamedScalarParam(t) {
|
|
177
|
+
if (t?.kind === "basic")
|
|
178
|
+
return t.name !== "any" && /^[A-Za-z_]\w*$/.test(t.name ?? "");
|
|
179
|
+
return (
|
|
180
|
+
t?.kind === "named" &&
|
|
181
|
+
t.underlying?.kind === "basic" &&
|
|
182
|
+
!t.typeArgs?.length &&
|
|
183
|
+
/^[A-Za-z_]\w*$/.test(t.name ?? "")
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// A non-constant untyped expression (`1 << n`) the typechecker already gave
|
|
188
|
+
// its parameter's type: it is still untyped when the lowered code is checked
|
|
189
|
+
// again, so its temporary needs that type declared.
|
|
190
|
+
function isRetypedUntyped(e) {
|
|
191
|
+
if (!e || e._const) return false;
|
|
192
|
+
switch (e.kind) {
|
|
193
|
+
case "ParenExpr":
|
|
194
|
+
return isRetypedUntyped(e.expr);
|
|
195
|
+
case "UnaryExpr":
|
|
196
|
+
return isRetypedUntyped(e.operand);
|
|
197
|
+
case "BinaryExpr":
|
|
198
|
+
if (e.op === "<<" || e.op === ">>") return Boolean(e.left?._const);
|
|
199
|
+
return isRetypedUntyped(e.left) || isRetypedUntyped(e.right);
|
|
200
|
+
default:
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// The type of the temporary that holds deferred argument `a`: an untyped
|
|
206
|
+
// non-constant value (a shift, a comparison) takes its basic or defined
|
|
207
|
+
// scalar parameter's type, as it does in a direct call. A variadic call's
|
|
208
|
+
// trailing arguments take the element type.
|
|
209
|
+
function deferTempType(funcType, a, arg) {
|
|
210
|
+
const argType = arg._type;
|
|
211
|
+
if (argType?.kind !== "untyped" && !isRetypedUntyped(arg)) return argType;
|
|
212
|
+
const params = funcType?.params;
|
|
213
|
+
let param = params?.[a];
|
|
214
|
+
if (funcType?.variadic && a >= params.length - 1) {
|
|
215
|
+
// The last parameter's type is the element type (a slice only when
|
|
216
|
+
// a table spells it that way).
|
|
217
|
+
param = params[params.length - 1];
|
|
218
|
+
if (!isNamedScalarParam(param)) param = param?.elem;
|
|
219
|
+
}
|
|
220
|
+
return isNamedScalarParam(param) ? param : argType;
|
|
221
|
+
}
|
|
222
|
+
|
|
149
223
|
function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
|
|
150
224
|
if (stmt.call?.kind !== "CallExpr") return 0;
|
|
151
225
|
const call = stmt.call;
|
|
@@ -245,19 +319,40 @@ function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
|
|
|
245
319
|
lhs: tupleLhs,
|
|
246
320
|
rhs: [arg],
|
|
247
321
|
});
|
|
322
|
+
} else if (isDeferConstant(arg)) {
|
|
323
|
+
tempIdents.push(arg);
|
|
248
324
|
} else {
|
|
249
325
|
defargCounter++;
|
|
250
326
|
const tempName = `__defarg$${defargCounter}`;
|
|
327
|
+
const tempType = deferTempType(call.func?._type, a, arg);
|
|
251
328
|
tempIdents.push({
|
|
252
329
|
kind: "Ident",
|
|
253
330
|
name: tempName,
|
|
254
|
-
_type:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
kind: "DefineStmt",
|
|
258
|
-
lhs: [{ kind: "Ident", name: tempName, _type: arg._type }],
|
|
259
|
-
rhs: [arg],
|
|
331
|
+
_type: tempType,
|
|
332
|
+
// A spread argument stays one: the call gets the slice itself.
|
|
333
|
+
...(arg._spread && { _spread: true }),
|
|
260
334
|
});
|
|
335
|
+
tempStmts.push(
|
|
336
|
+
tempType === arg._type &&
|
|
337
|
+
!(isRetypedUntyped(arg) && isNamedScalarParam(tempType))
|
|
338
|
+
? {
|
|
339
|
+
kind: "DefineStmt",
|
|
340
|
+
lhs: [{ kind: "Ident", name: tempName, _type: tempType }],
|
|
341
|
+
rhs: [arg],
|
|
342
|
+
}
|
|
343
|
+
: {
|
|
344
|
+
// Declared with the parameter's type so the lowered code
|
|
345
|
+
// type-checks again with the constant converted.
|
|
346
|
+
kind: "VarDecl",
|
|
347
|
+
decls: [
|
|
348
|
+
{
|
|
349
|
+
names: [tempName],
|
|
350
|
+
type: { kind: "TypeName", name: tempType.name },
|
|
351
|
+
value: [arg],
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
},
|
|
355
|
+
);
|
|
261
356
|
}
|
|
262
357
|
}
|
|
263
358
|
}
|
|
@@ -271,6 +366,8 @@ function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
|
|
|
271
366
|
...call,
|
|
272
367
|
func: funcExpr,
|
|
273
368
|
args: callArgs,
|
|
369
|
+
// A deferred native call runs the Go body, as on the JS backend.
|
|
370
|
+
_noNative: true,
|
|
274
371
|
_multiForward: tempIdents.length > 0 ? false : call._multiForward,
|
|
275
372
|
};
|
|
276
373
|
const synthFuncLit = {
|
|
@@ -300,3 +397,124 @@ function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
|
|
|
300
397
|
|
|
301
398
|
return tempStmts.length;
|
|
302
399
|
}
|
|
400
|
+
|
|
401
|
+
let fwdCounter = 0;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Splits a multi-value call forwarded into interfaces into temps:
|
|
405
|
+
* `return f()` where some result is an interface becomes
|
|
406
|
+
* `__fwd$1, __fwd$2 := f(); return __fwd$1, __fwd$2`, and `g(f())` where
|
|
407
|
+
* some parameter is an interface becomes `…; g(__fwd$1, __fwd$2)`. The
|
|
408
|
+
* typechecker records per-element interface-box marks in `call._fwdIface`;
|
|
409
|
+
* each temp takes its element's marks, so both backends box it through their
|
|
410
|
+
* ordinary per-value path. Forwards with no interface element are left alone.
|
|
411
|
+
*
|
|
412
|
+
* `g(f())` is split only as the operand of a statement (expression
|
|
413
|
+
* statement, single-value `:=`/`=`/`var`/`return`) whose callee evaluates
|
|
414
|
+
* no call, so hoisting `f()` keeps Go's evaluation order.
|
|
415
|
+
*/
|
|
416
|
+
export function splitForwardedTuples(programOrPrograms) {
|
|
417
|
+
fwdCounter = 0;
|
|
418
|
+
const programs = Array.isArray(programOrPrograms)
|
|
419
|
+
? programOrPrograms
|
|
420
|
+
: [programOrPrograms];
|
|
421
|
+
for (const program of programs)
|
|
422
|
+
for (const d of program?.decls ?? []) splitForwardsIn(d);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function splitForwardsIn(node) {
|
|
426
|
+
if (!node || typeof node !== "object") return;
|
|
427
|
+
if (Array.isArray(node)) {
|
|
428
|
+
for (let i = 0; i < node.length; i++) {
|
|
429
|
+
const item = node[i];
|
|
430
|
+
splitForwardsIn(item);
|
|
431
|
+
const defines = splitForwardsInStmt(item);
|
|
432
|
+
node.splice(i, 0, ...defines);
|
|
433
|
+
i += defines.length;
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
forEachChild(node, splitForwardsIn);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// The `:=` statements to insert before `stmt` (in order) after rewriting its
|
|
441
|
+
// forwarded calls to read the temps.
|
|
442
|
+
function splitForwardsInStmt(stmt) {
|
|
443
|
+
const defines = [];
|
|
444
|
+
const operand = statementOperand(stmt);
|
|
445
|
+
if (
|
|
446
|
+
operand?.kind === "CallExpr" &&
|
|
447
|
+
operand._multiForward &&
|
|
448
|
+
operand.args?.length === 1 &&
|
|
449
|
+
!hasCallOrReceive(operand.func)
|
|
450
|
+
) {
|
|
451
|
+
const split = forwardTemps(operand.args[0]);
|
|
452
|
+
if (split) {
|
|
453
|
+
operand.args = split.operands;
|
|
454
|
+
operand._multiForward = false;
|
|
455
|
+
defines.push(split.define);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (stmt?.kind === "ReturnStmt") {
|
|
459
|
+
const define = splitForwardedReturn(stmt);
|
|
460
|
+
if (define) defines.push(define);
|
|
461
|
+
}
|
|
462
|
+
return defines;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// The single expression a statement evaluates last, or null.
|
|
466
|
+
function statementOperand(stmt) {
|
|
467
|
+
switch (stmt?.kind) {
|
|
468
|
+
case "ExprStmt":
|
|
469
|
+
return stmt.expr;
|
|
470
|
+
case "DefineStmt":
|
|
471
|
+
case "AssignStmt":
|
|
472
|
+
return stmt.rhs?.length === 1 &&
|
|
473
|
+
stmt.lhs?.every((e) => e.kind === "Ident")
|
|
474
|
+
? stmt.rhs[0]
|
|
475
|
+
: null;
|
|
476
|
+
case "ReturnStmt":
|
|
477
|
+
return stmt.values?.length === 1 ? stmt.values[0] : null;
|
|
478
|
+
case "VarDecl":
|
|
479
|
+
return stmt.decls?.length === 1 && stmt.decls[0].value?.length === 1
|
|
480
|
+
? stmt.decls[0].value[0]
|
|
481
|
+
: null;
|
|
482
|
+
default:
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Splits one forwarded call into temps: returns the `:=` statement defining
|
|
488
|
+
// them and the operand idents (carrying each element's interface-box marks),
|
|
489
|
+
// or null when the call needs no split.
|
|
490
|
+
function forwardTemps(call) {
|
|
491
|
+
const holders = call?._fwdIface;
|
|
492
|
+
const types = call?._type?.types;
|
|
493
|
+
if (!holders || !Array.isArray(types) || types.length !== holders.length)
|
|
494
|
+
return null;
|
|
495
|
+
const names = types.map(() => `__fwd$${++fwdCounter}`);
|
|
496
|
+
const define = {
|
|
497
|
+
kind: "DefineStmt",
|
|
498
|
+
lhs: names.map((name, i) => ({ kind: "Ident", name, _type: types[i] })),
|
|
499
|
+
rhs: [call],
|
|
500
|
+
};
|
|
501
|
+
// The call is no longer a forward; a cached AST must not split it again.
|
|
502
|
+
call._fwdIface = undefined;
|
|
503
|
+
const operands = names.map((name, i) => ({
|
|
504
|
+
kind: "Ident",
|
|
505
|
+
name,
|
|
506
|
+
_type: types[i],
|
|
507
|
+
...holders[i],
|
|
508
|
+
}));
|
|
509
|
+
return { define, operands };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function splitForwardedReturn(stmt) {
|
|
513
|
+
if (stmt.values?.length !== 1) return null;
|
|
514
|
+
const split = forwardTemps(stmt.values[0]);
|
|
515
|
+
if (!split) return null;
|
|
516
|
+
stmt.values = split.operands;
|
|
517
|
+
if (Array.isArray(stmt._types))
|
|
518
|
+
stmt._types = split.operands.map((o) => o._type);
|
|
519
|
+
return split.define;
|
|
520
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// src/lower/gen-types.js
|
|
2
|
+
// Spells checked types as Go source for the generated per-package file
|
|
3
|
+
// (src/lower/json-codecs.js, src/lower/errors-as.js,
|
|
4
|
+
// src/lower/fmt-formatters.js), and holds what those generators share: the
|
|
5
|
+
// imports the spelled code needs and the enumeration of the program's types.
|
|
6
|
+
|
|
7
|
+
import { defaultType } from "../typechecker/types.js";
|
|
8
|
+
import { forEachChild } from "./walk.js";
|
|
9
|
+
|
|
10
|
+
export function under(t) {
|
|
11
|
+
return t?.kind === "named" ? under(t.underlying) : t;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const isEmptyIface = (t) =>
|
|
15
|
+
(t?.kind === "basic" && t.name === "any") ||
|
|
16
|
+
(under(t)?.kind === "interface" && under(t).methods?.size === 0);
|
|
17
|
+
|
|
18
|
+
// Arrays and maps of these share one WebAssembly representation (and so one
|
|
19
|
+
// type-switch case), so only literals may register them: a `[2]byte` conversion
|
|
20
|
+
// must not shadow a `[2]int32` that is stored in an interface. A slice of
|
|
21
|
+
// them is tagged with its Go type in an interface and registers like any other.
|
|
22
|
+
export const SHARED_REPR = new Set([
|
|
23
|
+
"bool",
|
|
24
|
+
"int8",
|
|
25
|
+
"int16",
|
|
26
|
+
"int32",
|
|
27
|
+
"uint8",
|
|
28
|
+
"uint16",
|
|
29
|
+
"uint32",
|
|
30
|
+
"byte",
|
|
31
|
+
"rune",
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
export function hasSharedRepr(t, depth = 0) {
|
|
35
|
+
if (!t || depth > 8) return false;
|
|
36
|
+
const u = under(t);
|
|
37
|
+
if (u?.kind === "basic") return SHARED_REPR.has(u.name);
|
|
38
|
+
if (u?.kind === "slice" || u?.kind === "array")
|
|
39
|
+
return hasSharedRepr(u.elem, depth + 1);
|
|
40
|
+
if (u?.kind === "map")
|
|
41
|
+
return hasSharedRepr(u.key, depth + 1) || hasSharedRepr(u.value, depth + 1);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// A method's results; a multi-value result is one tuple type.
|
|
46
|
+
export function results(m) {
|
|
47
|
+
const r = (m.returns ?? []).filter(
|
|
48
|
+
(t) => !(t?.kind === "basic" && t.name === "void"),
|
|
49
|
+
);
|
|
50
|
+
return r.length === 1 && r[0]?.kind === "tuple" ? r[0].types : r;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Calls every `fn(node)` for the AST nodes under `root`.
|
|
54
|
+
export function walkNodes(root, fn) {
|
|
55
|
+
if (!root || typeof root !== "object") return;
|
|
56
|
+
if (Array.isArray(root)) {
|
|
57
|
+
for (const n of root) walkNodes(n, fn);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
fn(root);
|
|
61
|
+
forEachChild(root, (c) => walkNodes(c, fn));
|
|
62
|
+
// templ bodies keep their Go expressions in non-enumerable properties
|
|
63
|
+
// (see parser/templ.js), which forEachChild does not visit.
|
|
64
|
+
for (const name of TEMPL_EXPR_PROPS) {
|
|
65
|
+
const d = Object.getOwnPropertyDescriptor(root, name);
|
|
66
|
+
if (d && !d.enumerable) walkNodes(d.value, fn);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const TEMPL_EXPR_PROPS = ["expr", "exprs", "forStmt"];
|
|
71
|
+
|
|
72
|
+
// True when `t` mentions a type parameter (a generic declaration, or a type
|
|
73
|
+
// built from one inside a generic function).
|
|
74
|
+
export function mentionsTypeParam(t, seen = new Set()) {
|
|
75
|
+
if (!t || seen.has(t)) return false;
|
|
76
|
+
seen.add(t);
|
|
77
|
+
const m = (x) => mentionsTypeParam(x, seen);
|
|
78
|
+
switch (t.kind) {
|
|
79
|
+
case "typeParam":
|
|
80
|
+
return true;
|
|
81
|
+
case "named":
|
|
82
|
+
return (t.typeArgs ?? []).some(m) || m(t.underlying);
|
|
83
|
+
case "pointer":
|
|
84
|
+
return m(t.base);
|
|
85
|
+
case "slice":
|
|
86
|
+
case "array":
|
|
87
|
+
return m(t.elem);
|
|
88
|
+
case "map":
|
|
89
|
+
return m(t.key) || m(t.value);
|
|
90
|
+
case "struct":
|
|
91
|
+
return (t._structFields ?? []).some((f) => m(f.type));
|
|
92
|
+
default:
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The last element of an import path: the package's name.
|
|
98
|
+
const canonicalName = (modPath) => modPath.split("/").pop();
|
|
99
|
+
|
|
100
|
+
// The names the programs declare at package level.
|
|
101
|
+
function declaredNames(programs) {
|
|
102
|
+
const names = new Set();
|
|
103
|
+
for (const p of programs)
|
|
104
|
+
for (const d of p.decls ?? []) {
|
|
105
|
+
if (d.kind === "TypeDecl" || d.kind === "FuncDecl") names.add(d.name);
|
|
106
|
+
else if (d.kind === "VarDecl" || d.kind === "ConstDecl")
|
|
107
|
+
for (const spec of d.decls ?? [])
|
|
108
|
+
for (const n of spec.names ?? []) names.add(n);
|
|
109
|
+
}
|
|
110
|
+
return names;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Every name an import answers to (its alias, its package name) -> its path.
|
|
114
|
+
function importPaths(programs) {
|
|
115
|
+
const paths = new Map();
|
|
116
|
+
for (const p of programs)
|
|
117
|
+
for (const imp of p.imports ?? [])
|
|
118
|
+
for (const spec of imp.imports) {
|
|
119
|
+
if (spec.alias === "_" || spec.alias === ".") continue;
|
|
120
|
+
paths.set(canonicalName(spec.path), spec.path);
|
|
121
|
+
if (spec.alias) paths.set(spec.alias, spec.path);
|
|
122
|
+
}
|
|
123
|
+
return paths;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The generated file's imports for the qualifiers (name -> path) its code
|
|
127
|
+
// spells: a path, or { path, alias } for a qualifier renamed to stay clear of
|
|
128
|
+
// a package-level name.
|
|
129
|
+
export function importsFor(qualifiers) {
|
|
130
|
+
return [...qualifiers].map(([name, path]) =>
|
|
131
|
+
name === canonicalName(path) ? path : { path, alias: name },
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Every type a closed-world generator may need to describe: the entries
|
|
136
|
+
// { type, node, via } for the type arguments of generic calls (`typeArg`),
|
|
137
|
+
// the type of every expression (`expr`), the static type of every value
|
|
138
|
+
// converted to an interface (`boxed`) and the package's declared named types
|
|
139
|
+
// (`declared`, node null).
|
|
140
|
+
export function collectProgramTypes(programs, checker) {
|
|
141
|
+
const found = [];
|
|
142
|
+
for (const p of programs)
|
|
143
|
+
walkNodes(p.decls, (node) => {
|
|
144
|
+
for (const a of node._typeArgs ?? [])
|
|
145
|
+
found.push({ type: defaultType(a), node, via: "typeArg" });
|
|
146
|
+
if (node._type)
|
|
147
|
+
found.push({ type: defaultType(node._type), node, via: "expr" });
|
|
148
|
+
if (node._ifaceSrc)
|
|
149
|
+
found.push({ type: defaultType(node._ifaceSrc), node, via: "boxed" });
|
|
150
|
+
// A multi-value call forwarded into interfaces (`return f()`,
|
|
151
|
+
// `g(f())`) keeps one mark holder per element until lowering
|
|
152
|
+
// splits it.
|
|
153
|
+
for (const h of node._fwdIface ?? [])
|
|
154
|
+
if (h?._ifaceSrc)
|
|
155
|
+
found.push({ type: defaultType(h._ifaceSrc), node, via: "boxed" });
|
|
156
|
+
});
|
|
157
|
+
for (const [name, t] of checker.types) {
|
|
158
|
+
if (t?.kind !== "named" || t.name !== name) continue;
|
|
159
|
+
if (name.includes(".") || name.includes("$$")) continue;
|
|
160
|
+
if (t._genericOf || t.typeArgs?.length) continue;
|
|
161
|
+
found.push({ type: t, node: null, via: "declared" });
|
|
162
|
+
}
|
|
163
|
+
return found;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export class TypeSpeller {
|
|
167
|
+
// `qualifiers` receives name -> import path for each package the spelled
|
|
168
|
+
// code names.
|
|
169
|
+
constructor(pkgName, qualifiers, programs = []) {
|
|
170
|
+
this.pkgName = pkgName;
|
|
171
|
+
this.qualifiers = qualifiers;
|
|
172
|
+
this.programs = programs;
|
|
173
|
+
this.importPaths = importPaths(programs);
|
|
174
|
+
this.declared = declaredNames(programs);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Go source for the type.
|
|
178
|
+
spell(t) {
|
|
179
|
+
const sp = (x) => this.spell(x);
|
|
180
|
+
const byKind = {
|
|
181
|
+
basic: () => t.name,
|
|
182
|
+
named: () => this.spellNamed(t),
|
|
183
|
+
pointer: () => `*${sp(t.base)}`,
|
|
184
|
+
slice: () => `[]${sp(t.elem)}`,
|
|
185
|
+
array: () => `[${t.size}]${sp(t.elem)}`,
|
|
186
|
+
map: () => `map[${sp(t.key)}]${sp(t.value)}`,
|
|
187
|
+
struct: () => this.spellStruct(t),
|
|
188
|
+
interface: () => (t.name === "error" ? "error" : "any"),
|
|
189
|
+
func: () => this.funcString(t, sp),
|
|
190
|
+
};
|
|
191
|
+
if (!byKind[t.kind])
|
|
192
|
+
throw new Error(`json: cannot spell type kind ${t.kind}`);
|
|
193
|
+
return byKind[t.kind]();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// The package name `%T` and errors print for the bare-named type `t`: this
|
|
197
|
+
// package, or the local package an importer reaches it through as `p.Name`.
|
|
198
|
+
declaringPackage(t) {
|
|
199
|
+
if (this.declared.has(t.name) || !this.checker) return this.pkgName;
|
|
200
|
+
for (const [key, v] of this.checker.types) {
|
|
201
|
+
const dot = key.indexOf(".");
|
|
202
|
+
if (v !== t || dot <= 0 || key.slice(dot + 1) !== t.name) continue;
|
|
203
|
+
const modPath = this.importPaths.get(key.slice(0, dot));
|
|
204
|
+
if (modPath) return canonicalName(modPath);
|
|
205
|
+
}
|
|
206
|
+
return this.pkgName;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// The name the generated file uses for the package at `modPath`: its
|
|
210
|
+
// package name, or `_gf_<name>` when the package declares that name itself
|
|
211
|
+
// or another path took it.
|
|
212
|
+
qualifierFor(modPath) {
|
|
213
|
+
for (const [name, path] of this.qualifiers)
|
|
214
|
+
if (path === modPath) return name;
|
|
215
|
+
const base = canonicalName(modPath);
|
|
216
|
+
const taken = (n) => this.declared.has(n) || this.qualifiers.has(n);
|
|
217
|
+
let name = base;
|
|
218
|
+
for (let i = 1; taken(name); i++) name = `_gf_${base}${i > 1 ? i : ""}`;
|
|
219
|
+
this.qualifiers.set(name, modPath);
|
|
220
|
+
return name;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
spellNamed(t) {
|
|
224
|
+
let name = t.name;
|
|
225
|
+
const at = name.indexOf("$$");
|
|
226
|
+
const dot = name.indexOf(".");
|
|
227
|
+
if (at >= 0) {
|
|
228
|
+
const orig = name.slice(0, at);
|
|
229
|
+
const modPath = name.slice(at + 2).replaceAll("$", "/");
|
|
230
|
+
name = `${this.qualifierFor(modPath)}.${orig}`;
|
|
231
|
+
} else if (dot > 0) {
|
|
232
|
+
const modPath = this.importPaths.get(name.slice(0, dot));
|
|
233
|
+
if (modPath) name = `${this.qualifierFor(modPath)}${name.slice(dot)}`;
|
|
234
|
+
}
|
|
235
|
+
return t.typeArgs?.length
|
|
236
|
+
? `${name}[${t.typeArgs.map((a) => this.spell(a)).join(", ")}]`
|
|
237
|
+
: name;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
spellStruct(t) {
|
|
241
|
+
const fields = (t._structFields ?? []).map((f) => {
|
|
242
|
+
const tag = f.tag ? ` \`${f.tag}\`` : "";
|
|
243
|
+
if (f.embedded) return `${this.spell(f.type)}${tag}`;
|
|
244
|
+
return `${f.names.join(", ")} ${this.spell(f.type)}${tag}`;
|
|
245
|
+
});
|
|
246
|
+
return `struct { ${fields.join("; ")} }`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
funcString(t, name) {
|
|
250
|
+
const rs = results(t);
|
|
251
|
+
const res =
|
|
252
|
+
rs.length === 0
|
|
253
|
+
? ""
|
|
254
|
+
: rs.length === 1
|
|
255
|
+
? ` ${name(rs[0])}`
|
|
256
|
+
: ` (${rs.map(name).join(", ")})`;
|
|
257
|
+
// The signature keeps a variadic parameter's element type.
|
|
258
|
+
const params = t.params.map((p, i) =>
|
|
259
|
+
t.variadic && i === t.params.length - 1 ? `...${name(p)}` : name(p),
|
|
260
|
+
);
|
|
261
|
+
return `func(${params.join(", ")})${res}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// True when the type can be written as Go source in the generated file.
|
|
265
|
+
// Subclasses decide what a named type, an interface and a struct field
|
|
266
|
+
// need.
|
|
267
|
+
spellable(t, depth = 0, seen = new Set()) {
|
|
268
|
+
if (!t || depth > 8) return false;
|
|
269
|
+
const ok = (x) => this.spellable(x, depth + 1, seen);
|
|
270
|
+
const byKind = {
|
|
271
|
+
basic: () => t.name !== "void" && t.name !== "nil",
|
|
272
|
+
named: () => this.spellableNamed(t, ok, seen),
|
|
273
|
+
// A JS-interop (.d.ts) type has no Go source to write.
|
|
274
|
+
namespace: () => false,
|
|
275
|
+
pointer: () => ok(t.base),
|
|
276
|
+
slice: () => ok(t.elem),
|
|
277
|
+
array: () => ok(t.elem),
|
|
278
|
+
map: () => ok(t.key) && ok(t.value),
|
|
279
|
+
interface: () => this.spellableInterface(t),
|
|
280
|
+
struct: () => this.spellableStruct(t, ok),
|
|
281
|
+
func: () => t.params.every(ok) && results(t).every(ok),
|
|
282
|
+
};
|
|
283
|
+
return byKind[t.kind]?.() ?? false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
spellableInterface(_t) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
spellableStruct(t, ok) {
|
|
291
|
+
return (t._structFields ?? []).every((f) => ok(f.type));
|
|
292
|
+
}
|
|
293
|
+
}
|
package/src/lower/index.js
CHANGED
|
@@ -16,9 +16,16 @@ import { scanAddressTaken } from "./boxing.js";
|
|
|
16
16
|
import { analyzeCaptures } from "./captures.js";
|
|
17
17
|
import { computeEmbeddedStubs } from "./embedding.js";
|
|
18
18
|
import { paramEscapes } from "./escape.js";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
extractNamedReturns,
|
|
21
|
+
hasDefer,
|
|
22
|
+
normalizeDefers,
|
|
23
|
+
splitForwardedTuples,
|
|
24
|
+
} from "./functions.js";
|
|
20
25
|
import { computeGlobalAnalysis } from "./globals.js";
|
|
21
|
-
import { OwnershipContext } from "./ownership.js";
|
|
26
|
+
import { OwnershipContext, takesElementAddress } from "./ownership.js";
|
|
27
|
+
|
|
28
|
+
export { takesElementAddress };
|
|
22
29
|
|
|
23
30
|
class LowerResult {
|
|
24
31
|
constructor() {
|
|
@@ -40,6 +47,8 @@ class LowerResult {
|
|
|
40
47
|
this.initPathFunctions = new Set();
|
|
41
48
|
// Map<ASTNode, Array<string>>
|
|
42
49
|
this.cachedGlobalsByFunc = new Map();
|
|
50
|
+
// True when some code takes `&xs[i]` (see takesElementAddress)
|
|
51
|
+
this.elemAddrTaken = false;
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
|
|
@@ -79,8 +88,11 @@ export function lower(programOrPrograms, checker = null) {
|
|
|
79
88
|
? programOrPrograms
|
|
80
89
|
: [programOrPrograms];
|
|
81
90
|
|
|
91
|
+
splitForwardedTuples(programs);
|
|
82
92
|
normalizeDefers(programs);
|
|
83
93
|
|
|
94
|
+
res.elemAddrTaken = programs.some((p) => takesElementAddress(p?.decls));
|
|
95
|
+
|
|
84
96
|
// 1. Aggregate methods across all files in the package
|
|
85
97
|
const methodMap = new Map();
|
|
86
98
|
for (const program of programs) {
|