gofront 1.9.0 → 1.9.2
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 +1 -1
- package/package.json +3 -3
- package/src/backend/js/expressions.js +329 -58
- package/src/backend/js/index.js +90 -10
- package/src/backend/js/runtime.js +51 -9
- package/src/backend/js/statements.js +193 -84
- package/src/backend/js/type-info.js +123 -8
- package/src/backend/shared/fmt-types.js +30 -1
- package/src/backend/wasm/emit-builtins.js +250 -9
- package/src/backend/wasm/emit-exprs.js +63 -22
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +239 -205
- package/src/backend/wasm/emit.js +73 -26
- package/src/backend/wasm/globals.js +1 -0
- package/src/backend/wasm/index.js +8 -0
- package/src/backend/wasm/module-emitter.js +211 -57
- package/src/backend/wasm/monomorph.js +36 -2
- package/src/backend/wasm/natives.js +4 -0
- package/src/backend/wasm/types.js +32 -3
- package/src/backend/wasm/variadic.js +6 -1
- package/src/cli/minifier.js +20 -6
- package/src/compiler.js +6 -1
- package/src/lexer/lexer.js +1 -0
- package/src/link/stdlib.js +1 -0
- package/src/link/type-names.js +72 -16
- package/src/lower/embedding.js +43 -15
- package/src/lower/fmt-formatters.js +38 -14
- package/src/lower/functions.js +223 -7
- package/src/lower/gen-types.js +21 -2
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +114 -6
- package/src/parser/declarations.js +4 -2
- package/src/parser/expressions.js +25 -41
- package/src/parser/statements.js +107 -0
- package/src/parser/types.js +11 -0
- package/src/typechecker/assignability.js +94 -12
- package/src/typechecker/constants.js +124 -2
- package/src/typechecker/expressions.js +171 -17
- package/src/typechecker/index.js +158 -29
- package/src/typechecker/operand-text.js +1 -0
- package/src/typechecker/resolve.js +308 -39
- package/src/typechecker/statements.js +171 -21
- package/src/typechecker/types.js +84 -2
package/src/backend/wasm/emit.js
CHANGED
|
@@ -213,6 +213,35 @@ export class FunctionEmitter {
|
|
|
213
213
|
return targets;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// Outer stacked labels (`a: b: for`) alias the innermost one. Collected
|
|
217
|
+
// once per root function so the yield closures of a range-over-func loop
|
|
218
|
+
// (emitted by their own emitter) resolve them too.
|
|
219
|
+
_stackedLabelAlias(label) {
|
|
220
|
+
const root = this.rootFuncDecl;
|
|
221
|
+
if (root && !root._labelAliases) {
|
|
222
|
+
const aliases = new Map();
|
|
223
|
+
const scan = (node) => {
|
|
224
|
+
if (!node || typeof node !== "object") return;
|
|
225
|
+
if (Array.isArray(node)) {
|
|
226
|
+
for (const item of node) scan(item);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (node.kind === "LabeledStmt" && node.body?.kind === "LabeledStmt") {
|
|
230
|
+
let inner = node.body;
|
|
231
|
+
while (inner.body?.kind === "LabeledStmt") inner = inner.body;
|
|
232
|
+
aliases.set(node.label, inner.label);
|
|
233
|
+
}
|
|
234
|
+
for (const k of Object.keys(node)) {
|
|
235
|
+
if (k.startsWith("_")) continue;
|
|
236
|
+
scan(node[k]);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
scan(root.body);
|
|
240
|
+
root._labelAliases = aliases;
|
|
241
|
+
}
|
|
242
|
+
return root?._labelAliases?.get(label) ?? this._labelAliases?.get(label);
|
|
243
|
+
}
|
|
244
|
+
|
|
216
245
|
_findLabelTarget(label, role) {
|
|
217
246
|
for (const target of this.rootFuncDecl?._labelTargets?.values() ?? []) {
|
|
218
247
|
if (target.label === label && target.role === role) return target;
|
|
@@ -1434,31 +1463,9 @@ export class FunctionEmitter {
|
|
|
1434
1463
|
typeIndex: cand.testTypeIndex,
|
|
1435
1464
|
fieldIndex: 0,
|
|
1436
1465
|
});
|
|
1437
|
-
if (cand.embedPath)
|
|
1438
|
-
for (const step of cand.embedPath) {
|
|
1439
|
-
if (step.castTypeIndex) {
|
|
1440
|
-
this.pushInstruction({
|
|
1441
|
-
op: "ref.cast",
|
|
1442
|
-
typeIndex: step.castTypeIndex,
|
|
1443
|
-
});
|
|
1444
|
-
}
|
|
1445
|
-
this.pushInstruction({
|
|
1446
|
-
op: "struct.get",
|
|
1447
|
-
typeIndex: step.parentTypeIndex,
|
|
1448
|
-
fieldIndex: step.fieldIndex,
|
|
1449
|
-
});
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1466
|
+
if (cand.embedPath) this._emitEmbedPath(cand);
|
|
1452
1467
|
} else {
|
|
1453
|
-
if (cand.embedPath)
|
|
1454
|
-
for (const step of cand.embedPath) {
|
|
1455
|
-
this.pushInstruction({
|
|
1456
|
-
op: "struct.get",
|
|
1457
|
-
typeIndex: step.parentTypeIndex,
|
|
1458
|
-
fieldIndex: step.fieldIndex,
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1468
|
+
if (cand.embedPath) this._emitEmbedPath(cand);
|
|
1462
1469
|
if (cand.needsValueDeref && cand.structInfo) {
|
|
1463
1470
|
this.emitCloneStruct(cand.structInfo, cand.targetRecvWType);
|
|
1464
1471
|
}
|
|
@@ -1469,7 +1476,10 @@ export class FunctionEmitter {
|
|
|
1469
1476
|
this.pushInstruction({ op: "local.get", index: p });
|
|
1470
1477
|
}
|
|
1471
1478
|
|
|
1472
|
-
this.pushInstruction({
|
|
1479
|
+
this.pushInstruction({
|
|
1480
|
+
op: "call",
|
|
1481
|
+
funcIndex: cand.selfDispatch ? fn._globalFuncIndex : cand.funcIndex,
|
|
1482
|
+
});
|
|
1473
1483
|
this.pushInstruction("return");
|
|
1474
1484
|
this.pushInstruction("end");
|
|
1475
1485
|
}
|
|
@@ -1477,12 +1487,49 @@ export class FunctionEmitter {
|
|
|
1477
1487
|
emitMiss("interface conversion: nil or unmatched type for method call");
|
|
1478
1488
|
}
|
|
1479
1489
|
|
|
1490
|
+
// Walks `cand.embedPath` from the embedder on the stack to the embedded
|
|
1491
|
+
// value. A step through a nil embedded pointer panics, except that the last
|
|
1492
|
+
// step may be nil for a pointer-receiver method (`nilCheckLast` is unset).
|
|
1493
|
+
_emitEmbedPath(cand) {
|
|
1494
|
+
const steps = cand.embedPath;
|
|
1495
|
+
for (const [i, step] of steps.entries()) {
|
|
1496
|
+
if (step.castTypeIndex) {
|
|
1497
|
+
this.pushInstruction({ op: "ref.cast", typeIndex: step.castTypeIndex });
|
|
1498
|
+
}
|
|
1499
|
+
this.pushInstruction({
|
|
1500
|
+
op: "struct.get",
|
|
1501
|
+
typeIndex: step.parentTypeIndex,
|
|
1502
|
+
fieldIndex: step.fieldIndex,
|
|
1503
|
+
});
|
|
1504
|
+
if (step.ptrWType && (i < steps.length - 1 || cand.nilCheckLast))
|
|
1505
|
+
this._emitNilPanicIfNull(step.ptrWType);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// Panics with a nil dereference when the reference on the stack is null,
|
|
1510
|
+
// leaving it on the stack otherwise.
|
|
1511
|
+
_emitNilPanicIfNull(wType) {
|
|
1512
|
+
const tmp = this.acquireTemp(wType);
|
|
1513
|
+
this.pushInstruction({ op: "local.tee", index: tmp });
|
|
1514
|
+
this.pushInstruction("ref.is_null");
|
|
1515
|
+
this.pushInstruction({ op: "if", blockType: "void" });
|
|
1516
|
+
this.emitPanic(
|
|
1517
|
+
"runtime error: invalid memory address or nil pointer dereference",
|
|
1518
|
+
);
|
|
1519
|
+
this.pushInstruction("end");
|
|
1520
|
+
this.pushInstruction({ op: "local.get", index: tmp });
|
|
1521
|
+
this.releaseTemp(tmp, wType);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1480
1524
|
// A typed nil `*S` inside the interface is a marker object (see
|
|
1481
1525
|
// getNilBoxType): a pointer-receiver method runs with a nil receiver, as in
|
|
1482
1526
|
// Go; a value-receiver or promoted method must read through the nil pointer.
|
|
1527
|
+
// A promoted candidate tests the outer struct but carries the embedded
|
|
1528
|
+
// struct's info, so it is recognised by its embed path.
|
|
1483
1529
|
_emitNilPtrCandidate(cand, fn, recvLocal) {
|
|
1484
1530
|
if (cand.isBoxedValue || !cand.structInfo) return;
|
|
1485
|
-
if (cand.testTypeIndex !== cand.structInfo.typeIndex)
|
|
1531
|
+
if (!cand.embedPath && cand.testTypeIndex !== cand.structInfo.typeIndex)
|
|
1532
|
+
return;
|
|
1486
1533
|
const nilBox = this.mod.getNilBoxType({
|
|
1487
1534
|
kind: "pointer",
|
|
1488
1535
|
base: { kind: "named", name: cand.recvTypeName },
|
|
@@ -104,6 +104,7 @@ function _zeroFieldConst(f, mod) {
|
|
|
104
104
|
const w = f.wType;
|
|
105
105
|
if (w === "i64") return [{ op: "i64.const", value: 0n }];
|
|
106
106
|
if (w === "f32" || w === "f64") return [{ op: `${w}.const`, value: 0 }];
|
|
107
|
+
if (w === "v128") return [{ op: "v128.const", bytes: new Array(16).fill(0) }];
|
|
107
108
|
if (w === "externref") return [{ op: "ref.null", heapType: "extern" }];
|
|
108
109
|
if (w === "anyref") return [{ op: "ref.null", heapType: "any" }];
|
|
109
110
|
if (typeof w !== "object" || w === null)
|
|
@@ -259,6 +259,7 @@ export function compileWasmModule(
|
|
|
259
259
|
_isClosure: true,
|
|
260
260
|
_isYieldClosure: Boolean(node._isYieldClosure),
|
|
261
261
|
_iterStateName: node._iterStateName ?? null,
|
|
262
|
+
_forStmt: node._forStmt ?? null,
|
|
262
263
|
_funcLit: node,
|
|
263
264
|
_rootFuncDecl: rootFn,
|
|
264
265
|
_srcFile: rootFn?._srcFile ?? null,
|
|
@@ -701,6 +702,13 @@ export function compileWasmModule(
|
|
|
701
702
|
),
|
|
702
703
|
);
|
|
703
704
|
}
|
|
705
|
+
// fmt's %p identity of a reference in an `any`: a defined map or func type
|
|
706
|
+
// is boxed there, so the helper hands the host the boxed reference.
|
|
707
|
+
if (funcDecls.some((fn) => fn.intrinsic === "fmt_ptr_id")) {
|
|
708
|
+
funcDecls.push(
|
|
709
|
+
helper("__any_ref", [{ name: "a", type: ANY_T }], ANY_T, "any_ref"),
|
|
710
|
+
);
|
|
711
|
+
}
|
|
704
712
|
if (mod.hasErrorType) {
|
|
705
713
|
const errPtr = {
|
|
706
714
|
kind: "pointer",
|
|
@@ -576,6 +576,7 @@ export class ModuleEmitter {
|
|
|
576
576
|
const known = this.taggedSliceTypes.get(key);
|
|
577
577
|
if (known) return known;
|
|
578
578
|
const base = this.getSliceType(elemGoType);
|
|
579
|
+
const root = this.getTaggedSliceRoot(base);
|
|
579
580
|
const tagIndex = this.taggedSliceTypes.size + 1;
|
|
580
581
|
const tagTypeIndex = this._getTotalTypeCount();
|
|
581
582
|
this._pushType({
|
|
@@ -589,7 +590,7 @@ export class ModuleEmitter {
|
|
|
589
590
|
this._pushType({
|
|
590
591
|
form: "struct",
|
|
591
592
|
sub: true,
|
|
592
|
-
superTypeIndex:
|
|
593
|
+
superTypeIndex: root.typeIndex,
|
|
593
594
|
fields: [
|
|
594
595
|
...base.typeEntry.fields,
|
|
595
596
|
{
|
|
@@ -603,6 +604,23 @@ export class ModuleEmitter {
|
|
|
603
604
|
return info;
|
|
604
605
|
}
|
|
605
606
|
|
|
607
|
+
// The common supertype of every tagged subtype of the slice type `base`. An
|
|
608
|
+
// unnamed `[]int` in an interface is the bare slice type, which every
|
|
609
|
+
// tagged subtype (`type S []int`) also passes `ref.test` for; testing
|
|
610
|
+
// "base and not root" is what keeps `any(S{}).([]int)` from matching.
|
|
611
|
+
getTaggedSliceRoot(base) {
|
|
612
|
+
if (base.tagRoot) return base.tagRoot;
|
|
613
|
+
const typeIndex = this._getTotalTypeCount();
|
|
614
|
+
this._pushType({
|
|
615
|
+
form: "struct",
|
|
616
|
+
sub: true,
|
|
617
|
+
superTypeIndex: base.typeIndex,
|
|
618
|
+
fields: [...base.typeEntry.fields],
|
|
619
|
+
});
|
|
620
|
+
base.tagRoot = { typeIndex };
|
|
621
|
+
return base.tagRoot;
|
|
622
|
+
}
|
|
623
|
+
|
|
606
624
|
getSliceTypeByIndex(typeIndex) {
|
|
607
625
|
return this.sliceTypesByIndex?.get(typeIndex) ?? null;
|
|
608
626
|
}
|
|
@@ -1330,61 +1348,60 @@ export class ModuleEmitter {
|
|
|
1330
1348
|
}
|
|
1331
1349
|
|
|
1332
1350
|
// Promoted methods on sName
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1351
|
+
const promoted = this._findPromotedMethod(sInfo, methodName);
|
|
1352
|
+
if (promoted?.iface) {
|
|
1353
|
+
this._addIfaceEmbedCandidates(
|
|
1354
|
+
candidates,
|
|
1355
|
+
seenKeys,
|
|
1356
|
+
sName,
|
|
1357
|
+
sInfo,
|
|
1358
|
+
promoted.embedPath,
|
|
1359
|
+
);
|
|
1360
|
+
} else if (promoted) {
|
|
1361
|
+
const { embedType, embedPath, viaPointer } = promoted;
|
|
1362
|
+
const embedMethodName = `${embedType}.${methodName}`;
|
|
1363
|
+
const fIdx = this.resolveFuncIndex(embedMethodName);
|
|
1364
|
+
const paramGoTypes = this.getFuncParamGoTypes(embedMethodName);
|
|
1365
|
+
const paramWTypes = this.getFuncParamTypes(embedMethodName);
|
|
1366
|
+
const isPtrRecv = paramGoTypes[0]?.kind === "pointer";
|
|
1367
|
+
const embedSInfo = this.getStructType(embedType);
|
|
1368
|
+
|
|
1369
|
+
const ptrKey = `ptr:${sName}`;
|
|
1370
|
+
if (!seenKeys.has(ptrKey)) {
|
|
1371
|
+
seenKeys.add(ptrKey);
|
|
1372
|
+
candidates.push({
|
|
1373
|
+
recvTypeName: sName,
|
|
1374
|
+
testTypeIndex: sInfo.typeIndex,
|
|
1375
|
+
isBoxedValue: false,
|
|
1376
|
+
needsValueDeref: !isPtrRecv,
|
|
1377
|
+
embedPath,
|
|
1378
|
+
nilCheckLast: !isPtrRecv,
|
|
1379
|
+
funcIndex: fIdx,
|
|
1380
|
+
targetParamTypes: paramWTypes,
|
|
1381
|
+
targetRecvWType: paramWTypes[0],
|
|
1382
|
+
structInfo: embedSInfo,
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1364
1385
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
structInfo: embedSInfo,
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1386
|
+
// A pointer-receiver method is in the value's method set only
|
|
1387
|
+
// when it is reached through an embedded pointer.
|
|
1388
|
+
if (!isPtrRecv || viaPointer) {
|
|
1389
|
+
const box = this.getIfaceBoxType({ kind: "named", name: sName });
|
|
1390
|
+
const valKey = `val:${sName}`;
|
|
1391
|
+
if (box && !seenKeys.has(valKey)) {
|
|
1392
|
+
seenKeys.add(valKey);
|
|
1393
|
+
candidates.push({
|
|
1394
|
+
recvTypeName: sName,
|
|
1395
|
+
testTypeIndex: box.typeIndex,
|
|
1396
|
+
isBoxedValue: true,
|
|
1397
|
+
needsValueDeref: false,
|
|
1398
|
+
embedPath,
|
|
1399
|
+
nilCheckLast: !isPtrRecv,
|
|
1400
|
+
funcIndex: fIdx,
|
|
1401
|
+
targetParamTypes: paramWTypes,
|
|
1402
|
+
targetRecvWType: paramWTypes[0],
|
|
1403
|
+
structInfo: embedSInfo,
|
|
1404
|
+
});
|
|
1388
1405
|
}
|
|
1389
1406
|
}
|
|
1390
1407
|
}
|
|
@@ -1495,6 +1512,116 @@ export class ModuleEmitter {
|
|
|
1495
1512
|
return candidates;
|
|
1496
1513
|
}
|
|
1497
1514
|
|
|
1515
|
+
// Whether `goType`, the type of an embedded field, is an interface whose
|
|
1516
|
+
// method set has `methodName`.
|
|
1517
|
+
embeddedInterfaceHas(goType, methodName) {
|
|
1518
|
+
let t = goType;
|
|
1519
|
+
for (let i = 0; i < 8 && t; i++) {
|
|
1520
|
+
if (t.kind === "TypeName" || t.kind === "Ident") {
|
|
1521
|
+
const named = this.checker?.types?.get(t.name);
|
|
1522
|
+
if (!named) return t.name === "error" && methodName === "Error";
|
|
1523
|
+
t = named;
|
|
1524
|
+
} else if (t.kind === "named") {
|
|
1525
|
+
t = t.underlying ?? this.checker?.types?.get(t.name)?.underlying;
|
|
1526
|
+
} else if (t.kind === "interface") {
|
|
1527
|
+
return t.methods?.has?.(methodName) ?? false;
|
|
1528
|
+
} else if (t.kind === "InterfaceType") {
|
|
1529
|
+
return (t.methods ?? []).some((m) => (m.name ?? m) === methodName);
|
|
1530
|
+
} else {
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// Dispatch candidates for a method `sName` promotes from an embedded
|
|
1538
|
+
// interface: the embedded value is loaded and dispatched again through the
|
|
1539
|
+
// same dispatcher (`selfDispatch`).
|
|
1540
|
+
_addIfaceEmbedCandidates(candidates, seenKeys, sName, sInfo, embedPath) {
|
|
1541
|
+
const base = {
|
|
1542
|
+
recvTypeName: sName,
|
|
1543
|
+
needsValueDeref: false,
|
|
1544
|
+
embedPath,
|
|
1545
|
+
selfDispatch: true,
|
|
1546
|
+
structInfo: sInfo,
|
|
1547
|
+
};
|
|
1548
|
+
const ptrKey = `ptr:${sName}`;
|
|
1549
|
+
if (!seenKeys.has(ptrKey)) {
|
|
1550
|
+
seenKeys.add(ptrKey);
|
|
1551
|
+
candidates.push({
|
|
1552
|
+
...base,
|
|
1553
|
+
testTypeIndex: sInfo.typeIndex,
|
|
1554
|
+
isBoxedValue: false,
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
const box = this.getIfaceBoxType({ kind: "named", name: sName });
|
|
1558
|
+
const valKey = `val:${sName}`;
|
|
1559
|
+
if (box && !seenKeys.has(valKey)) {
|
|
1560
|
+
seenKeys.add(valKey);
|
|
1561
|
+
candidates.push({
|
|
1562
|
+
...base,
|
|
1563
|
+
testTypeIndex: box.typeIndex,
|
|
1564
|
+
isBoxedValue: true,
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// The shallowest embedded type of `sInfo` (any depth) that declares
|
|
1570
|
+
// `methodName`: its name, the field path to reach it from an `sInfo`
|
|
1571
|
+
// struct, and whether that path goes through an embedded pointer. Null
|
|
1572
|
+
// when two embeds at that depth declare it (ambiguous, as in Go).
|
|
1573
|
+
_findPromotedMethod(sInfo, methodName) {
|
|
1574
|
+
let level = [{ info: sInfo, path: [], viaPointer: false }];
|
|
1575
|
+
const seen = new Set([sInfo]);
|
|
1576
|
+
while (level.length > 0) {
|
|
1577
|
+
const next = [];
|
|
1578
|
+
const hits = [];
|
|
1579
|
+
for (const { info, path, viaPointer } of level) {
|
|
1580
|
+
for (const embed of info.embeds ?? []) {
|
|
1581
|
+
const embedType = embed.typeName ?? embed.name;
|
|
1582
|
+
const goType = info.fields?.[embed.fieldIndex]?.goType;
|
|
1583
|
+
const isPtr =
|
|
1584
|
+
goType?.kind === "PointerType" || goType?.kind === "pointer";
|
|
1585
|
+
const step = {
|
|
1586
|
+
path: [
|
|
1587
|
+
...path,
|
|
1588
|
+
{
|
|
1589
|
+
parentTypeIndex: info.typeIndex,
|
|
1590
|
+
fieldIndex: embed.fieldIndex,
|
|
1591
|
+
// An embedded pointer may be nil: reading through it
|
|
1592
|
+
// is a recoverable nil dereference.
|
|
1593
|
+
...(isPtr && {
|
|
1594
|
+
ptrWType: info.fields[embed.fieldIndex].wType,
|
|
1595
|
+
}),
|
|
1596
|
+
},
|
|
1597
|
+
],
|
|
1598
|
+
viaPointer: viaPointer || isPtr,
|
|
1599
|
+
};
|
|
1600
|
+
if (this.embeddedInterfaceHas(goType, methodName)) {
|
|
1601
|
+
hits.push({ iface: true, embedPath: step.path, viaPointer: false });
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
if (this.funcMap.has(`${embedType}.${methodName}`)) {
|
|
1605
|
+
hits.push({
|
|
1606
|
+
embedType,
|
|
1607
|
+
embedPath: step.path,
|
|
1608
|
+
viaPointer: step.viaPointer,
|
|
1609
|
+
});
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
// A type reached twice at one depth stays twice in the next
|
|
1613
|
+
// level, so a diamond's method is ambiguous there too.
|
|
1614
|
+
const inner = this.structTypes.get(embedType);
|
|
1615
|
+
if (inner && !seen.has(inner)) next.push({ info: inner, ...step });
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
if (hits.length > 0) return hits.length === 1 ? hits[0] : null;
|
|
1619
|
+
for (const n of next) seen.add(n.info);
|
|
1620
|
+
level = next;
|
|
1621
|
+
}
|
|
1622
|
+
return null;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1498
1625
|
// A value-receiver method of a named scalar is also in the method set of
|
|
1499
1626
|
// the pointer: `var g Getter = &x` holds a pointer box whose target is read
|
|
1500
1627
|
// before the call.
|
|
@@ -1560,7 +1687,7 @@ export class ModuleEmitter {
|
|
|
1560
1687
|
}
|
|
1561
1688
|
|
|
1562
1689
|
// Check struct value (boxed)
|
|
1563
|
-
if (this.
|
|
1690
|
+
if (this._valueImplements(namedType, ifaceResolved)) {
|
|
1564
1691
|
const box = this.getIfaceBoxType(namedType);
|
|
1565
1692
|
if (box && !seen.has(box.typeIndex)) {
|
|
1566
1693
|
seen.add(box.typeIndex);
|
|
@@ -1578,7 +1705,7 @@ export class ModuleEmitter {
|
|
|
1578
1705
|
tVal.underlying?.kind !== "interface"
|
|
1579
1706
|
) {
|
|
1580
1707
|
if (this.checker?.implements?.(tVal, ifaceResolved)) {
|
|
1581
|
-
const box = this.
|
|
1708
|
+
const box = this._namedValueBox(tVal);
|
|
1582
1709
|
if (box && !seen.has(box.typeIndex)) {
|
|
1583
1710
|
seen.add(box.typeIndex);
|
|
1584
1711
|
candidates.push({ name: tName, typeIndex: box.typeIndex });
|
|
@@ -1598,6 +1725,33 @@ export class ModuleEmitter {
|
|
|
1598
1725
|
return candidates;
|
|
1599
1726
|
}
|
|
1600
1727
|
|
|
1728
|
+
// The WebAssembly type a value of the named non-struct type `tVal` takes
|
|
1729
|
+
// inside an interface: a defined slice type is a tagged slice (see
|
|
1730
|
+
// getTaggedSliceType), anything else an interface box.
|
|
1731
|
+
_namedValueBox(tVal) {
|
|
1732
|
+
if (!isSliceType(tVal, this.checker)) return this.getIfaceBoxType(tVal);
|
|
1733
|
+
const elem = getSliceElemType(tVal, this.checker);
|
|
1734
|
+
return elem ? this.getTaggedSliceType(tVal, elem) : null;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// Whether the struct value `namedType` implements `iface`, including when its
|
|
1738
|
+
// pointer-receiver methods are promoted through an embedded pointer, which
|
|
1739
|
+
// puts them in the value's method set (`struct{ *Inner }`).
|
|
1740
|
+
_valueImplements(namedType, iface) {
|
|
1741
|
+
const checker = this.checker;
|
|
1742
|
+
if (checker?.implements?.(namedType, iface)) return true;
|
|
1743
|
+
const ptr = { kind: "pointer", base: namedType };
|
|
1744
|
+
if (!checker?.implements?.(ptr, iface)) return false;
|
|
1745
|
+
const st = checker.resolveType(namedType.underlying);
|
|
1746
|
+
const methods = st?.methods?.size ? st.methods : namedType.methods;
|
|
1747
|
+
const names = [...(iface.methods?.keys() ?? [])];
|
|
1748
|
+
return names.every(
|
|
1749
|
+
(n) =>
|
|
1750
|
+
!methods?.get(n)?._ptrRecv ||
|
|
1751
|
+
checker._promotedThroughPointer(namedType, n),
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1601
1755
|
// `*T` of a named scalar or slice is boxed apart from `T`.
|
|
1602
1756
|
_addPtrNamedImplementor(tVal, tName, ifaceResolved, seen, candidates) {
|
|
1603
1757
|
const ptrType = { kind: "pointer", base: tVal };
|
|
@@ -73,9 +73,20 @@ function rawTypeKey(t) {
|
|
|
73
73
|
if (t.kind === "map" || t.kind === "MapType") {
|
|
74
74
|
return `map_${typeKey(t.key)}_${typeKey(t.value)}`;
|
|
75
75
|
}
|
|
76
|
-
if (t.kind === "func"
|
|
76
|
+
if (t.kind === "func") {
|
|
77
77
|
const params = (t.params ?? []).map(typeKey).join("_");
|
|
78
|
-
const rets = (t.returns ?? [])
|
|
78
|
+
const rets = (t.returns ?? [])
|
|
79
|
+
.filter((r) => !(r?.kind === "basic" && r.name === "void"))
|
|
80
|
+
.map(typeKey)
|
|
81
|
+
.join("_");
|
|
82
|
+
return `fn_${params}_ret_${rets}`;
|
|
83
|
+
}
|
|
84
|
+
if (t.kind === "FuncType") {
|
|
85
|
+
const params = (t.params ?? []).map((p) => typeKey(p.type)).join("_");
|
|
86
|
+
const rt = t.returnType;
|
|
87
|
+
const rets = rt
|
|
88
|
+
? (rt.kind === "TupleType" ? rt.types : [rt]).map(typeKey).join("_")
|
|
89
|
+
: "";
|
|
79
90
|
return `fn_${params}_ret_${rets}`;
|
|
80
91
|
}
|
|
81
92
|
if (t.name) return t.name;
|
|
@@ -135,9 +146,32 @@ export function typeToTypeNode(t) {
|
|
|
135
146
|
};
|
|
136
147
|
}
|
|
137
148
|
if (t.kind === "typeParam") return { kind: "TypeName", name: t.name };
|
|
149
|
+
// Without this a func type argument fell through to `any`, so a
|
|
150
|
+
// `map[string]V` built in `mk[func()]` was a `map[string]any`.
|
|
151
|
+
if (t.kind === "func") return funcTypeNode(t);
|
|
138
152
|
return { kind: "TypeName", name: t.name ?? "any" };
|
|
139
153
|
}
|
|
140
154
|
|
|
155
|
+
// A checker func type as a `FuncType` node (several results as a `TupleType`).
|
|
156
|
+
function funcTypeNode(t) {
|
|
157
|
+
// A variadic func lists its last parameter by element type.
|
|
158
|
+
const params = (t.params ?? []).map((p, i, all) => ({
|
|
159
|
+
name: "_",
|
|
160
|
+
type: typeToTypeNode(p),
|
|
161
|
+
variadic: !!t.variadic && i === all.length - 1,
|
|
162
|
+
}));
|
|
163
|
+
const rets = (t.returns ?? []).filter(
|
|
164
|
+
(r) => !(r?.kind === "basic" && r.name === "void"),
|
|
165
|
+
);
|
|
166
|
+
// Several results come as one tuple type or as a list.
|
|
167
|
+
const results = rets.length === 1 && rets[0]?.types ? rets[0].types : rets;
|
|
168
|
+
let returnType = null;
|
|
169
|
+
if (results.length === 1) returnType = typeToTypeNode(results[0]);
|
|
170
|
+
else if (results.length > 1)
|
|
171
|
+
returnType = { kind: "TupleType", types: results.map(typeToTypeNode) };
|
|
172
|
+
return { kind: "FuncType", params, returnType };
|
|
173
|
+
}
|
|
174
|
+
|
|
141
175
|
// Replaces the type parameters inside a checker type (`[]E`, `map[K]V`,
|
|
142
176
|
// `Box[E]`) by their type arguments; the result mixes checker types and the
|
|
143
177
|
// AST type nodes of the substitution, which `typeKey` and `typeToTypeNode`
|
|
@@ -175,6 +175,10 @@ const WASM_INTRINSICS = {
|
|
|
175
175
|
mod.getOrAddFuncImport("env", "fmt_ptr_id", [EXT], ["i32"]),
|
|
176
176
|
emit: (e) => {
|
|
177
177
|
e.pushInstruction({ op: "local.get", index: 0 });
|
|
178
|
+
e.pushInstruction({
|
|
179
|
+
op: "call",
|
|
180
|
+
funcIndex: e.mod.resolveFuncIndex("__any_ref"),
|
|
181
|
+
});
|
|
178
182
|
e.pushInstruction("extern.convert_any");
|
|
179
183
|
e.pushInstruction({
|
|
180
184
|
op: "call",
|
|
@@ -407,6 +407,24 @@ export function goTypeKey(t) {
|
|
|
407
407
|
if (t.kind === "map" || t.kind === "MapType") {
|
|
408
408
|
return `map_${goTypeKey(t.key ?? t.keyType)}_${goTypeKey(t.value ?? t.val ?? t.elem ?? t.valueType ?? t.valType)}`;
|
|
409
409
|
}
|
|
410
|
+
// Func types differ by signature (`[2]func()` is not `[2]func(int) bool`).
|
|
411
|
+
// A checker func lists the element type of a variadic parameter, like a
|
|
412
|
+
// parsed `FuncType`.
|
|
413
|
+
if (t.kind === "func" || t.kind === "FuncType") {
|
|
414
|
+
const parsed = t.kind === "FuncType";
|
|
415
|
+
const params = (t.params ?? []).map((p) => goTypeKey(parsed ? p.type : p));
|
|
416
|
+
const variadic = parsed ? t.params?.at(-1)?.variadic : t.variadic;
|
|
417
|
+
const rets = parsed
|
|
418
|
+
? t.returnType
|
|
419
|
+
? [t.returnType]
|
|
420
|
+
: []
|
|
421
|
+
: (t.returns ?? []).filter(
|
|
422
|
+
(r) => !(r?.kind === "basic" && r.name === "void"),
|
|
423
|
+
);
|
|
424
|
+
return `fn_${variadic ? "v" : ""}${params.join(",")}_${rets.map(goTypeKey).join(",")}`;
|
|
425
|
+
}
|
|
426
|
+
if (t.kind === "tuple" || t.kind === "TupleType")
|
|
427
|
+
return `tup_${(t.types ?? []).map(goTypeKey).join(",")}`;
|
|
410
428
|
if (t.kind === "struct" || t.kind === "StructType") {
|
|
411
429
|
if (t.name) return t.name;
|
|
412
430
|
const rawFields = [];
|
|
@@ -480,6 +498,7 @@ function isReplaceableRef(goType, checker) {
|
|
|
480
498
|
return (
|
|
481
499
|
isSliceType(goType, checker) ||
|
|
482
500
|
isMapType(goType, checker) ||
|
|
501
|
+
isComplex(goType) ||
|
|
483
502
|
t?.kind === "pointer" ||
|
|
484
503
|
t?.kind === "PointerType"
|
|
485
504
|
);
|
|
@@ -523,8 +542,14 @@ export function isSliceType(goType, checker = null) {
|
|
|
523
542
|
}
|
|
524
543
|
|
|
525
544
|
// Element kinds whose slices share one WebAssembly representation (a slice of
|
|
526
|
-
// i32), so `[]bool`, `[]byte
|
|
545
|
+
// i32, or of i64 for the 64-bit integers), so `[]bool`, `[]byte`, `[]rune` and
|
|
546
|
+
// `[]int` vs `[]int64` cannot be told apart by `ref.test`.
|
|
527
547
|
const TAGGED_SLICE_ELEMS = new Set([
|
|
548
|
+
"int",
|
|
549
|
+
"int64",
|
|
550
|
+
"uint",
|
|
551
|
+
"uint64",
|
|
552
|
+
"uintptr",
|
|
528
553
|
"bool",
|
|
529
554
|
"int8",
|
|
530
555
|
"int16",
|
|
@@ -536,8 +561,8 @@ const TAGGED_SLICE_ELEMS = new Set([
|
|
|
536
561
|
"rune",
|
|
537
562
|
]);
|
|
538
563
|
|
|
539
|
-
// True for a defined slice type
|
|
540
|
-
//
|
|
564
|
+
// True for a defined slice type, a slice of booleans or integers, or a slice
|
|
565
|
+
// of a method interface. In an
|
|
541
566
|
// interface such a value carries a per-Go-type tag (see getTaggedSliceType).
|
|
542
567
|
export function isTaggedSliceType(goType, checker = null) {
|
|
543
568
|
if (!isSliceType(goType, checker)) return false;
|
|
@@ -560,6 +585,10 @@ export function isTaggedSliceType(goType, checker = null) {
|
|
|
560
585
|
};
|
|
561
586
|
else break;
|
|
562
587
|
}
|
|
588
|
+
// A slice of a method interface (`[]error`) is a slice of `anyref` like
|
|
589
|
+
// `[]any`, and `[]fmt.Stringer` another one. `[]any` and `[]interface{}`
|
|
590
|
+
// are the same type, so neither is tagged.
|
|
591
|
+
if (isNonEmptyInterface(elem, checker)) return true;
|
|
563
592
|
return elem?.kind === "basic" && TAGGED_SLICE_ELEMS.has(elem.name);
|
|
564
593
|
}
|
|
565
594
|
|
|
@@ -26,13 +26,17 @@ function isStdlibCall(call) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function packCallArgs(call) {
|
|
29
|
-
|
|
29
|
+
// A call node can be reachable twice (`defer f(x)` lowers to a wrapper
|
|
30
|
+
// whose body lists the same call under `stmts` and `list`): pack it once.
|
|
31
|
+
if (call._variadicPacked || call._multiForward || isStdlibCall(call))
|
|
32
|
+
return false;
|
|
30
33
|
const fn = variadicFuncType(call.func?._type);
|
|
31
34
|
if (!fn) return false;
|
|
32
35
|
const n = fn.params.length;
|
|
33
36
|
const args = call.args ?? [];
|
|
34
37
|
if (args.length === n && args[n - 1]?._spread) {
|
|
35
38
|
args[n - 1]._spread = false;
|
|
39
|
+
call._variadicPacked = true;
|
|
36
40
|
return true;
|
|
37
41
|
}
|
|
38
42
|
if (args.length < n - 1) return false;
|
|
@@ -62,6 +66,7 @@ function packCallArgs(call) {
|
|
|
62
66
|
_col: call._col,
|
|
63
67
|
},
|
|
64
68
|
];
|
|
69
|
+
call._variadicPacked = true;
|
|
65
70
|
return true;
|
|
66
71
|
}
|
|
67
72
|
|