gofront 1.4.0 → 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.
Files changed (77) hide show
  1. package/README.md +174 -403
  2. package/package.json +16 -19
  3. package/src/{codegen → backend/js}/expressions.js +265 -77
  4. package/src/{codegen → backend/js}/index.js +120 -89
  5. package/src/{codegen → backend/js}/runtime.js +102 -0
  6. package/src/{codegen → backend/js}/source-map.js +32 -0
  7. package/src/{codegen → backend/js}/statements.js +139 -64
  8. package/src/backend/js/stdlib/bits.js +32 -0
  9. package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
  10. package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
  11. package/src/{codegen → backend/js}/stdlib/index.js +6 -0
  12. package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
  13. package/src/{codegen → backend/js}/stdlib/path.js +2 -2
  14. package/src/backend/js/stdlib/shared.js +17 -0
  15. package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
  16. package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
  17. package/src/backend/js/stdlib/strconv.js +58 -0
  18. package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
  19. package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
  20. package/src/{codegen → backend/js}/stdlib/time.js +3 -3
  21. package/src/{codegen → backend/js}/templ.js +3 -3
  22. package/src/backend/wasm/boundary.js +1798 -0
  23. package/src/backend/wasm/emit-builtins.js +1103 -0
  24. package/src/backend/wasm/emit-exprs.js +1908 -0
  25. package/src/backend/wasm/emit-maps.js +1863 -0
  26. package/src/backend/wasm/emit-shared.js +519 -0
  27. package/src/backend/wasm/emit-stdlib.js +3006 -0
  28. package/src/backend/wasm/emit-stmts.js +2472 -0
  29. package/src/backend/wasm/emit.js +1376 -0
  30. package/src/backend/wasm/encode.js +937 -0
  31. package/src/backend/wasm/glue.js +144 -0
  32. package/src/backend/wasm/index.js +2790 -0
  33. package/src/backend/wasm/monomorph.js +711 -0
  34. package/src/backend/wasm/optimize.js +227 -0
  35. package/src/backend/wasm/types.js +544 -0
  36. package/src/backend/wasm/wat.js +302 -0
  37. package/src/cli-core.js +323 -75
  38. package/src/compiler.js +447 -14
  39. package/src/dev-server.js +56 -15
  40. package/src/index.js +99 -120
  41. package/src/lexer.js +18 -0
  42. package/src/lower/boxing.js +35 -0
  43. package/src/lower/captures.js +172 -0
  44. package/src/lower/embedding.js +30 -0
  45. package/src/lower/escape.js +164 -0
  46. package/src/lower/functions.js +302 -0
  47. package/src/lower/globals.js +316 -0
  48. package/src/lower/index.js +118 -0
  49. package/src/lower/ownership.js +137 -0
  50. package/src/lower/range.js +67 -0
  51. package/src/lower/walk.js +25 -0
  52. package/src/minifier.js +2 -0
  53. package/src/parser/index.js +8 -3
  54. package/src/resolver.js +3 -1
  55. package/src/test-runner.js +94 -20
  56. package/src/typechecker/assignability.js +42 -1
  57. package/src/typechecker/expressions.js +182 -3
  58. package/src/typechecker/index.js +261 -5
  59. package/src/typechecker/resolve.js +35 -6
  60. package/src/typechecker/stdlib/core.js +34 -19
  61. package/src/typechecker/stdlib/extended.js +18 -3
  62. package/src/typechecker/stdlib/shared.js +53 -0
  63. package/src/typechecker/stdlib.js +2 -0
  64. package/src/typechecker/types.js +259 -6
  65. package/src/vendor.js +2 -2
  66. package/src/codegen/stdlib/strconv.js +0 -33
  67. /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
  68. /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
  69. /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
  70. /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
  71. /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
  72. /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
  73. /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
  74. /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
  75. /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
  76. /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
  77. /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
@@ -25,9 +25,18 @@ import {
25
25
  TAINTED_ANY,
26
26
  TypeCheckError,
27
27
  typeStr,
28
+ VALID_TARGETS,
28
29
  VOID,
29
30
  } from "./types.js";
30
31
 
32
+ function rootIdentName(e) {
33
+ if (!e) return null;
34
+ if (e.kind === "Ident") return e.name;
35
+ if (e.kind === "SelectorExpr" || e.kind === "IndexExpr")
36
+ return rootIdentName(e.expr);
37
+ return null;
38
+ }
39
+
31
40
  // Re-export for consumers that import from typechecker.js
32
41
  export { TypeCheckError, typeStr };
33
42
 
@@ -36,6 +45,11 @@ export class TypeChecker {
36
45
  this.types = new Map(); // named types
37
46
  this.globals = new Scope();
38
47
  this.errors = [];
48
+ this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
49
+ this.target = "js";
50
+ this.pkgName = null;
51
+ this.blockers = new Map();
52
+ this.summary = null;
39
53
  this._currentFile = null; // filename of file currently being checked
40
54
  this._currentSource = null;
41
55
  this._loopDepth = 0; // for break/continue validation
@@ -64,7 +78,8 @@ export class TypeChecker {
64
78
  // Add an imported GoFront package as a qualified namespace.
65
79
  // e.g. addPackageNamespace('utils', symbolsMap, typesMap)
66
80
  // lets callers type-check `utils.Foo` via SelectorExpr.
67
- addPackageNamespace(pkgName, symbols, types) {
81
+ // `target` is the imported package's compile target ("js"/"wasm"/"both").
82
+ addPackageNamespace(pkgName, symbols, types, target = null) {
68
83
  const members = {};
69
84
  for (const [name, type] of symbols) members[name] = type;
70
85
  // _gofront: true marks this as a GoFront package — exported identifier rules apply
@@ -73,6 +88,7 @@ export class TypeChecker {
73
88
  name: pkgName,
74
89
  members,
75
90
  _gofront: true,
91
+ _target: target,
76
92
  });
77
93
  for (const [name, type] of types) {
78
94
  this.types.set(name, type);
@@ -117,17 +133,62 @@ export class TypeChecker {
117
133
  return new Map(this.types);
118
134
  }
119
135
 
120
- err(msg, node) {
136
+ recordBlocker(category, item = null) {
137
+ if (!this.blockers.has(category)) {
138
+ this.blockers.set(category, []);
139
+ }
140
+ this.blockers.get(category).push(item);
141
+ }
142
+
143
+ getSummary() {
144
+ if (this.blockers.size === 0) return null;
145
+ let total = 0;
146
+ const parts = [];
147
+ for (const [category, items] of this.blockers) {
148
+ const uniqueItems = [
149
+ ...new Set(items.filter((x) => x != null && x !== "")),
150
+ ];
151
+ const count = uniqueItems.length > 0 ? uniqueItems.length : items.length;
152
+ total += count;
153
+ const itemsStr =
154
+ uniqueItems.length > 0 ? ` (${uniqueItems.join(", ")})` : "";
155
+ // "gom usage" is a mass noun; everything else pluralises on the last word.
156
+ const label =
157
+ count === 1 || category === "gom usage" ? category : `${category}s`;
158
+ parts.push(`${count} ${label}${itemsStr}`);
159
+ }
160
+ const noun = total === 1 ? "blocker" : "blockers";
161
+ const what = this.target === "both" ? "both" : "wasm";
162
+ return `package '${this.pkgName}' cannot be ${what}: ${total} ${noun} — ${parts.join(", ")}`;
163
+ }
164
+
165
+ err(msg, node, hint = null) {
121
166
  const e = new TypeCheckError(
122
167
  msg,
123
168
  node,
124
169
  this._currentFile,
125
170
  this._currentSource,
171
+ hint,
126
172
  );
127
173
  this.errors.push(e);
128
174
  return TAINTED_ANY; // tainted recovery type — suppresses downstream cascade errors
129
175
  }
130
176
 
177
+ // Non-fatal diagnostic: formatted like an error but collected separately so
178
+ // the build still succeeds. The compiler prints these as warnings.
179
+ warn(msg, node, hint = null) {
180
+ const e = new TypeCheckError(
181
+ msg,
182
+ node,
183
+ this._currentFile,
184
+ this._currentSource,
185
+ hint,
186
+ );
187
+ // `log.warn` adds the "warning:" label, so strip the error one here:
188
+ // "Type error in main.go at line 3:5: msg" → "main.go at line 3:5: msg".
189
+ this.warnings.push(e.message.replace(/^Type error(?: in)?[: ]\s*/, ""));
190
+ }
191
+
131
192
  _reportUnused(scope, node) {
132
193
  for (const name of scope.unusedLocals()) {
133
194
  this.err(`'${name}' declared and not used`, node);
@@ -163,18 +224,181 @@ export class TypeChecker {
163
224
  }
164
225
  }
165
226
 
166
- // Like check() but operates over multiple programs (same-package multi-file).
167
- // All passes run across all files before moving to the next pass,
168
- // so every file sees every other file's declarations.
169
227
  checkAll(programs) {
228
+ for (const p of programs) {
229
+ const d = p._targetDirective;
230
+ if (!d) continue;
231
+ this._setCurrentFile(p);
232
+ if (!VALID_TARGETS.has(d.value)) {
233
+ this.err(
234
+ `unknown //gofront:target '${d.value}' (expected js, wasm or both)`,
235
+ d,
236
+ );
237
+ p.target = null;
238
+ if (this.target === d.value) this.target = "js";
239
+ } else if (!d.beforePackage) {
240
+ this.err("//gofront:target must appear before the package clause", d);
241
+ }
242
+ }
243
+ if (programs.length > 0) {
244
+ if (!this.target || this.target === "js") {
245
+ const pTarget = programs.find((p) => p.target)?.target;
246
+ if (pTarget) this.target = pTarget;
247
+ }
248
+ if (!this.pkgName) {
249
+ this.pkgName = programs[0].pkg?.name ?? null;
250
+ }
251
+ }
252
+
253
+ if (this.target === "wasm" || this.target === "both") {
254
+ for (const p of programs) {
255
+ if (p._filename?.endsWith(".templ")) {
256
+ this._setCurrentFile(p);
257
+ const fileName = p._filename.split("/").pop();
258
+ this.recordBlocker(".templ file", fileName);
259
+ this.err(
260
+ `.templ files are not allowed in wasm packages; move '${fileName}' to a js package`,
261
+ p.pkg,
262
+ );
263
+ }
264
+ }
265
+ }
266
+
170
267
  this._collectTypesPass(programs);
171
268
  this._collectFuncsPass(programs);
172
269
  this._promoteEmbeddedMethods();
173
270
  this._collectVarsConstsPass(programs);
174
271
  this._checkTopDeclsPass(programs);
272
+
273
+ if (this.target === "both") {
274
+ this._checkMutablePackageVars(programs);
275
+ }
276
+
277
+ if (this.blockers.size > 0) {
278
+ const summaryLine = this.getSummary();
279
+ this.summary = summaryLine;
280
+ const summaryErr = new Error(summaryLine);
281
+ summaryErr.isSummary = true;
282
+ this.errors.push(summaryErr);
283
+ }
284
+
175
285
  return this.errors;
176
286
  }
177
287
 
288
+ _checkMutablePackageVars(programs) {
289
+ const pkgVars = new Set();
290
+ for (const p of programs) {
291
+ for (const d of p.decls) {
292
+ if (d.kind === "VarDecl") {
293
+ for (const spec of d.decls) {
294
+ for (const name of spec.names) {
295
+ if (name !== "_") pkgVars.add(name);
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+ if (pkgVars.size === 0) return;
302
+
303
+ const SCOPE_KINDS = new Set([
304
+ "Block",
305
+ "FuncLit",
306
+ "ForStmt",
307
+ "IfStmt",
308
+ "SwitchStmt",
309
+ "TypeSwitchStmt",
310
+ "CaseClause",
311
+ ]);
312
+
313
+ const report = (root, node) => {
314
+ this.recordBlocker("mutable package variable", root);
315
+ this.err(
316
+ `package-level variable '${root}' is mutated; not allowed in 'both' packages (each target gets its own copy)`,
317
+ node,
318
+ );
319
+ };
320
+
321
+ const checkBody = (body, initialLocals) => {
322
+ const scopes = [new Set(initialLocals)];
323
+ const isLocal = (name) => scopes.some((s) => s.has(name));
324
+ const declare = (name) => {
325
+ if (name && name !== "_") scopes[scopes.length - 1].add(name);
326
+ };
327
+ const flag = (e, node) => {
328
+ const root = rootIdentName(e);
329
+ if (root && pkgVars.has(root) && !isLocal(root)) report(root, node);
330
+ };
331
+
332
+ const walk = (node) => {
333
+ if (!node || typeof node !== "object") return;
334
+ if (Array.isArray(node)) {
335
+ for (const item of node) walk(item);
336
+ return;
337
+ }
338
+ const opensScope = SCOPE_KINDS.has(node.kind);
339
+ if (opensScope) scopes.push(new Set());
340
+ switch (node.kind) {
341
+ case "FuncLit":
342
+ for (const p of node.params ?? []) declare(p.name);
343
+ for (const r of node.returnType?._namedReturns ?? [])
344
+ declare(r.name);
345
+ break;
346
+ case "VarDecl":
347
+ for (const spec of node.decls ?? [])
348
+ for (const name of spec.names ?? []) declare(name);
349
+ break;
350
+ case "DefineStmt":
351
+ for (const e of node.lhs ?? [])
352
+ if (e.kind === "Ident") declare(e.name);
353
+ break;
354
+ case "TypeSwitchStmt":
355
+ declare(node.assign);
356
+ break;
357
+ case "AssignStmt":
358
+ for (const e of node.lhs) flag(e, node);
359
+ break;
360
+ case "IncDecStmt":
361
+ flag(node.expr, node);
362
+ break;
363
+ case "UnaryExpr":
364
+ if (node.op === "&") flag(node.operand, node);
365
+ break;
366
+ case "SelectorExpr":
367
+ // Pointer-receiver method call or value: `G.Bump()` mutates G.
368
+ if (node._isMethodValue && node._type?._ptrRecv)
369
+ flag(node.expr, node);
370
+ break;
371
+ }
372
+ for (const key of Object.keys(node)) {
373
+ if (key.startsWith("_")) continue;
374
+ walk(node[key]);
375
+ }
376
+ if (opensScope) scopes.pop();
377
+ };
378
+ walk(body);
379
+ };
380
+
381
+ for (const p of programs) {
382
+ if (p._filename?.endsWith("_test.go")) continue;
383
+ this._currentFile = p._filename;
384
+ this._currentSource = p._source;
385
+ for (const d of p.decls) {
386
+ if ((d.kind === "FuncDecl" || d.kind === "MethodDecl") && d.body) {
387
+ if (d.name === "init" && d.kind === "FuncDecl") continue;
388
+ const locals = [];
389
+ if (d.recvName) locals.push(d.recvName);
390
+ for (const prm of d.params ?? []) if (prm.name) locals.push(prm.name);
391
+ for (const r of d.returnType?._namedReturns ?? [])
392
+ if (r.name) locals.push(r.name);
393
+ checkBody(d.body, locals);
394
+ } else if (d.kind === "VarDecl") {
395
+ // Closures in package-level initializers run after init.
396
+ for (const spec of d.decls ?? []) checkBody(spec.value, []);
397
+ }
398
+ }
399
+ }
400
+ }
401
+
178
402
  _collectTypesPass(programs) {
179
403
  for (const p of programs) {
180
404
  for (const d of p.decls) {
@@ -318,6 +542,7 @@ export class TypeChecker {
318
542
  ? existing
319
543
  : { kind: "named", name: decl.name };
320
544
  named.underlying = underlying;
545
+ named._target = this.target;
321
546
  named._generic = {
322
547
  typeParams: typeParamTypes,
323
548
  declNode: decl,
@@ -343,6 +568,7 @@ export class TypeChecker {
343
568
  ? existing
344
569
  : { kind: "named", name: decl.name };
345
570
  named.underlying = underlying;
571
+ named._target = this.target;
346
572
  if (underlying.kind === "struct") {
347
573
  underlying.name = decl.name;
348
574
  underlying.methods = new Map();
@@ -507,6 +733,10 @@ export class TypeChecker {
507
733
  }
508
734
 
509
735
  _collectCssDecl(decl) {
736
+ if (this.target === "wasm" || this.target === "both") {
737
+ this.recordBlocker("css declaration");
738
+ this.err("css declarations are not allowed in wasm packages", decl);
739
+ }
510
740
  const paramTypes = (decl.params || []).map((p) =>
511
741
  this.resolveTypeNode(p.type, this.globals),
512
742
  );
@@ -551,17 +781,40 @@ export class TypeChecker {
551
781
  }
552
782
 
553
783
  checkFuncDecl(decl, outer) {
784
+ if (
785
+ (this.target === "wasm" || this.target === "both") &&
786
+ (decl.async || decl.isAsync)
787
+ ) {
788
+ this.recordBlocker("async function", decl.name);
789
+ this.err(
790
+ "async functions are not supported in wasm packages; keep async code in a js package",
791
+ decl,
792
+ );
793
+ }
554
794
  const inner = new Scope(outer);
555
795
  this._injectTypeParams(decl, inner, outer);
556
796
  for (const p of decl.params) {
557
797
  inner.define(p.name, this.resolveTypeNode(p.type, inner));
558
798
  }
559
799
  const returnType = this._setupFuncReturnType(decl, inner, outer);
800
+ const savedSharedAlloc = this._sharedAllocOk;
801
+ this._sharedAllocOk = decl.name === "init";
560
802
  this._runFuncBody(decl, inner, returnType);
803
+ this._sharedAllocOk = savedSharedAlloc;
561
804
  this._checkMissingReturn(decl, returnType, "function");
562
805
  }
563
806
 
564
807
  checkMethodDecl(decl, outer) {
808
+ if (
809
+ (this.target === "wasm" || this.target === "both") &&
810
+ (decl.async || decl.isAsync)
811
+ ) {
812
+ this.recordBlocker("async function", decl.name);
813
+ this.err(
814
+ "async functions are not supported in wasm packages; keep async code in a js package",
815
+ decl,
816
+ );
817
+ }
565
818
  const inner = new Scope(outer);
566
819
  const recvTypeName =
567
820
  decl.recvType.kind === "GenericTypeName"
@@ -640,6 +893,8 @@ export class TypeChecker {
640
893
  }
641
894
 
642
895
  checkVarDecl(decl, scope) {
896
+ const savedSharedAlloc = this._sharedAllocOk;
897
+ this._sharedAllocOk = scope === this.globals;
643
898
  for (const spec of decl.decls) {
644
899
  let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
645
900
  if (spec.value) {
@@ -653,6 +908,7 @@ export class TypeChecker {
653
908
  if (!type) type = ANY;
654
909
  for (const name of spec.names) scope.defineLocal(name, type);
655
910
  }
911
+ this._sharedAllocOk = savedSharedAlloc;
656
912
  }
657
913
 
658
914
  checkConstDecl(decl, scope) {
@@ -4,6 +4,7 @@
4
4
  import {
5
5
  ANY,
6
6
  BASIC_TYPES,
7
+ COMPARABLE,
7
8
  defaultType,
8
9
  isAny,
9
10
  isTypedArraySlice,
@@ -12,6 +13,7 @@ import {
12
13
  typedArrayConstructorForElem,
13
14
  typeStr,
14
15
  VOID,
16
+ WASM_STDLIB_MEMBERS,
15
17
  } from "./types.js";
16
18
 
17
19
  /** @typedef {import('./index.js').TypeChecker} TypeChecker */
@@ -81,8 +83,7 @@ export const resolveMethods = {
81
83
  switch (node.kind) {
82
84
  case "TypeName": {
83
85
  if (BASIC_TYPES[node.name]) return BASIC_TYPES[node.name];
84
- if (node.name === "comparable")
85
- return { kind: "basic", name: "comparable" };
86
+ if (node.name === "comparable") return COMPARABLE;
86
87
  if (scope) {
87
88
  const fromScope = scope.lookup(node.name);
88
89
  if (fromScope?.kind === "typeParam") return fromScope;
@@ -99,12 +100,16 @@ export const resolveMethods = {
99
100
  case "GenericTypeName": {
100
101
  const base = this.types.get(node.name);
101
102
  if (!base) return this.err(`Unknown type '${node.name}'`, node);
102
- if (base.kind === "named" && base._generic)
103
- return this.instantiateGenericType(
103
+ if (base.kind === "named" && base._generic) {
104
+ const res = this.instantiateGenericType(
104
105
  base._generic,
105
106
  node.typeArgs,
106
107
  scope,
107
108
  );
109
+ node._typeArgs = res.typeArgs;
110
+ node._genericType = base;
111
+ return res;
112
+ }
108
113
  return this.err(`Type '${node.name}' is not generic`, node);
109
114
  }
110
115
  case "TypeParam": {
@@ -266,12 +271,15 @@ export const resolveMethods = {
266
271
  const typeArgs = typeArgNodes.map((n) => this.resolveTypeNode(n, scope));
267
272
  for (let i = 0; i < generic.typeParams.length; i++)
268
273
  map.set(generic.typeParams[i].name, typeArgs[i] ?? ANY);
274
+ // Resolve the declaration with its type params in scope (as typeParams,
275
+ // never as value symbols), then substitute the concrete arguments.
269
276
  const typeScope = new Scope(this.globals);
270
- for (const [name, type] of map) typeScope.define(name, type);
277
+ for (const tp of generic.typeParams) typeScope.define(tp.name, tp);
271
278
  const underlying = this.resolveTypeNode(generic.declNode.type, typeScope);
272
279
  const instantiated = this.substituteType(underlying, map);
273
280
  if (instantiated.kind === "struct") {
274
281
  instantiated.name = generic.declNode.name;
282
+ instantiated.typeArgs = typeArgs;
275
283
  if (!instantiated.methods) instantiated.methods = new Map();
276
284
  if (generic.methods) {
277
285
  for (const [mName, mType] of generic.methods)
@@ -281,6 +289,7 @@ export const resolveMethods = {
281
289
  return {
282
290
  kind: "named",
283
291
  name: generic.declNode.name,
292
+ typeArgs,
284
293
  underlying: instantiated,
285
294
  };
286
295
  },
@@ -406,6 +415,9 @@ export const resolveMethods = {
406
415
  return this._fieldTypeInterface(base, baseType, field, node);
407
416
  case "namespace":
408
417
  return this._fieldTypeNamespace(base, field, node);
418
+ case "shared":
419
+ if (base.methods?.has(field)) return base.methods.get(field);
420
+ return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
409
421
  case "slice":
410
422
  if (isTypedArraySlice(base)) {
411
423
  return this._fieldTypeTypedArraySlice(base, baseType, field, node);
@@ -475,7 +487,24 @@ export const resolveMethods = {
475
487
  `cannot refer to unexported name ${base.name}.${field}`,
476
488
  node,
477
489
  );
478
- if (field in base.members) return base.members[field];
490
+ if (field in base.members) {
491
+ if (
492
+ (this.target === "wasm" || this.target === "both") &&
493
+ WASM_STDLIB_MEMBERS.has(base.name)
494
+ ) {
495
+ const allowed = WASM_STDLIB_MEMBERS.get(base.name);
496
+ if (allowed && !allowed.has(field)) {
497
+ const name = `${base.name}.${field}`;
498
+ this.recordBlocker("unsupported stdlib member", name);
499
+ return this.err(
500
+ `'${name}' is not yet available in wasm packages`,
501
+ node,
502
+ "move this call to a JS package, or use a supported alternative",
503
+ );
504
+ }
505
+ }
506
+ return base.members[field];
507
+ }
479
508
  return this.err(`No member '${field}' in namespace ${base.name}`, node);
480
509
  },
481
510
  };
@@ -9,6 +9,8 @@ import {
9
9
  INT,
10
10
  STRING,
11
11
  UINT8,
12
+ UINT32,
13
+ UINT64,
12
14
  VOID,
13
15
  } from "../types.js";
14
16
 
@@ -428,6 +430,21 @@ export function setupCoreGlobals(globals, types) {
428
430
  },
429
431
  });
430
432
 
433
+ globals.define("bits", {
434
+ kind: "namespace",
435
+ name: "bits",
436
+ members: {
437
+ LeadingZeros32: { kind: "func", params: [UINT32], returns: [INT] },
438
+ TrailingZeros32: { kind: "func", params: [UINT32], returns: [INT] },
439
+ OnesCount32: { kind: "func", params: [UINT32], returns: [INT] },
440
+ RotateLeft32: { kind: "func", params: [UINT32, INT], returns: [UINT32] },
441
+ LeadingZeros64: { kind: "func", params: [UINT64], returns: [INT] },
442
+ TrailingZeros64: { kind: "func", params: [UINT64], returns: [INT] },
443
+ OnesCount64: { kind: "func", params: [UINT64], returns: [INT] },
444
+ RotateLeft64: { kind: "func", params: [UINT64, INT], returns: [UINT64] },
445
+ },
446
+ });
447
+
431
448
  globals.define("errors", {
432
449
  kind: "namespace",
433
450
  name: "errors",
@@ -523,6 +540,14 @@ export function setupCoreGlobals(globals, types) {
523
540
  },
524
541
  });
525
542
 
543
+ // Slice helpers that return a slice of the same type as their first argument.
544
+ const sameSlice = (params, extra = {}) => ({
545
+ kind: "func",
546
+ params,
547
+ returns: [ANY],
548
+ _derivedReturn: "arg0",
549
+ ...extra,
550
+ });
526
551
  globals.define("slices", {
527
552
  kind: "namespace",
528
553
  name: "slices",
@@ -541,26 +566,16 @@ export function setupCoreGlobals(globals, types) {
541
566
  Min: { kind: "func", params: [ANY], returns: [ANY] },
542
567
  MaxFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
543
568
  MinFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
544
- Clone: { kind: "func", params: [ANY], returns: [ANY] },
545
- Compact: { kind: "func", params: [ANY], returns: [ANY] },
546
- CompactFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
569
+ Clone: sameSlice([ANY]),
570
+ Compact: sameSlice([ANY]),
571
+ CompactFunc: sameSlice([ANY, ANY]),
547
572
  Concat: { kind: "func", params: [ANY], returns: [ANY], variadic: true },
548
- Delete: { kind: "func", params: [ANY, INT, INT], returns: [ANY] },
549
- DeleteFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
550
- Insert: {
551
- kind: "func",
552
- params: [ANY, INT, ANY],
553
- returns: [ANY],
554
- variadic: true,
555
- },
556
- Replace: {
557
- kind: "func",
558
- params: [ANY, INT, INT, ANY],
559
- returns: [ANY],
560
- variadic: true,
561
- },
562
- Grow: { kind: "func", params: [ANY, INT], returns: [ANY] },
563
- Clip: { kind: "func", params: [ANY], returns: [ANY] },
573
+ Delete: sameSlice([ANY, INT, INT]),
574
+ DeleteFunc: sameSlice([ANY, ANY]),
575
+ Insert: sameSlice([ANY, INT, ANY], { variadic: true }),
576
+ Replace: sameSlice([ANY, INT, INT, ANY], { variadic: true }),
577
+ Grow: sameSlice([ANY, INT]),
578
+ Clip: sameSlice([ANY]),
564
579
  },
565
580
  });
566
581
 
@@ -160,9 +160,24 @@ export function setupExtendedGlobals(globals, types) {
160
160
  kind: "namespace",
161
161
  name: "maps",
162
162
  members: {
163
- Keys: { kind: "func", params: [ANY], returns: [ANY] },
164
- Values: { kind: "func", params: [ANY], returns: [ANY] },
165
- Clone: { kind: "func", params: [ANY], returns: [ANY] },
163
+ Keys: {
164
+ kind: "func",
165
+ params: [ANY],
166
+ returns: [ANY],
167
+ _derivedReturn: "keysOfArg0",
168
+ },
169
+ Values: {
170
+ kind: "func",
171
+ params: [ANY],
172
+ returns: [ANY],
173
+ _derivedReturn: "valuesOfArg0",
174
+ },
175
+ Clone: {
176
+ kind: "func",
177
+ params: [ANY],
178
+ returns: [ANY],
179
+ _derivedReturn: "arg0",
180
+ },
166
181
  Copy: { kind: "func", params: [ANY, ANY], returns: [VOID] },
167
182
  Equal: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
168
183
  EqualFunc: { kind: "func", params: [ANY, ANY, ANY], returns: [BOOL] },
@@ -0,0 +1,53 @@
1
+ // `gofront/shared` — fixed-size linear-memory buffers shared zero-copy between
2
+ // wasm packages and JS. Each `shared.Xxx` is a named type whose underlying kind
3
+ // is "shared" (not a slice): it supports indexing, len() and `.Subarray(lo, hi)`
4
+ // but not slicing, append or implicit conversion to GC slices.
5
+
6
+ import {
7
+ FLOAT32,
8
+ FLOAT64,
9
+ INT,
10
+ INT8,
11
+ INT16,
12
+ INT32,
13
+ UINT8,
14
+ UINT16,
15
+ UINT32,
16
+ } from "../types.js";
17
+
18
+ // key → [elem type, JS TypedArray ctor, byte size]
19
+ export const SHARED_KINDS = {
20
+ Float32: { elem: FLOAT32, ctor: "Float32Array", bytes: 4, key: "f32" },
21
+ Float64: { elem: FLOAT64, ctor: "Float64Array", bytes: 8, key: "f64" },
22
+ Int8: { elem: INT8, ctor: "Int8Array", bytes: 1, key: "i8" },
23
+ Int16: { elem: INT16, ctor: "Int16Array", bytes: 2, key: "i16" },
24
+ Int32: { elem: INT32, ctor: "Int32Array", bytes: 4, key: "i32" },
25
+ Uint8: { elem: UINT8, ctor: "Uint8Array", bytes: 1, key: "u8" },
26
+ Uint16: { elem: UINT16, ctor: "Uint16Array", bytes: 2, key: "u16" },
27
+ Uint32: { elem: UINT32, ctor: "Uint32Array", bytes: 4, key: "u32" },
28
+ };
29
+
30
+ export function setupSharedGlobals(globals, types) {
31
+ const members = {};
32
+ for (const [name, info] of Object.entries(SHARED_KINDS)) {
33
+ const t = {
34
+ kind: "named",
35
+ name: `shared.${name}`,
36
+ underlying: { kind: "shared", elem: info.elem, shared: name },
37
+ };
38
+ t.underlying.methods = new Map([
39
+ ["Subarray", { kind: "func", params: [INT, INT], returns: [t] }],
40
+ ]);
41
+ types.set(t.name, t);
42
+ members[name] = t;
43
+ members[`New${name}`] = { kind: "func", params: [INT], returns: [t] };
44
+ }
45
+ globals.define("shared", { kind: "namespace", name: "shared", members });
46
+ }
47
+
48
+ // Returns the SHARED_KINDS entry for a (possibly named) shared type, or null.
49
+ export function sharedInfo(t) {
50
+ const u = t?.kind === "named" ? t.underlying : t;
51
+ if (u?.kind !== "shared") return null;
52
+ return { name: u.shared, ...SHARED_KINDS[u.shared] };
53
+ }
@@ -9,10 +9,12 @@
9
9
 
10
10
  import { setupCoreGlobals } from "./stdlib/core.js";
11
11
  import { setupExtendedGlobals } from "./stdlib/extended.js";
12
+ import { setupSharedGlobals } from "./stdlib/shared.js";
12
13
  import { setupWebGlobals } from "./stdlib/web.js";
13
14
 
14
15
  export function setupGlobals(globals, types) {
15
16
  setupCoreGlobals(globals, types);
16
17
  setupExtendedGlobals(globals, types);
18
+ setupSharedGlobals(globals, types);
17
19
  setupWebGlobals(globals, types);
18
20
  }