gofront 1.5.1 → 1.6.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 +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +50 -19
- package/src/backend/js/index.js +100 -55
- package/src/backend/js/runtime.js +102 -0
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +96 -16
- package/src/backend/js/stdlib/errors.js +2 -2
- package/src/backend/js/stdlib/fmt.js +26 -8
- package/src/backend/js/stdlib/index.js +3 -0
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +1 -1
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +34 -9
- package/src/backend/js/stdlib/strings.js +4 -1
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +3 -3
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +486 -33
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +606 -5009
- package/src/backend/wasm/encode.js +212 -64
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +1426 -49
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +159 -35
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +275 -72
- package/src/compiler.js +144 -21
- package/src/index.js +86 -115
- package/src/lower/boxing.js +3 -4
- package/src/lower/captures.js +10 -17
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +5 -10
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/resolver.js +2 -1
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +142 -2
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +17 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +206 -7
- package/src/vendor.js +2 -2
package/src/lower/escape.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { collectDeclaredNames } from "./captures.js";
|
|
8
8
|
import { rootIdentName } from "./ownership.js";
|
|
9
|
+
import { forEachChild } from "./walk.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Checks whether an expression evaluates to one of the target pointer variables
|
|
@@ -15,7 +16,7 @@ import { rootIdentName } from "./ownership.js";
|
|
|
15
16
|
* @param {Set<string>} targetNames Set of identifier names being tracked
|
|
16
17
|
* @returns {boolean}
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
+
function evaluatesToPointer(expr, targetNames) {
|
|
19
20
|
if (!expr || typeof expr !== "object") return false;
|
|
20
21
|
if (expr.kind === "Ident") {
|
|
21
22
|
return targetNames.has(expr.name);
|
|
@@ -144,10 +145,7 @@ export function paramEscapes(body, paramName) {
|
|
|
144
145
|
captured = true;
|
|
145
146
|
return;
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
-
if (k.startsWith("_")) continue;
|
|
149
|
-
checkClosure(cNode[k]);
|
|
150
|
-
}
|
|
148
|
+
forEachChild(cNode, checkClosure);
|
|
151
149
|
};
|
|
152
150
|
checkClosure(node.body);
|
|
153
151
|
if (captured) {
|
|
@@ -158,10 +156,7 @@ export function paramEscapes(body, paramName) {
|
|
|
158
156
|
return;
|
|
159
157
|
}
|
|
160
158
|
|
|
161
|
-
|
|
162
|
-
if (key.startsWith("_")) continue;
|
|
163
|
-
walk(node[key]);
|
|
164
|
-
}
|
|
159
|
+
forEachChild(node, walk);
|
|
165
160
|
};
|
|
166
161
|
|
|
167
162
|
walk(body);
|
package/src/lower/functions.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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";
|
|
5
|
+
|
|
4
6
|
export function extractNamedReturns(decl) {
|
|
5
7
|
const named = decl?.returnType?._namedReturns;
|
|
6
8
|
if (!named || named.length === 0) return null;
|
|
@@ -19,12 +21,282 @@ export function hasDefer(body) {
|
|
|
19
21
|
if (Array.isArray(node)) return node.some(walk);
|
|
20
22
|
if (node.kind === "DeferStmt") return true;
|
|
21
23
|
if (node.kind === "FuncLit") return false; // separate function boundary
|
|
22
|
-
|
|
23
|
-
if (key.startsWith("_")) continue;
|
|
24
|
-
if (walk(node[key])) return true;
|
|
25
|
-
}
|
|
26
|
-
return false;
|
|
24
|
+
return someChild(node, walk);
|
|
27
25
|
};
|
|
28
26
|
|
|
29
27
|
return walk(body);
|
|
30
28
|
}
|
|
29
|
+
|
|
30
|
+
let defargCounter = 0;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* True when `body` contains a direct `recover()` call (not nested inside a
|
|
34
|
+
* FuncLit). Used to decide which functions may legitimately observe an
|
|
35
|
+
* in-flight panic: Go only honours recover() when it is called directly by
|
|
36
|
+
* a deferred function.
|
|
37
|
+
*/
|
|
38
|
+
export function hasDirectRecover(body) {
|
|
39
|
+
if (!body) return false;
|
|
40
|
+
if (body._hasDirectRecover != null) return Boolean(body._hasDirectRecover);
|
|
41
|
+
const walk = (node) => {
|
|
42
|
+
if (!node || typeof node !== "object") return false;
|
|
43
|
+
if (Array.isArray(node)) return node.some(walk);
|
|
44
|
+
if (node.kind === "FuncLit") return false;
|
|
45
|
+
if (
|
|
46
|
+
node.kind === "CallExpr" &&
|
|
47
|
+
node.func?.kind === "Ident" &&
|
|
48
|
+
node.func.name === "recover"
|
|
49
|
+
)
|
|
50
|
+
return true;
|
|
51
|
+
return someChild(node, walk);
|
|
52
|
+
};
|
|
53
|
+
const result = walk(body);
|
|
54
|
+
body._hasDirectRecover = result;
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const BUILTIN_FUNC_NAMES = new Set([
|
|
59
|
+
"println",
|
|
60
|
+
"print",
|
|
61
|
+
"panic",
|
|
62
|
+
"recover",
|
|
63
|
+
"make",
|
|
64
|
+
"new",
|
|
65
|
+
"len",
|
|
66
|
+
"cap",
|
|
67
|
+
"append",
|
|
68
|
+
"copy",
|
|
69
|
+
"delete",
|
|
70
|
+
"close",
|
|
71
|
+
"clear",
|
|
72
|
+
"min",
|
|
73
|
+
"max",
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
export function normalizeDefers(programOrPrograms) {
|
|
77
|
+
defargCounter = 0;
|
|
78
|
+
const programs = Array.isArray(programOrPrograms)
|
|
79
|
+
? programOrPrograms
|
|
80
|
+
: [programOrPrograms];
|
|
81
|
+
|
|
82
|
+
const pkgFuncNames = new Set(BUILTIN_FUNC_NAMES);
|
|
83
|
+
// Package-level funcs/methods whose body calls recover() directly. A
|
|
84
|
+
// `defer f()` whose callee is one of these must keep recover armed when the
|
|
85
|
+
// synthesized wrapper forwards the call.
|
|
86
|
+
const recoverFuncs = new Set();
|
|
87
|
+
for (const program of programs) {
|
|
88
|
+
for (const d of program?.decls ?? []) {
|
|
89
|
+
if (d.kind === "FuncDecl") pkgFuncNames.add(d.name);
|
|
90
|
+
if (
|
|
91
|
+
(d.kind === "FuncDecl" || d.kind === "MethodDecl") &&
|
|
92
|
+
hasDirectRecover(d.body)
|
|
93
|
+
)
|
|
94
|
+
recoverFuncs.add(d.name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const ctx = { pkgFuncNames, recoverFuncs };
|
|
98
|
+
|
|
99
|
+
for (const program of programs) {
|
|
100
|
+
if (!program?.decls) continue;
|
|
101
|
+
for (const d of program.decls) {
|
|
102
|
+
if (d.body) normalizeBlockDefers(d.body, ctx);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeBlockDefers(node, ctx) {
|
|
108
|
+
if (!node || typeof node !== "object") return;
|
|
109
|
+
if (Array.isArray(node)) {
|
|
110
|
+
for (let i = 0; i < node.length; i++) {
|
|
111
|
+
const item = node[i];
|
|
112
|
+
if (item && item.kind === "DeferStmt") {
|
|
113
|
+
const inserted = normalizeDeferStmt(item, node, i, ctx);
|
|
114
|
+
i += inserted;
|
|
115
|
+
} else {
|
|
116
|
+
normalizeBlockDefers(item, ctx);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (node.kind === "FuncLit") {
|
|
123
|
+
normalizeBlockDefers(node.body, ctx);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
forEachChild(node, (child) => normalizeBlockDefers(child, ctx));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Does the deferred callee possibly call recover() directly? Known package
|
|
131
|
+
// funcs/methods are looked up; unknown callees (closure variables, func
|
|
132
|
+
// values) are assumed to, so recover stays armed through the wrapper.
|
|
133
|
+
function deferCalleeMayRecover(funcExpr, ctx) {
|
|
134
|
+
if (!ctx) return false;
|
|
135
|
+
if (funcExpr.kind === "Ident") {
|
|
136
|
+
if (BUILTIN_FUNC_NAMES.has(funcExpr.name)) return false;
|
|
137
|
+
if (ctx.pkgFuncNames.has(funcExpr.name))
|
|
138
|
+
return ctx.recoverFuncs.has(funcExpr.name);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
if (funcExpr.kind === "SelectorExpr") {
|
|
142
|
+
if (funcExpr.expr?._type?.kind === "namespace") return false;
|
|
143
|
+
return ctx.recoverFuncs.has(funcExpr.field);
|
|
144
|
+
}
|
|
145
|
+
if (funcExpr.kind === "FuncLit") return hasDirectRecover(funcExpr.body);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
|
|
150
|
+
if (stmt.call?.kind !== "CallExpr") return 0;
|
|
151
|
+
const call = stmt.call;
|
|
152
|
+
const isZeroArgFuncLit =
|
|
153
|
+
call.func?.kind === "FuncLit" && (!call.args || call.args.length === 0);
|
|
154
|
+
if (isZeroArgFuncLit) {
|
|
155
|
+
// The FuncLit itself is the deferred function: recover() inside it is
|
|
156
|
+
// honoured.
|
|
157
|
+
call.func._isDeferTarget = true;
|
|
158
|
+
normalizeBlockDefers(call.func.body, ctx);
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
const pkgFuncNames = ctx?.pkgFuncNames ?? null;
|
|
162
|
+
|
|
163
|
+
const tempStmts = [];
|
|
164
|
+
let funcExpr = call.func;
|
|
165
|
+
|
|
166
|
+
// Hoist method receiver if call.func is a method call (e.g. defer r.Close()).
|
|
167
|
+
// Skipped for pointer-receiver methods on an addressable (non-pointer)
|
|
168
|
+
// operand: Go evaluates `&recv` at defer time, so the deferred call must
|
|
169
|
+
// observe later mutations of the original variable — copying the struct
|
|
170
|
+
// into a temp would break that. The receiver expression itself is then
|
|
171
|
+
// re-evaluated at run time, which matches Go for the common `defer x.M()`
|
|
172
|
+
// / `defer s.f.M()` cases.
|
|
173
|
+
const recvIsAddressableValue =
|
|
174
|
+
call.func?._type?._ptrRecv === true &&
|
|
175
|
+
call.func.expr?._type?.kind !== "pointer";
|
|
176
|
+
if (
|
|
177
|
+
parentList &&
|
|
178
|
+
index >= 0 &&
|
|
179
|
+
call.func?.kind === "SelectorExpr" &&
|
|
180
|
+
call.func.expr?._type?.kind !== "namespace" &&
|
|
181
|
+
!recvIsAddressableValue
|
|
182
|
+
) {
|
|
183
|
+
const recv = call.func.expr;
|
|
184
|
+
defargCounter++;
|
|
185
|
+
const recvName = `__defrecv$${defargCounter}`;
|
|
186
|
+
const recvIdent = {
|
|
187
|
+
kind: "Ident",
|
|
188
|
+
name: recvName,
|
|
189
|
+
_type: recv._type,
|
|
190
|
+
};
|
|
191
|
+
tempStmts.push({
|
|
192
|
+
kind: "DefineStmt",
|
|
193
|
+
lhs: [{ kind: "Ident", name: recvName, _type: recv._type }],
|
|
194
|
+
rhs: [recv],
|
|
195
|
+
});
|
|
196
|
+
funcExpr = {
|
|
197
|
+
...call.func,
|
|
198
|
+
expr: recvIdent,
|
|
199
|
+
};
|
|
200
|
+
} else if (
|
|
201
|
+
parentList &&
|
|
202
|
+
index >= 0 &&
|
|
203
|
+
call.func?.kind === "Ident" &&
|
|
204
|
+
pkgFuncNames &&
|
|
205
|
+
!pkgFuncNames.has(call.func.name)
|
|
206
|
+
) {
|
|
207
|
+
// Hoist local function variable (e.g. fn := f1; defer fn(); fn = f2)
|
|
208
|
+
defargCounter++;
|
|
209
|
+
const fnName = `__deffn$${defargCounter}`;
|
|
210
|
+
const fnIdent = {
|
|
211
|
+
kind: "Ident",
|
|
212
|
+
name: fnName,
|
|
213
|
+
_type: call.func._type,
|
|
214
|
+
};
|
|
215
|
+
tempStmts.push({
|
|
216
|
+
kind: "DefineStmt",
|
|
217
|
+
lhs: [{ kind: "Ident", name: fnName, _type: call.func._type }],
|
|
218
|
+
rhs: [call.func],
|
|
219
|
+
});
|
|
220
|
+
funcExpr = fnIdent;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const args = call.args ?? [];
|
|
224
|
+
const tempIdents = [];
|
|
225
|
+
|
|
226
|
+
if (parentList && index >= 0 && args.length > 0) {
|
|
227
|
+
for (let a = 0; a < args.length; a++) {
|
|
228
|
+
const arg = args[a];
|
|
229
|
+
if (arg._type?.kind === "tuple" && Array.isArray(arg._type.types)) {
|
|
230
|
+
const tupleTypes = arg._type.types;
|
|
231
|
+
const tupleLhs = [];
|
|
232
|
+
for (let t = 0; t < tupleTypes.length; t++) {
|
|
233
|
+
defargCounter++;
|
|
234
|
+
const tempName = `__defarg$${defargCounter}`;
|
|
235
|
+
const ident = {
|
|
236
|
+
kind: "Ident",
|
|
237
|
+
name: tempName,
|
|
238
|
+
_type: tupleTypes[t],
|
|
239
|
+
};
|
|
240
|
+
tempIdents.push(ident);
|
|
241
|
+
tupleLhs.push(ident);
|
|
242
|
+
}
|
|
243
|
+
tempStmts.push({
|
|
244
|
+
kind: "DefineStmt",
|
|
245
|
+
lhs: tupleLhs,
|
|
246
|
+
rhs: [arg],
|
|
247
|
+
});
|
|
248
|
+
} else {
|
|
249
|
+
defargCounter++;
|
|
250
|
+
const tempName = `__defarg$${defargCounter}`;
|
|
251
|
+
tempIdents.push({
|
|
252
|
+
kind: "Ident",
|
|
253
|
+
name: tempName,
|
|
254
|
+
_type: arg._type,
|
|
255
|
+
});
|
|
256
|
+
tempStmts.push({
|
|
257
|
+
kind: "DefineStmt",
|
|
258
|
+
lhs: [{ kind: "Ident", name: tempName, _type: arg._type }],
|
|
259
|
+
rhs: [arg],
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (parentList && index >= 0 && tempStmts.length > 0) {
|
|
266
|
+
parentList.splice(index, 0, ...tempStmts);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const callArgs = tempIdents.length > 0 ? tempIdents : args;
|
|
270
|
+
const innerCall = {
|
|
271
|
+
...call,
|
|
272
|
+
func: funcExpr,
|
|
273
|
+
args: callArgs,
|
|
274
|
+
_multiForward: tempIdents.length > 0 ? false : call._multiForward,
|
|
275
|
+
};
|
|
276
|
+
const synthFuncLit = {
|
|
277
|
+
kind: "FuncLit",
|
|
278
|
+
params: [],
|
|
279
|
+
returnType: null,
|
|
280
|
+
body: {
|
|
281
|
+
kind: "Block",
|
|
282
|
+
stmts: [{ kind: "ExprStmt", expr: innerCall }],
|
|
283
|
+
list: [{ kind: "ExprStmt", expr: innerCall }],
|
|
284
|
+
_hasDefer: false,
|
|
285
|
+
},
|
|
286
|
+
_type: { kind: "FuncType", params: [], returnType: null },
|
|
287
|
+
_isDeferTarget: true,
|
|
288
|
+
// The wrapper only forwards to the real deferred callee; when that
|
|
289
|
+
// callee may call recover() directly, recover must stay armed across
|
|
290
|
+
// the wrapper frame.
|
|
291
|
+
_deferForward: deferCalleeMayRecover(call.func, ctx),
|
|
292
|
+
};
|
|
293
|
+
if (call.func?.kind === "FuncLit") normalizeBlockDefers(call.func.body, ctx);
|
|
294
|
+
stmt.call = {
|
|
295
|
+
kind: "CallExpr",
|
|
296
|
+
func: synthFuncLit,
|
|
297
|
+
args: [],
|
|
298
|
+
_type: { kind: "basic", name: "void" },
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
return tempStmts.length;
|
|
302
|
+
}
|
package/src/lower/globals.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// and computes functions that participate in the package initialization call path.
|
|
4
4
|
|
|
5
5
|
import { isReferenceType } from "./boxing.js";
|
|
6
|
+
import { forEachChild, someChild } from "./walk.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Computes:
|
|
@@ -117,11 +118,9 @@ function scanReassignments(node, packageGlobals, reassigned, insideInit) {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
checkAddressTaken(node, packageGlobals, reassigned);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
}
|
|
121
|
+
forEachChild(node, (child) =>
|
|
122
|
+
scanReassignments(child, packageGlobals, reassigned, insideInit),
|
|
123
|
+
);
|
|
125
124
|
}
|
|
126
125
|
|
|
127
126
|
function addCallTarget(call, called) {
|
|
@@ -145,9 +144,7 @@ function findCalledFunctions(node, called = new Set()) {
|
|
|
145
144
|
return called;
|
|
146
145
|
}
|
|
147
146
|
if (node.kind === "CallExpr") addCallTarget(node, called);
|
|
148
|
-
|
|
149
|
-
if (!k.startsWith("_")) findCalledFunctions(node[k], called);
|
|
150
|
-
}
|
|
147
|
+
forEachChild(node, (child) => findCalledFunctions(child, called));
|
|
151
148
|
return called;
|
|
152
149
|
}
|
|
153
150
|
|
|
@@ -244,9 +241,7 @@ function scanReadIdents(node, readSet) {
|
|
|
244
241
|
readSet.add(node.name);
|
|
245
242
|
return;
|
|
246
243
|
}
|
|
247
|
-
|
|
248
|
-
if (!k.startsWith("_")) scanReadIdents(node[k], readSet);
|
|
249
|
-
}
|
|
244
|
+
forEachChild(node, (child) => scanReadIdents(child, readSet));
|
|
250
245
|
}
|
|
251
246
|
|
|
252
247
|
function isLocallyDeclared(node, name) {
|
|
@@ -265,10 +260,7 @@ function isLocallyDeclared(node, name) {
|
|
|
265
260
|
return true;
|
|
266
261
|
}
|
|
267
262
|
}
|
|
268
|
-
|
|
269
|
-
if (!k.startsWith("_") && isLocallyDeclared(node[k], name)) return true;
|
|
270
|
-
}
|
|
271
|
-
return false;
|
|
263
|
+
return someChild(node, (child) => isLocallyDeclared(child, name));
|
|
272
264
|
}
|
|
273
265
|
|
|
274
266
|
function getGlobalsToCache(fn, neverAssignedGlobals, initPathFunctions) {
|
package/src/lower/index.js
CHANGED
|
@@ -9,16 +9,17 @@ export * from "./functions.js";
|
|
|
9
9
|
export * from "./globals.js";
|
|
10
10
|
export * from "./ownership.js";
|
|
11
11
|
export * from "./range.js";
|
|
12
|
+
export * from "./walk.js";
|
|
12
13
|
|
|
13
14
|
import { scanAddressTaken } from "./boxing.js";
|
|
14
15
|
import { analyzeCaptures } from "./captures.js";
|
|
15
16
|
import { computeEmbeddedStubs } from "./embedding.js";
|
|
16
17
|
import { paramEscapes } from "./escape.js";
|
|
17
|
-
import { extractNamedReturns, hasDefer } from "./functions.js";
|
|
18
|
+
import { extractNamedReturns, hasDefer, normalizeDefers } from "./functions.js";
|
|
18
19
|
import { computeGlobalAnalysis } from "./globals.js";
|
|
19
20
|
import { OwnershipContext } from "./ownership.js";
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
class LowerResult {
|
|
22
23
|
constructor() {
|
|
23
24
|
// Map<ASTNode (FuncDecl, MethodDecl, FuncLit), Set<string>>
|
|
24
25
|
this.boxedVars = new Map();
|
|
@@ -53,6 +54,8 @@ export function lower(programOrPrograms, checker = null) {
|
|
|
53
54
|
? programOrPrograms
|
|
54
55
|
: [programOrPrograms];
|
|
55
56
|
|
|
57
|
+
normalizeDefers(programs);
|
|
58
|
+
|
|
56
59
|
// 1. Aggregate methods across all files in the package
|
|
57
60
|
const methodMap = new Map();
|
|
58
61
|
for (const program of programs) {
|
package/src/lower/ownership.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/lower/ownership.js
|
|
2
2
|
// Tracks variable ownership, mutation and clone elision for Go value semantics.
|
|
3
3
|
|
|
4
|
+
import { forEachChild, someChild } from "./walk.js";
|
|
5
|
+
|
|
4
6
|
export function rootIdentName(e) {
|
|
5
7
|
while (e) {
|
|
6
8
|
if (e.kind === "Ident") return e.name;
|
|
@@ -42,11 +44,7 @@ export function nodeMutatesVar(node, name, addrOnly = false) {
|
|
|
42
44
|
)
|
|
43
45
|
return true;
|
|
44
46
|
if (!addrOnly && nodeWritesVar(node, name)) return true;
|
|
45
|
-
|
|
46
|
-
if (key.startsWith("_")) continue;
|
|
47
|
-
if (nodeMutatesVar(node[key], name, addrOnly)) return true;
|
|
48
|
-
}
|
|
49
|
-
return false;
|
|
47
|
+
return someChild(node, (child) => nodeMutatesVar(child, name, addrOnly));
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
// Single-pass collector of all mutated or address-taken variables in an AST node.
|
|
@@ -85,10 +83,7 @@ export function collectMutatedVars(
|
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
if (key.startsWith("_")) continue;
|
|
90
|
-
collectMutatedVars(node[key], mutated, addrOnly);
|
|
91
|
-
}
|
|
86
|
+
forEachChild(node, (child) => collectMutatedVars(child, mutated, addrOnly));
|
|
92
87
|
return mutated;
|
|
93
88
|
}
|
|
94
89
|
|
|
@@ -112,7 +107,7 @@ export function closureMutates(body, name, cache = null) {
|
|
|
112
107
|
if (!node || typeof node !== "object") return false;
|
|
113
108
|
if (Array.isArray(node)) return node.some(walk);
|
|
114
109
|
if (node.kind === "FuncLit") return nodeMutatesVar(node.body, name);
|
|
115
|
-
return
|
|
110
|
+
return someChild(node, walk);
|
|
116
111
|
};
|
|
117
112
|
const res = walk(body);
|
|
118
113
|
if (cache) cache.set(key, res);
|
package/src/lower/range.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Classifies for-range loop shapes and variable scoping/redelcarations.
|
|
3
3
|
|
|
4
4
|
import { isNumeric } from "../typechecker/types.js";
|
|
5
|
+
import { someChild } from "./walk.js";
|
|
5
6
|
|
|
6
7
|
export function isRangeFor(stmt) {
|
|
7
8
|
if (!stmt?.init) return false;
|
|
@@ -40,11 +41,7 @@ export function nodeAssigns(node, name) {
|
|
|
40
41
|
node.expr.name === name)
|
|
41
42
|
)
|
|
42
43
|
return true;
|
|
43
|
-
|
|
44
|
-
if (key.startsWith("_")) continue;
|
|
45
|
-
if (nodeAssigns(node[key], name)) return true;
|
|
46
|
-
}
|
|
47
|
-
return false;
|
|
44
|
+
return someChild(node, (child) => nodeAssigns(child, name));
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
// True when the body re-declares one of `names` at its top level (legal Go shadowing).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/lower/walk.js
|
|
2
|
+
// Shared AST child traversal for the lowering passes. Side-table fields
|
|
3
|
+
// (`_type`, `_boxed`, …) start with "_" and are never AST children.
|
|
4
|
+
|
|
5
|
+
/** True for a side-table key that traversal must skip. */
|
|
6
|
+
function isSideTableKey(key) {
|
|
7
|
+
return key.charCodeAt(0) === 95; // "_"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Calls `fn(child)` for every non-side-table property of `node`. */
|
|
11
|
+
export function forEachChild(node, fn) {
|
|
12
|
+
for (const key of Object.keys(node)) {
|
|
13
|
+
if (isSideTableKey(key)) continue;
|
|
14
|
+
fn(node[key]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** True when `pred(child)` holds for some non-side-table property of `node`. */
|
|
19
|
+
export function someChild(node, pred) {
|
|
20
|
+
for (const key of Object.keys(node)) {
|
|
21
|
+
if (isSideTableKey(key)) continue;
|
|
22
|
+
if (pred(node[key])) return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
package/src/resolver.js
CHANGED
|
@@ -49,6 +49,7 @@ const BUILTIN_PACKAGES = new Set([
|
|
|
49
49
|
"path",
|
|
50
50
|
"path/filepath",
|
|
51
51
|
"testing",
|
|
52
|
+
"gofront/shared",
|
|
52
53
|
]);
|
|
53
54
|
|
|
54
55
|
// Returns true for local relative paths (start with ./ or ../)
|
|
@@ -72,7 +73,7 @@ export function resolveGwDir(importPath, fromFile) {
|
|
|
72
73
|
return gwFiles.length > 0 ? dir : null;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
function resolveImport(importPath, fromFile) {
|
|
76
77
|
const fromDir = dirname(resolve(fromFile));
|
|
77
78
|
const nodeModules = findNodeModules(fromDir);
|
|
78
79
|
|
package/src/test-runner.js
CHANGED
|
@@ -14,6 +14,19 @@ import {
|
|
|
14
14
|
gwFilesIn,
|
|
15
15
|
} from "./compiler.js";
|
|
16
16
|
|
|
17
|
+
// WasmGC + exception handling (what the wasm backend emits) ship enabled by
|
|
18
|
+
// default from Node 22 (V8 11.9+). Older runtimes fail at instantiate time
|
|
19
|
+
// with an opaque CompileError, so check up front and explain.
|
|
20
|
+
export const MIN_WASM_NODE_MAJOR = 22;
|
|
21
|
+
|
|
22
|
+
export function checkWasmRuntime(version = process.version) {
|
|
23
|
+
const major = Number.parseInt(version.replace(/^v/, ""), 10);
|
|
24
|
+
if (Number.isNaN(major) || major >= MIN_WASM_NODE_MAJOR) return;
|
|
25
|
+
throw new Error(
|
|
26
|
+
`running wasm packages requires Node ${MIN_WASM_NODE_MAJOR}+ (WasmGC); found ${version}\n hint: upgrade Node, or compile with --js-only to run the JS backend instead`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
export function isTestFunc(decl) {
|
|
18
31
|
if (decl.kind !== "FuncDecl") return false;
|
|
19
32
|
if (!decl.name.startsWith("Test")) return false;
|
|
@@ -382,6 +395,13 @@ export async function runTests(targetDir, options = {}) {
|
|
|
382
395
|
|
|
383
396
|
// `wasm` packages run inside the linked app.wasm; `js` packages that import
|
|
384
397
|
// wasm packages need the linked module too (hybrid packages).
|
|
398
|
+
if (compiled.target === "both" || compiled.wasm) {
|
|
399
|
+
try {
|
|
400
|
+
checkWasmRuntime();
|
|
401
|
+
} catch (err) {
|
|
402
|
+
return reportBuildFailure(err, resolvedDir, options);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
385
405
|
if (compiled.target === "both") {
|
|
386
406
|
return runDualTarget(compiled, resolvedDir, runHarness, options);
|
|
387
407
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Assignability, binary result types, and interface satisfaction.
|
|
2
2
|
// Installed as a mixin on TypeChecker.prototype.
|
|
3
3
|
|
|
4
|
+
import { sharedInfo } from "./stdlib/shared.js";
|
|
4
5
|
import {
|
|
5
6
|
ANY,
|
|
6
7
|
BOOL,
|
|
@@ -159,6 +160,7 @@ export const assignabilityMethods = {
|
|
|
159
160
|
target = this.resolveType(target);
|
|
160
161
|
source = this.resolveType(source);
|
|
161
162
|
this._markIfaceBox(target, source, node);
|
|
163
|
+
this._checkWasmIfaceProvider(target, source, node);
|
|
162
164
|
if (this._assertAssignableEarlyReturn(target, source)) return;
|
|
163
165
|
if (isPointer(target) && isPointer(source)) {
|
|
164
166
|
this.assertAssignable(
|
|
@@ -200,6 +202,24 @@ export const assignabilityMethods = {
|
|
|
200
202
|
node._ifaceBox = "ptr";
|
|
201
203
|
},
|
|
202
204
|
|
|
205
|
+
// A js package cannot provide an implementation of a wasm interface: the
|
|
206
|
+
// value would cross into wasm, and JS-implemented proxies are not supported.
|
|
207
|
+
_checkWasmIfaceProvider(target, source, node) {
|
|
208
|
+
if (this.target !== "js" || target?.kind !== "named") return;
|
|
209
|
+
if (target._target !== "wasm" || !source) return;
|
|
210
|
+
const iface = this.resolveType(target.underlying);
|
|
211
|
+
if (iface?.kind !== "interface" || !(iface.methods?.size > 0)) return;
|
|
212
|
+
const ptr = source.kind === "pointer";
|
|
213
|
+
const src = ptr ? this.resolveType(source.base) : source;
|
|
214
|
+
if (src?.kind !== "named" || src._target === "wasm") return;
|
|
215
|
+
if (this.resolveType(src.underlying)?.kind === "interface") return;
|
|
216
|
+
if (!this.implements(source, iface, node)) return;
|
|
217
|
+
this.err(
|
|
218
|
+
`type '${ptr ? "*" : ""}${src.name}' (${src._target ?? "js"}) cannot implement wasm interface '${target.name}' across the boundary; implement it in a wasm package or pass a func callback`,
|
|
219
|
+
node,
|
|
220
|
+
);
|
|
221
|
+
},
|
|
222
|
+
|
|
203
223
|
_assertAssignableEarlyReturn(target, source) {
|
|
204
224
|
return (
|
|
205
225
|
target?.kind === "typeParam" ||
|
|
@@ -222,6 +242,23 @@ export const assignabilityMethods = {
|
|
|
222
242
|
);
|
|
223
243
|
return;
|
|
224
244
|
}
|
|
245
|
+
const sBase = this.resolveType(
|
|
246
|
+
source.kind === "named" ? source.underlying : source,
|
|
247
|
+
);
|
|
248
|
+
if (sharedInfo(source) && tBase?.kind === "slice") {
|
|
249
|
+
this.err(
|
|
250
|
+
`cannot use ${typeStr(source)} as ${typeStr(target)}: a linear-memory buffer cannot be passed as a GC slice without an explicit copy`,
|
|
251
|
+
node,
|
|
252
|
+
);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (tBase?.kind === "shared" && sBase?.kind === "slice") {
|
|
256
|
+
this.err(
|
|
257
|
+
`cannot use ${typeStr(source)} as ${typeStr(target)}: a GC slice cannot be used as a shared buffer; copy its elements into one allocated with shared.New${tBase.shared}`,
|
|
258
|
+
node,
|
|
259
|
+
);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
225
262
|
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
226
263
|
},
|
|
227
264
|
|
|
@@ -346,6 +383,7 @@ export const assignabilityMethods = {
|
|
|
346
383
|
},
|
|
347
384
|
|
|
348
385
|
implements(srcType, iface, _node) {
|
|
386
|
+
const isPtr = srcType?.kind === "pointer";
|
|
349
387
|
// *T has the full method set of T (value + pointer receivers).
|
|
350
388
|
if (srcType?.kind === "pointer") srcType = this.resolveType(srcType.base);
|
|
351
389
|
let base = srcType.kind === "named" ? srcType.underlying : srcType;
|
|
@@ -366,7 +404,10 @@ export const assignabilityMethods = {
|
|
|
366
404
|
: null;
|
|
367
405
|
if (!methodMap) return false;
|
|
368
406
|
for (const [name, required] of iface.methods) {
|
|
369
|
-
|
|
407
|
+
const actual = methodMap.get(name);
|
|
408
|
+
if (!actual) return false;
|
|
409
|
+
if (!isPtr && actual._ptrRecv) return false;
|
|
410
|
+
if (!this._implementsMethod(required, actual)) return false;
|
|
370
411
|
}
|
|
371
412
|
return true;
|
|
372
413
|
},
|