gofront 1.4.0 → 1.5.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.
Files changed (61) hide show
  1. package/README.md +72 -9
  2. package/package.json +2 -1
  3. package/src/{codegen → backend/js}/expressions.js +217 -60
  4. package/src/{codegen → backend/js}/index.js +21 -35
  5. package/src/{codegen → backend/js}/statements.js +43 -48
  6. package/src/backend/js/stdlib/bits.js +32 -0
  7. package/src/{codegen → backend/js}/stdlib/index.js +3 -0
  8. package/src/{codegen → backend/js}/templ.js +2 -2
  9. package/src/backend/wasm/boundary.js +1345 -0
  10. package/src/backend/wasm/emit.js +5779 -0
  11. package/src/backend/wasm/encode.js +789 -0
  12. package/src/backend/wasm/glue.js +130 -0
  13. package/src/backend/wasm/index.js +1413 -0
  14. package/src/backend/wasm/types.js +420 -0
  15. package/src/backend/wasm/wat.js +255 -0
  16. package/src/cli-core.js +52 -7
  17. package/src/compiler.js +322 -12
  18. package/src/dev-server.js +56 -15
  19. package/src/index.js +17 -9
  20. package/src/lexer.js +18 -0
  21. package/src/lower/boxing.js +36 -0
  22. package/src/lower/captures.js +179 -0
  23. package/src/lower/embedding.js +30 -0
  24. package/src/lower/escape.js +169 -0
  25. package/src/lower/functions.js +30 -0
  26. package/src/lower/globals.js +324 -0
  27. package/src/lower/index.js +115 -0
  28. package/src/lower/ownership.js +142 -0
  29. package/src/lower/range.js +70 -0
  30. package/src/minifier.js +2 -0
  31. package/src/parser/index.js +8 -3
  32. package/src/resolver.js +1 -0
  33. package/src/test-runner.js +74 -20
  34. package/src/typechecker/expressions.js +40 -1
  35. package/src/typechecker/index.js +234 -4
  36. package/src/typechecker/statements.js +7 -0
  37. package/src/typechecker/stdlib/core.js +17 -0
  38. package/src/typechecker/types.js +56 -2
  39. /package/src/{codegen → backend/js}/runtime.js +0 -0
  40. /package/src/{codegen → backend/js}/source-map.js +0 -0
  41. /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
  42. /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
  43. /package/src/{codegen → backend/js}/stdlib/errors.js +0 -0
  44. /package/src/{codegen → backend/js}/stdlib/fmt.js +0 -0
  45. /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
  46. /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
  47. /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
  48. /package/src/{codegen → backend/js}/stdlib/maps.js +0 -0
  49. /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
  50. /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
  51. /package/src/{codegen → backend/js}/stdlib/path.js +0 -0
  52. /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
  53. /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
  54. /package/src/{codegen → backend/js}/stdlib/slices.js +0 -0
  55. /package/src/{codegen → backend/js}/stdlib/sort.js +0 -0
  56. /package/src/{codegen → backend/js}/stdlib/strconv.js +0 -0
  57. /package/src/{codegen → backend/js}/stdlib/strings.js +0 -0
  58. /package/src/{codegen → backend/js}/stdlib/testing.js +0 -0
  59. /package/src/{codegen → backend/js}/stdlib/time.js +0 -0
  60. /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
  61. /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
@@ -0,0 +1,30 @@
1
+ // src/lower/functions.js
2
+ // Analyzes function signatures, named returns and defer structures.
3
+
4
+ export function extractNamedReturns(decl) {
5
+ const named = decl?.returnType?._namedReturns;
6
+ if (!named || named.length === 0) return null;
7
+ return {
8
+ entries: named, // [{ name, type }]
9
+ names: named.map((r) => r.name).filter(Boolean),
10
+ };
11
+ }
12
+
13
+ export function hasDefer(body) {
14
+ if (!body) return false;
15
+ if (body._hasDefer != null) return Boolean(body._hasDefer);
16
+
17
+ const walk = (node) => {
18
+ if (!node || typeof node !== "object") return false;
19
+ if (Array.isArray(node)) return node.some(walk);
20
+ if (node.kind === "DeferStmt") return true;
21
+ if (node.kind === "FuncLit") return false; // separate function boundary
22
+ for (const key of Object.keys(node)) {
23
+ if (key.startsWith("_")) continue;
24
+ if (walk(node[key])) return true;
25
+ }
26
+ return false;
27
+ };
28
+
29
+ return walk(body);
30
+ }
@@ -0,0 +1,324 @@
1
+ // src/lower/globals.js
2
+ // Identifies package-level globals that are never assigned outside init,
3
+ // and computes functions that participate in the package initialization call path.
4
+
5
+ import { isReferenceType } from "./boxing.js";
6
+
7
+ /**
8
+ * Computes:
9
+ * - neverAssignedGlobals: Set of package variable names that are never reassigned outside init.
10
+ * - initPathFunctions: Set of function/method names reachable during package initialization.
11
+ * - cachedGlobalsByFunc: Map of AST function declarations to list of global names to cache.
12
+ *
13
+ * @param {Array<Object>} programs
14
+ * @param {Object} [_checker]
15
+ * @returns {{ neverAssignedGlobals: Set<string>, initPathFunctions: Set<string>, cachedGlobalsByFunc: Map<Object, Array<string>> }}
16
+ */
17
+ export function computeGlobalAnalysis(programs, _checker = null) {
18
+ const progs = Array.isArray(programs) ? programs : [programs];
19
+ const packageGlobals = collectPackageGlobals(progs);
20
+ const reassigned = collectReassigned(progs, packageGlobals);
21
+
22
+ const neverAssignedGlobals = new Set();
23
+ for (const g of packageGlobals) {
24
+ if (!reassigned.has(g)) neverAssignedGlobals.add(g);
25
+ }
26
+
27
+ const initPathFunctions = buildInitPathFunctions(progs);
28
+ const cachedGlobalsByFunc = buildCachedGlobalsMap(
29
+ progs,
30
+ neverAssignedGlobals,
31
+ initPathFunctions,
32
+ );
33
+
34
+ return { neverAssignedGlobals, initPathFunctions, cachedGlobalsByFunc };
35
+ }
36
+
37
+ function collectVarNames(spec, out) {
38
+ const names = spec.names ?? (spec.name ? [spec.name] : []);
39
+ for (const n of names) {
40
+ const name = typeof n === "string" ? n : n?.name;
41
+ if (name && name !== "_") out.add(name);
42
+ }
43
+ }
44
+
45
+ function collectPackageGlobals(programs) {
46
+ const out = new Set();
47
+ for (const p of programs) {
48
+ for (const d of p?.decls ?? []) {
49
+ if (d.kind === "VarDecl") {
50
+ for (const spec of d.decls ?? d.specs ?? [d]) {
51
+ collectVarNames(spec, out);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ return out;
57
+ }
58
+
59
+ function collectReassigned(programs, packageGlobals) {
60
+ const reassigned = new Set();
61
+ for (const p of programs) {
62
+ for (const d of p?.decls ?? []) {
63
+ if (d.kind === "FuncDecl" || d.kind === "MethodDecl") {
64
+ const isInit =
65
+ d.kind === "FuncDecl" &&
66
+ (d.name === "init" || d.name.startsWith("init$"));
67
+ scanReassignments(d.body, packageGlobals, reassigned, isInit);
68
+ }
69
+ }
70
+ }
71
+ return reassigned;
72
+ }
73
+
74
+ function checkAssignLhs(lhs, packageGlobals, reassigned) {
75
+ for (const l of lhs ?? []) {
76
+ if (l.kind === "Ident" && packageGlobals.has(l.name)) {
77
+ reassigned.add(l.name);
78
+ }
79
+ }
80
+ }
81
+
82
+ function checkIncDec(stmt, packageGlobals, reassigned) {
83
+ if (stmt.expr?.kind === "Ident" && packageGlobals.has(stmt.expr.name)) {
84
+ reassigned.add(stmt.expr.name);
85
+ }
86
+ }
87
+
88
+ function checkAddressTaken(node, packageGlobals, reassigned) {
89
+ if (
90
+ node.kind === "Ident" &&
91
+ node._addressTaken &&
92
+ packageGlobals.has(node.name)
93
+ ) {
94
+ if (!isReferenceType(node._type)) {
95
+ reassigned.add(node.name);
96
+ }
97
+ }
98
+ }
99
+
100
+ function scanReassignments(node, packageGlobals, reassigned, insideInit) {
101
+ if (!node || typeof node !== "object") return;
102
+ if (Array.isArray(node)) {
103
+ for (const child of node) {
104
+ scanReassignments(child, packageGlobals, reassigned, insideInit);
105
+ }
106
+ return;
107
+ }
108
+ if (node.kind === "FuncLit") {
109
+ scanReassignments(node.body, packageGlobals, reassigned, false);
110
+ return;
111
+ }
112
+ if (!insideInit) {
113
+ if (node.kind === "AssignStmt" || node.kind === "CompoundAssignStmt") {
114
+ checkAssignLhs(node.lhs, packageGlobals, reassigned);
115
+ } else if (node.kind === "IncDecStmt") {
116
+ checkIncDec(node, packageGlobals, reassigned);
117
+ }
118
+ }
119
+ checkAddressTaken(node, packageGlobals, reassigned);
120
+ for (const k of Object.keys(node)) {
121
+ if (!k.startsWith("_")) {
122
+ scanReassignments(node[k], packageGlobals, reassigned, insideInit);
123
+ }
124
+ }
125
+ }
126
+
127
+ function addCallTarget(call, called) {
128
+ if (call.func?.kind === "Ident") {
129
+ called.add(call.func.name);
130
+ return;
131
+ }
132
+ if (call.func?.kind === "SelectorExpr") {
133
+ const sel = call.func.sel ?? call.func.field;
134
+ if (sel) called.add(sel);
135
+ const typeName =
136
+ call.func.expr?._type?.name ?? call.func.expr?._type?.base?.name;
137
+ if (typeName && sel) called.add(`${typeName}.${sel}`);
138
+ }
139
+ }
140
+
141
+ function findCalledFunctions(node, called = new Set()) {
142
+ if (!node || typeof node !== "object") return called;
143
+ if (Array.isArray(node)) {
144
+ for (const child of node) findCalledFunctions(child, called);
145
+ return called;
146
+ }
147
+ if (node.kind === "CallExpr") addCallTarget(node, called);
148
+ for (const k of Object.keys(node)) {
149
+ if (!k.startsWith("_")) findCalledFunctions(node[k], called);
150
+ }
151
+ return called;
152
+ }
153
+
154
+ function addCallees(callGraph, caller, callees) {
155
+ let set = callGraph.get(caller);
156
+ if (!set) {
157
+ set = new Set();
158
+ callGraph.set(caller, set);
159
+ }
160
+ for (const c of callees) set.add(c);
161
+ }
162
+
163
+ function buildInitSeeds(programs) {
164
+ const seeds = new Set(["init", "__init_globals"]);
165
+ for (const p of programs) {
166
+ for (const d of p?.decls ?? []) {
167
+ if (
168
+ d.kind === "FuncDecl" &&
169
+ (d.name === "init" || d.name.startsWith("init$"))
170
+ ) {
171
+ seeds.add(d.name);
172
+ }
173
+ if (d.kind === "VarDecl") {
174
+ for (const spec of d.decls ?? d.specs ?? [d]) {
175
+ const values = spec.value ?? (spec.init ? [spec.init] : []);
176
+ for (const val of values) findCalledFunctions(val, seeds);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ return seeds;
182
+ }
183
+
184
+ function buildCallGraph(programs) {
185
+ const callGraph = new Map();
186
+ for (const p of programs) {
187
+ for (const d of p?.decls ?? []) {
188
+ if ((d.kind === "FuncDecl" || d.kind === "MethodDecl") && d.body) {
189
+ const callees = findCalledFunctions(d.body);
190
+ addCallees(callGraph, d.name, callees);
191
+ if (d.kind === "MethodDecl" && d.recvType?.name) {
192
+ addCallees(callGraph, `${d.recvType.name}.${d.name}`, callees);
193
+ }
194
+ }
195
+ }
196
+ }
197
+ return callGraph;
198
+ }
199
+
200
+ function buildInitPathFunctions(programs) {
201
+ const seeds = buildInitSeeds(programs);
202
+ const callGraph = buildCallGraph(programs);
203
+ const initPath = new Set(seeds);
204
+ const worklist = Array.from(seeds);
205
+ while (worklist.length > 0) {
206
+ const caller = worklist.pop();
207
+ const callees = callGraph.get(caller);
208
+ if (callees) {
209
+ for (const callee of callees) {
210
+ if (!initPath.has(callee)) {
211
+ initPath.add(callee);
212
+ worklist.push(callee);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ return initPath;
218
+ }
219
+
220
+ // Idents read anywhere in the function, nested closures included: the emitter
221
+ // narrows the list per emitted body (root vs. each lifted closure).
222
+ function scanReadIdents(node, readSet) {
223
+ if (!node || typeof node !== "object") return;
224
+ if (Array.isArray(node)) {
225
+ for (const c of node) scanReadIdents(c, readSet);
226
+ return;
227
+ }
228
+ if (node.kind === "SelectorExpr") {
229
+ scanReadIdents(node.expr, readSet);
230
+ return;
231
+ }
232
+ if (node.kind === "DefineStmt") {
233
+ scanReadIdents(node.rhs, readSet);
234
+ return;
235
+ }
236
+ if (node.kind === "AssignStmt" || node.kind === "CompoundAssignStmt") {
237
+ for (const l of node.lhs ?? []) {
238
+ if (l.kind !== "Ident") scanReadIdents(l, readSet);
239
+ }
240
+ scanReadIdents(node.rhs, readSet);
241
+ return;
242
+ }
243
+ if (node.kind === "Ident" && typeof node.name === "string") {
244
+ readSet.add(node.name);
245
+ return;
246
+ }
247
+ for (const k of Object.keys(node)) {
248
+ if (!k.startsWith("_")) scanReadIdents(node[k], readSet);
249
+ }
250
+ }
251
+
252
+ function isLocallyDeclared(node, name) {
253
+ if (!node || typeof node !== "object") return false;
254
+ if (Array.isArray(node)) return node.some((c) => isLocallyDeclared(c, name));
255
+ if (node.kind === "FuncLit") return false;
256
+ if (node.kind === "DefineStmt") {
257
+ return Boolean(
258
+ node.lhs?.some((l) => l.kind === "Ident" && l.name === name),
259
+ );
260
+ }
261
+ if (node.kind === "VarDecl") {
262
+ for (const s of node.decls ?? node.specs ?? []) {
263
+ const names = s.names ?? (s.name ? [s.name] : []);
264
+ if (names.some((n) => (typeof n === "string" ? n : n.name) === name))
265
+ return true;
266
+ }
267
+ }
268
+ for (const k of Object.keys(node)) {
269
+ if (!k.startsWith("_") && isLocallyDeclared(node[k], name)) return true;
270
+ }
271
+ return false;
272
+ }
273
+
274
+ function getGlobalsToCache(fn, neverAssignedGlobals, initPathFunctions) {
275
+ if (!fn.body || neverAssignedGlobals.size === 0) return [];
276
+ if (fn._isGlobalInit || fn.name === "init" || fn.name.startsWith("init$"))
277
+ return [];
278
+ if (initPathFunctions.has(fn.name)) return [];
279
+ if (fn._methodName && initPathFunctions.has(fn._methodName)) return [];
280
+ if (
281
+ fn.recvType?.name &&
282
+ initPathFunctions.has(`${fn.recvType.name}.${fn.name}`)
283
+ )
284
+ return [];
285
+
286
+ const paramNames = new Set((fn.params ?? []).map((p) => p.name));
287
+ if (fn.recvName) paramNames.add(fn.recvName);
288
+
289
+ const reads = new Set();
290
+ scanReadIdents(fn.body, reads);
291
+
292
+ const result = [];
293
+ for (const name of Array.from(neverAssignedGlobals).sort()) {
294
+ if (
295
+ reads.has(name) &&
296
+ !paramNames.has(name) &&
297
+ !isLocallyDeclared(fn.body, name)
298
+ ) {
299
+ result.push(name);
300
+ }
301
+ }
302
+ return result;
303
+ }
304
+
305
+ function buildCachedGlobalsMap(
306
+ programs,
307
+ neverAssignedGlobals,
308
+ initPathFunctions,
309
+ ) {
310
+ const map = new Map();
311
+ for (const p of programs) {
312
+ for (const d of p?.decls ?? []) {
313
+ if ((d.kind === "FuncDecl" || d.kind === "MethodDecl") && d.body) {
314
+ const globals = getGlobalsToCache(
315
+ d,
316
+ neverAssignedGlobals,
317
+ initPathFunctions,
318
+ );
319
+ if (globals.length > 0) map.set(d, globals);
320
+ }
321
+ }
322
+ }
323
+ return map;
324
+ }
@@ -0,0 +1,115 @@
1
+ // src/lower/index.js
2
+ // Lowering step: analyzes AST and types to populate side-tables for backends (JS and WASM).
3
+
4
+ export * from "./boxing.js";
5
+ export * from "./captures.js";
6
+ export * from "./embedding.js";
7
+ export * from "./escape.js";
8
+ export * from "./functions.js";
9
+ export * from "./globals.js";
10
+ export * from "./ownership.js";
11
+ export * from "./range.js";
12
+
13
+ import { scanAddressTaken } from "./boxing.js";
14
+ import { analyzeCaptures } from "./captures.js";
15
+ import { computeEmbeddedStubs } from "./embedding.js";
16
+ import { paramEscapes } from "./escape.js";
17
+ import { extractNamedReturns, hasDefer } from "./functions.js";
18
+ import { computeGlobalAnalysis } from "./globals.js";
19
+ import { OwnershipContext } from "./ownership.js";
20
+
21
+ export class LowerResult {
22
+ constructor() {
23
+ // Map<ASTNode (FuncDecl, MethodDecl, FuncLit), Set<string>>
24
+ this.boxedVars = new Map();
25
+ // Map<ASTNode, OwnershipContext>
26
+ this.ownership = new Map();
27
+ // Map<ASTNode, Object>
28
+ this.functions = new Map();
29
+ // Map<string, Array<{ methodName: string, embedName: string }>>
30
+ this.embeddedStubs = new Map();
31
+ // Map<ASTNode, CaptureAnalysis>
32
+ this.captures = new Map();
33
+ // Map<ASTNode, Set<string>>
34
+ this.escapes = new Map();
35
+ // Set<string>
36
+ this.neverAssignedGlobals = new Set();
37
+ // Set<string>
38
+ this.initPathFunctions = new Set();
39
+ // Map<ASTNode, Array<string>>
40
+ this.cachedGlobalsByFunc = new Map();
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Runs lowering analyses across an AST or collection of programs, returning side-tables.
46
+ * @param {Object} programOrPrograms
47
+ * @param {Object} checker
48
+ * @returns {LowerResult}
49
+ */
50
+ export function lower(programOrPrograms, checker = null) {
51
+ const res = new LowerResult();
52
+ const programs = Array.isArray(programOrPrograms)
53
+ ? programOrPrograms
54
+ : [programOrPrograms];
55
+
56
+ // 1. Aggregate methods across all files in the package
57
+ const methodMap = new Map();
58
+ for (const program of programs) {
59
+ for (const d of program?.decls ?? []) {
60
+ if (d.kind === "MethodDecl" && d.recvType?.name) {
61
+ const recvTypeName = d.recvType.name;
62
+ if (!methodMap.has(recvTypeName)) methodMap.set(recvTypeName, []);
63
+ methodMap.get(recvTypeName).push(d);
64
+ }
65
+ }
66
+ }
67
+
68
+ // 2. Perform lowering passes
69
+ for (const program of programs) {
70
+ if (!program?.decls) continue;
71
+
72
+ for (const d of program.decls) {
73
+ if (d.kind === "FuncDecl" || d.kind === "MethodDecl") {
74
+ if (d.body) {
75
+ const boxed = scanAddressTaken(d.body, new Set());
76
+ res.boxedVars.set(d, boxed);
77
+ res.ownership.set(d, new OwnershipContext(d.body));
78
+ res.functions.set(d, {
79
+ hasDefer: hasDefer(d.body),
80
+ namedReturns: extractNamedReturns(d),
81
+ });
82
+ res.captures.set(d, analyzeCaptures(d));
83
+
84
+ const escapingParams = new Set();
85
+ for (const p of d.params ?? []) {
86
+ if (p.name && p.name !== "_" && paramEscapes(d.body, p.name)) {
87
+ escapingParams.add(p.name);
88
+ }
89
+ }
90
+ res.escapes.set(d, escapingParams);
91
+ }
92
+ }
93
+ }
94
+
95
+ // Compute embedded stubs for structs
96
+ if (checker) {
97
+ for (const d of program.decls) {
98
+ if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
99
+ const methods = methodMap.get(d.name) ?? [];
100
+ const stubs = computeEmbeddedStubs(d.name, methods, checker);
101
+ res.embeddedStubs.set(d.name, stubs);
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ // 3. Compute package-level global analysis
108
+ const { neverAssignedGlobals, initPathFunctions, cachedGlobalsByFunc } =
109
+ computeGlobalAnalysis(programs, checker);
110
+ res.neverAssignedGlobals = neverAssignedGlobals;
111
+ res.initPathFunctions = initPathFunctions;
112
+ res.cachedGlobalsByFunc = cachedGlobalsByFunc;
113
+
114
+ return res;
115
+ }
@@ -0,0 +1,142 @@
1
+ // src/lower/ownership.js
2
+ // Tracks variable ownership, mutation and clone elision for Go value semantics.
3
+
4
+ export function rootIdentName(e) {
5
+ while (e) {
6
+ if (e.kind === "Ident") return e.name;
7
+ if (e.kind === "SelectorExpr" || e.kind === "IndexExpr") e = e.expr;
8
+ else if (e.kind === "UnaryExpr" && e.op === "*") e = e.operand;
9
+ else return null;
10
+ }
11
+ return null;
12
+ }
13
+
14
+ // True when `node` itself (not its children) writes to variable `name`.
15
+ export function nodeWritesVar(node, name) {
16
+ const root = (e) => rootIdentName(e) === name;
17
+ switch (node.kind) {
18
+ case "AssignStmt":
19
+ return node.lhs.some(root);
20
+ case "DefineStmt":
21
+ return node.lhs.some((e) => e._redecl && e.name === name);
22
+ case "IncDecStmt":
23
+ return root(node.expr);
24
+ case "SelectorExpr":
25
+ return Boolean(
26
+ node._isMethodValue && node._type?._ptrRecv && root(node.expr),
27
+ );
28
+ default:
29
+ return false;
30
+ }
31
+ }
32
+
33
+ // True when `node` may modify (or take the address of) the value held by variable `name`.
34
+ export function nodeMutatesVar(node, name, addrOnly = false) {
35
+ if (!node || typeof node !== "object") return false;
36
+ if (Array.isArray(node))
37
+ return node.some((n) => nodeMutatesVar(n, name, addrOnly));
38
+ if (
39
+ node.kind === "UnaryExpr" &&
40
+ node.op === "&" &&
41
+ rootIdentName(node.operand) === name
42
+ )
43
+ return true;
44
+ if (!addrOnly && nodeWritesVar(node, name)) return true;
45
+ for (const key of Object.keys(node)) {
46
+ if (key.startsWith("_")) continue;
47
+ if (nodeMutatesVar(node[key], name, addrOnly)) return true;
48
+ }
49
+ return false;
50
+ }
51
+
52
+ // Single-pass collector of all mutated or address-taken variables in an AST node.
53
+ export function collectMutatedVars(
54
+ node,
55
+ mutated = new Set(),
56
+ addrOnly = false,
57
+ ) {
58
+ if (!node || typeof node !== "object") return mutated;
59
+ if (Array.isArray(node)) {
60
+ for (const item of node) collectMutatedVars(item, mutated, addrOnly);
61
+ return mutated;
62
+ }
63
+
64
+ if (node.kind === "UnaryExpr" && node.op === "&") {
65
+ const root = rootIdentName(node.operand);
66
+ if (root) mutated.add(root);
67
+ } else if (!addrOnly) {
68
+ if (node.kind === "AssignStmt") {
69
+ for (const e of node.lhs) {
70
+ const root = rootIdentName(e);
71
+ if (root) mutated.add(root);
72
+ }
73
+ } else if (node.kind === "DefineStmt") {
74
+ for (const e of node.lhs) {
75
+ if (e._redecl && e.name) mutated.add(e.name);
76
+ }
77
+ } else if (node.kind === "IncDecStmt") {
78
+ const root = rootIdentName(node.expr);
79
+ if (root) mutated.add(root);
80
+ } else if (node.kind === "SelectorExpr") {
81
+ if (node._isMethodValue && node._type?._ptrRecv) {
82
+ const root = rootIdentName(node.expr);
83
+ if (root) mutated.add(root);
84
+ }
85
+ }
86
+ }
87
+
88
+ for (const key of Object.keys(node)) {
89
+ if (key.startsWith("_")) continue;
90
+ collectMutatedVars(node[key], mutated, addrOnly);
91
+ }
92
+ return mutated;
93
+ }
94
+
95
+ export function fnMutates(body, name, addrOnly = false, cache = null) {
96
+ if (!body) return true;
97
+ const key = `${addrOnly ? "&" : ""}${name}`;
98
+ if (cache) {
99
+ if (!cache.has(key)) {
100
+ cache.set(key, nodeMutatesVar(body, name, addrOnly));
101
+ }
102
+ return cache.get(key);
103
+ }
104
+ return nodeMutatesVar(body, name, addrOnly);
105
+ }
106
+
107
+ export function closureMutates(body, name, cache = null) {
108
+ const key = `λ${name}`;
109
+ if (cache?.has(key)) return cache.get(key);
110
+
111
+ const walk = (node) => {
112
+ if (!node || typeof node !== "object") return false;
113
+ if (Array.isArray(node)) return node.some(walk);
114
+ if (node.kind === "FuncLit") return nodeMutatesVar(node.body, name);
115
+ return Object.keys(node).some((k) => !k.startsWith("_") && walk(node[k]));
116
+ };
117
+ const res = walk(body);
118
+ if (cache) cache.set(key, res);
119
+ return res;
120
+ }
121
+
122
+ export class OwnershipContext {
123
+ constructor(body) {
124
+ this.body = body;
125
+ this.mut = new Map();
126
+ this.owned = new Set();
127
+ this.borrowed = new Set();
128
+ }
129
+
130
+ markOwnership(name, owned) {
131
+ if (!name || name === "_") return;
132
+ (owned ? this.owned : this.borrowed).add(name);
133
+ }
134
+
135
+ fnMutates(name, addrOnly = false) {
136
+ return fnMutates(this.body, name, addrOnly, this.mut);
137
+ }
138
+
139
+ closureMutates(name) {
140
+ return closureMutates(this.body, name, this.mut);
141
+ }
142
+ }
@@ -0,0 +1,70 @@
1
+ // src/lower/range.js
2
+ // Classifies for-range loop shapes and variable scoping/redelcarations.
3
+
4
+ import { isNumeric } from "../typechecker/types.js";
5
+
6
+ export function isRangeFor(stmt) {
7
+ if (!stmt?.init) return false;
8
+ const init = stmt.init;
9
+ if (init.kind !== "DefineStmt" && init.kind !== "AssignStmt") return false;
10
+ return init.rhs?.[0]?.kind === "RangeExpr";
11
+ }
12
+
13
+ export function isIntRangeType(iterType) {
14
+ return isNumeric(iterType);
15
+ }
16
+
17
+ export function isMapRangeType(iterType) {
18
+ return (
19
+ iterType?.kind === "map" ||
20
+ (iterType?.kind === "named" && iterType.underlying?.kind === "map")
21
+ );
22
+ }
23
+
24
+ export function isStringRangeType(iterType) {
25
+ return (
26
+ (iterType?.kind === "basic" && iterType.name === "string") ||
27
+ (iterType?.kind === "untyped" && iterType.base === "string")
28
+ );
29
+ }
30
+
31
+ // True when `node` contains an assignment or ++/-- targeting identifier `name`.
32
+ export function nodeAssigns(node, name) {
33
+ if (!node || typeof node !== "object") return false;
34
+ if (Array.isArray(node)) return node.some((n) => nodeAssigns(n, name));
35
+ if (
36
+ (node.kind === "AssignStmt" &&
37
+ node.lhs.some((e) => e.kind === "Ident" && e.name === name)) ||
38
+ (node.kind === "IncDecStmt" &&
39
+ node.expr.kind === "Ident" &&
40
+ node.expr.name === name)
41
+ )
42
+ return true;
43
+ for (const key of Object.keys(node)) {
44
+ if (key.startsWith("_")) continue;
45
+ if (nodeAssigns(node[key], name)) return true;
46
+ }
47
+ return false;
48
+ }
49
+
50
+ // True when the body re-declares one of `names` at its top level (legal Go shadowing).
51
+ export function bodyRedeclares(body, names) {
52
+ if (!names || names.length === 0) return false;
53
+ for (const s of body?.stmts ?? []) {
54
+ if (s.kind === "DefineStmt" && s.lhs.some((e) => names.includes(e.name)))
55
+ return true;
56
+ if (
57
+ (s.kind === "VarDecl" || s.kind === "ConstDecl") &&
58
+ s.decls?.some((d) => d.names?.some((n) => names.includes(n)))
59
+ )
60
+ return true;
61
+ }
62
+ return false;
63
+ }
64
+
65
+ // Whether `for i := range n` can be emitted as a plain `for (let i = 0; …)` without hidden registers.
66
+ export function isSimpleIntRange(stmt, name, isAssign, boxedVars = new Set()) {
67
+ if (isAssign) return false;
68
+ if (!name || name === "_") return true;
69
+ return !boxedVars.has(name) && !nodeAssigns(stmt.body, name);
70
+ }
package/src/minifier.js CHANGED
@@ -219,6 +219,8 @@ function _scanNumber(code, i, len) {
219
219
  } else {
220
220
  j = _scanDecimalNumber(code, j, len);
221
221
  }
222
+ // BigInt literal suffix (`123n`)
223
+ if (j < len && code[j] === "n") j++;
222
224
  return j;
223
225
  }
224
226