gofront 1.5.1 → 1.6.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 (91) hide show
  1. package/README.md +174 -466
  2. package/package.json +16 -20
  3. package/src/backend/js/expressions.js +288 -128
  4. package/src/backend/js/index.js +138 -56
  5. package/src/backend/js/runtime.js +1055 -53
  6. package/src/backend/js/source-map.js +32 -0
  7. package/src/backend/js/statements.js +151 -83
  8. package/src/backend/js/stdlib/base64.js +12 -0
  9. package/src/backend/js/stdlib/binary.js +102 -0
  10. package/src/backend/js/stdlib/bits.js +15 -1
  11. package/src/backend/js/stdlib/builder.js +28 -9
  12. package/src/backend/js/stdlib/bytes.js +75 -18
  13. package/src/backend/js/stdlib/errors.js +22 -3
  14. package/src/backend/js/stdlib/fmt.js +95 -20
  15. package/src/backend/js/stdlib/hex.js +17 -0
  16. package/src/backend/js/stdlib/html.js +1 -1
  17. package/src/backend/js/stdlib/index.js +19 -0
  18. package/src/backend/js/stdlib/io.js +105 -3
  19. package/src/backend/js/stdlib/maps.js +1 -1
  20. package/src/backend/js/stdlib/math.js +13 -1
  21. package/src/backend/js/stdlib/path.js +20 -6
  22. package/src/backend/js/stdlib/rand.js +10 -5
  23. package/src/backend/js/stdlib/sha256.js +12 -0
  24. package/src/backend/js/stdlib/shared.js +17 -0
  25. package/src/backend/js/stdlib/slices.js +53 -27
  26. package/src/backend/js/stdlib/sort.js +1 -1
  27. package/src/backend/js/stdlib/strconv.js +52 -11
  28. package/src/backend/js/stdlib/strings.js +152 -16
  29. package/src/backend/js/stdlib/sync.js +13 -0
  30. package/src/backend/js/stdlib/testing.js +2 -2
  31. package/src/backend/js/stdlib/time.js +93 -22
  32. package/src/backend/js/stdlib/unicode.js +2 -2
  33. package/src/backend/js/templ.js +1 -1
  34. package/src/backend/wasm/boundary.js +1677 -58
  35. package/src/backend/wasm/const-fold.js +205 -0
  36. package/src/backend/wasm/emit-builtins.js +1230 -0
  37. package/src/backend/wasm/emit-exprs.js +2096 -0
  38. package/src/backend/wasm/emit-maps.js +1999 -0
  39. package/src/backend/wasm/emit-shared.js +519 -0
  40. package/src/backend/wasm/emit-stdlib.js +13 -0
  41. package/src/backend/wasm/emit-stmts.js +2466 -0
  42. package/src/backend/wasm/emit.js +656 -5031
  43. package/src/backend/wasm/encode.js +259 -85
  44. package/src/backend/wasm/glue.js +19 -5
  45. package/src/backend/wasm/index.js +2432 -86
  46. package/src/backend/wasm/monomorph.js +711 -0
  47. package/src/backend/wasm/optimize.js +236 -0
  48. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  49. package/src/backend/wasm/stdlib/core.js +8903 -0
  50. package/src/backend/wasm/stdlib/index.js +40 -0
  51. package/src/backend/wasm/stdlib/io.js +2161 -0
  52. package/src/backend/wasm/stdlib/packages.js +2400 -0
  53. package/src/backend/wasm/stdlib/pure.js +720 -0
  54. package/src/backend/wasm/stdlib/time.js +1200 -0
  55. package/src/backend/wasm/types.js +312 -36
  56. package/src/backend/wasm/wat.js +49 -2
  57. package/src/cli-core.js +276 -73
  58. package/src/compiler.js +162 -25
  59. package/src/dts-parser.js +79 -126
  60. package/src/index.js +88 -121
  61. package/src/lexer.js +80 -29
  62. package/src/lower/boxing.js +4 -6
  63. package/src/lower/captures.js +12 -23
  64. package/src/lower/escape.js +4 -9
  65. package/src/lower/functions.js +277 -5
  66. package/src/lower/globals.js +7 -15
  67. package/src/lower/index.js +5 -2
  68. package/src/lower/ownership.js +35 -19
  69. package/src/lower/range.js +2 -5
  70. package/src/lower/walk.js +25 -0
  71. package/src/minifier.js +20 -68
  72. package/src/parser/declarations.js +3 -6
  73. package/src/parser/expressions.js +3 -8
  74. package/src/parser/index.js +18 -3
  75. package/src/parser/statements.js +25 -38
  76. package/src/parser/types.js +1 -2
  77. package/src/resolver.js +3 -27
  78. package/src/templ-lexer.js +29 -63
  79. package/src/templ-parser.js +22 -45
  80. package/src/test-runner.js +20 -0
  81. package/src/typechecker/assignability.js +123 -14
  82. package/src/typechecker/expressions.js +244 -4
  83. package/src/typechecker/index.js +27 -1
  84. package/src/typechecker/resolve.js +71 -12
  85. package/src/typechecker/statements.js +0 -7
  86. package/src/typechecker/stdlib/core.js +292 -103
  87. package/src/typechecker/stdlib/extended.js +471 -4
  88. package/src/typechecker/stdlib/shared.js +53 -0
  89. package/src/typechecker/stdlib.js +2 -0
  90. package/src/typechecker/types.js +545 -8
  91. package/src/vendor.js +2 -2
@@ -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(
@@ -180,24 +182,99 @@ export const assignabilityMethods = {
180
182
  // Tags struct values / struct pointers flowing into an interface so codegen can
181
183
  // tell `T` from `*T` at runtime (both compile to the same class instance).
182
184
  _markIfaceBox(target, source, node) {
183
- if (this._skipIfaceMark) return;
184
- if (!node || typeof node !== "object" || !target || !source) return;
185
+ if (
186
+ this._skipIfaceMark ||
187
+ !node ||
188
+ typeof node !== "object" ||
189
+ !target ||
190
+ !source
191
+ )
192
+ return;
185
193
  if (target.kind === "typeParam") return;
186
194
  const tBase = this.resolveType(
187
195
  target.kind === "named" ? target.underlying : target,
188
196
  );
189
197
  if (!isAny(target) && tBase?.kind !== "interface") return;
190
- const structOf = (t) => {
191
- const r = this.resolveType(t);
192
- const b = r?.kind === "named" ? this.resolveType(r.underlying) : r;
193
- return b?.kind === "struct";
194
- };
195
198
  const sBase = this.resolveType(
196
199
  source.kind === "named" ? source.underlying : source,
197
200
  );
198
- if (sBase?.kind === "struct") node._ifaceBox = "value";
199
- else if (sBase?.kind === "pointer" && structOf(sBase.base))
200
- node._ifaceBox = "ptr";
201
+ const info = this._classifyIfaceBox(sBase);
202
+ if (!info) return;
203
+ node._ifaceBox = info.box;
204
+ if (info.ptrFields) node._ifacePtrFields = info.ptrFields;
205
+ },
206
+
207
+ _getStructPtrFields(st, prefix = "", seen = new Set()) {
208
+ if (st?.kind !== "struct" || !st?.fields) return [];
209
+ if (seen.has(st)) return [];
210
+ const nextSeen = new Set(seen);
211
+ nextSeen.add(st);
212
+ const ptrs = [];
213
+ for (const [fname, ftype] of st.fields.entries()) {
214
+ const full = prefix ? `${prefix}.${fname}` : fname;
215
+ const rf = this.resolveType(ftype);
216
+ const bf = rf?.kind === "named" ? this.resolveType(rf.underlying) : rf;
217
+ if (isPointer(bf)) {
218
+ ptrs.push(full);
219
+ } else if (bf?.kind === "struct") {
220
+ ptrs.push(...this._getStructPtrFields(bf, full, nextSeen));
221
+ }
222
+ }
223
+ return ptrs;
224
+ },
225
+
226
+ _classifyIfaceBox(sBase) {
227
+ if (sBase?.kind === "struct") {
228
+ const pf = this._getStructPtrFields(sBase);
229
+ return { box: "value", ptrFields: pf.length > 0 ? pf : null };
230
+ }
231
+ if (sBase?.kind === "pointer") {
232
+ const st = this.resolveType(
233
+ sBase.base?.kind === "named" ? sBase.base.underlying : sBase.base,
234
+ );
235
+ if (st?.kind === "struct") {
236
+ const pf = this._getStructPtrFields(st);
237
+ return { box: "ptr", ptrFields: pf.length > 0 ? pf : null };
238
+ }
239
+ return null;
240
+ }
241
+ if (sBase?.kind === "map") {
242
+ const valType = this.resolveType(sBase.value ?? sBase.val ?? sBase.elem);
243
+ const keyType = this.resolveType(sBase.key);
244
+ return {
245
+ box: isPointer(valType) || isPointer(keyType) ? "ptr_map" : "map",
246
+ };
247
+ }
248
+ if (sBase?.kind === "slice") {
249
+ const elemType = this.resolveType(sBase.elem);
250
+ if (isPointer(elemType)) return { box: "ptr_slice" };
251
+ const elemBase =
252
+ elemType?.kind === "named"
253
+ ? this.resolveType(elemType.underlying)
254
+ : elemType;
255
+ const pf =
256
+ elemBase?.kind === "struct" ? this._getStructPtrFields(elemBase) : [];
257
+ return pf.length > 0 ? { box: "slice", ptrFields: pf } : null;
258
+ }
259
+ return null;
260
+ },
261
+
262
+ // A js package cannot provide an implementation of a wasm interface: the
263
+ // value would cross into wasm, and JS-implemented proxies are not supported.
264
+ _checkWasmIfaceProvider(target, source, node) {
265
+ if (this.target !== "js" || target?.kind !== "named") return;
266
+ if (target._target !== "wasm" || !source) return;
267
+ const iface = this.resolveType(target.underlying);
268
+ if (iface?.kind !== "interface" || !(iface.methods?.size > 0)) return;
269
+ const ptr = source.kind === "pointer";
270
+ const src = ptr ? this.resolveType(source.base) : source;
271
+ if (src?.kind !== "named" || src._target === "wasm") return;
272
+ if (this.resolveType(src.underlying)?.kind === "interface") return;
273
+ if (!this.implements(source, iface, node)) return;
274
+ this.err(
275
+ `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`,
276
+ node,
277
+ );
201
278
  },
202
279
 
203
280
  _assertAssignableEarlyReturn(target, source) {
@@ -222,6 +299,23 @@ export const assignabilityMethods = {
222
299
  );
223
300
  return;
224
301
  }
302
+ const sBase = this.resolveType(
303
+ source.kind === "named" ? source.underlying : source,
304
+ );
305
+ if (sharedInfo(source) && tBase?.kind === "slice") {
306
+ this.err(
307
+ `cannot use ${typeStr(source)} as ${typeStr(target)}: a linear-memory buffer cannot be passed as a GC slice without an explicit copy`,
308
+ node,
309
+ );
310
+ return;
311
+ }
312
+ if (tBase?.kind === "shared" && sBase?.kind === "slice") {
313
+ this.err(
314
+ `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}`,
315
+ node,
316
+ );
317
+ return;
318
+ }
225
319
  this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
226
320
  },
227
321
 
@@ -334,18 +428,30 @@ export const assignabilityMethods = {
334
428
  return true;
335
429
  },
336
430
 
431
+ _unpackReturns(rList) {
432
+ if (
433
+ rList.length === 1 &&
434
+ (rList[0]?.kind === "tuple" || rList[0]?.kind === "TupleType") &&
435
+ rList[0].types
436
+ ) {
437
+ return rList[0].types;
438
+ }
439
+ return rList;
440
+ },
441
+
337
442
  _implementsMethod(required, actual) {
338
443
  if (!actual) return false;
339
444
  const rp = required.params ?? [],
340
445
  ap = actual.params ?? [];
341
- const rr = required.returns ?? [],
342
- ar = actual.returns ?? [];
446
+ const rr = this._unpackReturns(required.returns ?? []),
447
+ ar = this._unpackReturns(actual.returns ?? []);
343
448
  if (!this._sigParamsMatch(rp, ap)) return false;
344
449
  if (!!required.variadic !== !!actual.variadic) return false;
345
450
  return this._sigParamsMatch(rr, ar);
346
451
  },
347
452
 
348
453
  implements(srcType, iface, _node) {
454
+ const isPtr = srcType?.kind === "pointer";
349
455
  // *T has the full method set of T (value + pointer receivers).
350
456
  if (srcType?.kind === "pointer") srcType = this.resolveType(srcType.base);
351
457
  let base = srcType.kind === "named" ? srcType.underlying : srcType;
@@ -360,13 +466,16 @@ export const assignabilityMethods = {
360
466
  }
361
467
  const methodMap =
362
468
  base?.kind === "struct"
363
- ? base.methods
469
+ ? base.methods || srcType.methods
364
470
  : srcType.kind === "named"
365
471
  ? srcType.methods
366
472
  : null;
367
473
  if (!methodMap) return false;
368
474
  for (const [name, required] of iface.methods) {
369
- if (!this._implementsMethod(required, methodMap.get(name))) return false;
475
+ const actual = methodMap.get(name);
476
+ if (!actual) return false;
477
+ if (!isPtr && actual._ptrRecv) return false;
478
+ if (!this._implementsMethod(required, actual)) return false;
370
479
  }
371
480
  return true;
372
481
  },
@@ -1,5 +1,6 @@
1
1
  // TypeChecker expression-checking methods — installed as a mixin on TypeChecker.prototype.
2
2
 
3
+ import { sharedInfo } from "./stdlib/shared.js";
3
4
  import {
4
5
  ANY,
5
6
  BOOL,
@@ -42,6 +43,13 @@ const BUILTIN_CHECK = {
42
43
  s.err(`cannot append to array (type ${typeStr(st)})`, e);
43
44
  return st;
44
45
  }
46
+ if (sharedInfo(st)) {
47
+ s.err(
48
+ `cannot append to ${typeStr(st)}: shared buffers have a fixed size`,
49
+ e,
50
+ );
51
+ return st;
52
+ }
45
53
  const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
46
54
  if (sb?.kind === "slice") {
47
55
  for (let i = 1; i < e.args.length; i++) {
@@ -55,7 +63,7 @@ const BUILTIN_CHECK = {
55
63
  }
56
64
  return st;
57
65
  },
58
- copy: () => INT,
66
+ copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
59
67
  delete: () => VOID,
60
68
  make: (s, _n, e, _at, sc) => {
61
69
  if (e.args.length < 1) return ANY;
@@ -258,6 +266,16 @@ export const expressionCheckMethods = {
258
266
  if (this._isStructOrNamedMethod(baseType, base, expr.field))
259
267
  expr._isMethodValue = true;
260
268
  }
269
+ if (
270
+ baseType?.kind === "namespace" &&
271
+ this.types.has(`${baseType.name}.${expr.field}`) &&
272
+ !(
273
+ baseType.name === "binary" &&
274
+ (expr.field === "LittleEndian" || expr.field === "BigEndian")
275
+ )
276
+ ) {
277
+ expr._isTypeRef = true;
278
+ }
261
279
  return ft;
262
280
  },
263
281
 
@@ -291,10 +309,42 @@ export const expressionCheckMethods = {
291
309
  expr._mapValueType = btu.value;
292
310
  return btu.value;
293
311
  }
312
+ if (btu.kind === "shared") {
313
+ expr._sharedElem = btu.elem;
314
+ const idx = this._constIntValue(expr.index);
315
+ if (idx !== null && idx < 0)
316
+ this.err(`invalid index ${idx} (index must not be negative)`, expr);
317
+ return btu.elem;
318
+ }
294
319
  if (isString(bt)) return INT;
295
320
  return this.err(`Cannot index type ${typeStr(bt)}`, expr);
296
321
  },
297
322
 
323
+ // copy(dst, src): element types must be identical when either side is a
324
+ // shared buffer (the wasm emitter uses one element type for both sides).
325
+ _checkBuiltinCopy(e, at) {
326
+ const elemOf = (t) => {
327
+ const u = this.resolveType(t?.kind === "named" ? t.underlying : t);
328
+ return u?.kind === "slice" || u?.kind === "shared" ? u.elem : null;
329
+ };
330
+ const [dt, st] = at;
331
+ if (!sharedInfo(dt) && !sharedInfo(st)) return INT;
332
+ const de = elemOf(dt);
333
+ const se = elemOf(st);
334
+ if (!de || !se) {
335
+ this.err(
336
+ `copy: cannot copy between ${typeStr(dt)} and ${typeStr(st)}`,
337
+ e,
338
+ );
339
+ } else if (typeStr(de) !== typeStr(se)) {
340
+ this.err(
341
+ `copy: element types differ (${typeStr(dt)} vs ${typeStr(st)})`,
342
+ e,
343
+ );
344
+ }
345
+ return INT;
346
+ },
347
+
298
348
  _checkSliceExpr(expr, scope) {
299
349
  const bt = this.checkExpr(expr.expr, scope);
300
350
  if (expr.low) this.checkExpr(expr.low, scope);
@@ -305,10 +355,25 @@ export const expressionCheckMethods = {
305
355
  if (bt.kind === "slice" || bt.kind === "array")
306
356
  return { kind: "slice", elem: bt.elem };
307
357
  if (isString(bt)) return STRING;
358
+ if (sharedInfo(bt))
359
+ return this.err(
360
+ `Cannot slice type ${typeStr(bt)}: use .Subarray(lo, hi) to create a view of a shared buffer`,
361
+ expr,
362
+ );
308
363
  return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
309
364
  },
310
365
 
311
366
  _checkFuncLit(expr, scope) {
367
+ const savedSharedAlloc = this._sharedAllocOk;
368
+ this._sharedAllocOk = false;
369
+ try {
370
+ return this._checkFuncLitBody(expr, scope);
371
+ } finally {
372
+ this._sharedAllocOk = savedSharedAlloc;
373
+ }
374
+ },
375
+
376
+ _checkFuncLitBody(expr, scope) {
312
377
  if (
313
378
  (this.target === "wasm" || this.target === "both") &&
314
379
  (expr.async || expr.isAsync)
@@ -409,6 +474,81 @@ export const expressionCheckMethods = {
409
474
  return targetType;
410
475
  },
411
476
 
477
+ _checkConversionValidity(srcType, targetType, expr) {
478
+ if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
479
+ const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
480
+ const tgtResolved =
481
+ targetType.kind === "named" ? targetType.underlying : targetType;
482
+
483
+ if (isNumeric(targetType) && isComplex(srcType)) {
484
+ this.err(
485
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
486
+ expr,
487
+ );
488
+ return;
489
+ }
490
+ if (isComplex(targetType) && !isComplexOrNumeric(srcType)) {
491
+ this.err(
492
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
493
+ expr,
494
+ );
495
+ return;
496
+ }
497
+ if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
498
+ this._checkElemTypeCompatible(
499
+ tgtResolved.elem,
500
+ srcResolved.elem,
501
+ srcType,
502
+ targetType,
503
+ expr,
504
+ );
505
+ return;
506
+ }
507
+ if (this._isInvalidArrayConv(tgtResolved, srcResolved, srcType)) {
508
+ this.err(
509
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
510
+ expr,
511
+ );
512
+ return;
513
+ }
514
+ if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
515
+ if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
516
+ if (
517
+ isString(srcResolved) &&
518
+ tgtResolved?.kind === "slice" &&
519
+ (tgtResolved.elem?.name === "byte" ||
520
+ tgtResolved.elem?.name === "rune" ||
521
+ tgtResolved.elem?.name === "uint8" ||
522
+ tgtResolved.elem?.name === "int32")
523
+ )
524
+ return;
525
+ if (
526
+ isString(tgtResolved) &&
527
+ (isNumeric(srcResolved) ||
528
+ (srcResolved?.kind === "slice" &&
529
+ (srcResolved.elem?.name === "byte" ||
530
+ srcResolved.elem?.name === "rune" ||
531
+ srcResolved.elem?.name === "uint8" ||
532
+ srcResolved.elem?.name === "int32")))
533
+ )
534
+ return;
535
+ if (srcResolved?.kind === "pointer" && tgtResolved?.kind === "pointer") {
536
+ const sBase =
537
+ srcResolved.base?.kind === "named"
538
+ ? srcResolved.base.underlying
539
+ : srcResolved.base;
540
+ const tBase =
541
+ tgtResolved.base?.kind === "named"
542
+ ? tgtResolved.base.underlying
543
+ : tgtResolved.base;
544
+ if (typeStr(sBase) === typeStr(tBase)) return;
545
+ }
546
+ this.err(
547
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
548
+ expr,
549
+ );
550
+ },
551
+
412
552
  _checkTypeAssertExpr(expr, scope) {
413
553
  const srcType = this.checkExpr(expr.expr, scope);
414
554
  const targetType = this.resolveTypeNode(expr.type, scope);
@@ -431,7 +571,11 @@ export const expressionCheckMethods = {
431
571
 
432
572
  _rangeCollTypeTuple(resolved, collType) {
433
573
  if (isString(resolved)) return { kind: "tuple", types: [INT, RUNE] };
434
- if (resolved?.kind === "slice" || resolved?.kind === "array")
574
+ if (
575
+ resolved?.kind === "slice" ||
576
+ resolved?.kind === "array" ||
577
+ resolved?.kind === "shared"
578
+ )
435
579
  return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
436
580
  if (resolved?.kind === "map")
437
581
  return {
@@ -467,6 +611,9 @@ export const expressionCheckMethods = {
467
611
  : null;
468
612
  if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
469
613
  }
614
+ expr._typeArgs = typeArgs;
615
+ if (expr.expr) expr.expr._typeArgs = typeArgs;
616
+ expr._genericFnType = baseType;
470
617
  return this.instantiateGenericFunc(baseType, typeArgs);
471
618
  }
472
619
  // The parser's type-arg heuristic mistakes `xs[d.Field]` (uppercase field
@@ -546,6 +693,8 @@ export const expressionCheckMethods = {
546
693
 
547
694
  checkCall(expr, scope) {
548
695
  let fnType = this.checkExpr(expr.func, scope);
696
+ this._checkSharedAllocSite(expr, scope);
697
+ this._checkBoundaryCallInLoop(expr, scope);
549
698
  if (fnType.kind === "builtin") {
550
699
  const argTypes = expr.args.map((a, i) => {
551
700
  // new(T) — first arg is a type name, not a value expression
@@ -565,6 +714,14 @@ export const expressionCheckMethods = {
565
714
  if (fnType.kind === "named" && expr.func._isTypeRef) {
566
715
  expr._isTypeConversion = true;
567
716
  expr._conversionTargetType = fnType;
717
+ if (expr.args.length !== 1) {
718
+ return this.err(
719
+ `cannot convert ${expr.args.length} arguments to type ${typeStr(fnType)}`,
720
+ expr,
721
+ );
722
+ }
723
+ const argType = argTypes[0];
724
+ this._checkConversionValidity(argType, fnType, expr);
568
725
  return fnType;
569
726
  }
570
727
 
@@ -576,12 +733,81 @@ export const expressionCheckMethods = {
576
733
 
577
734
  this._checkCallArgs(fnType, argTypes, expr);
578
735
 
736
+ // Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
737
+ // how their result derives from the first argument.
738
+ if (fnType._derivedReturn) {
739
+ const derived = this._derivedReturnType(fnType._derivedReturn, argTypes);
740
+ if (derived) return derived;
741
+ }
742
+
579
743
  const ret = fnType.returns;
580
744
  if (!ret || ret.length === 0) return VOID;
581
745
  if (ret.length === 1) return ret[0];
582
746
  return { kind: "tuple", types: ret };
583
747
  },
584
748
 
749
+ // `shared.NewXxx(n)` allocates from a linear memory that is sized once at
750
+ // startup (no memory.grow → no detached TypedArray views on the JS side).
751
+ // Allocation is therefore only legal in package-level var initializers and
752
+ // init(), which both run inside the module start function.
753
+ // JS→WASM calls copy non-shared arguments across the boundary on every
754
+ // call. Inside a loop that dominates the per-iteration cost, so hint that
755
+ // the loop belongs on the WASM side (or the data in gofront/shared).
756
+ _checkBoundaryCallInLoop(expr, scope) {
757
+ if (this._loopDepth === 0 || this.target !== "js") return;
758
+ const fn = expr.func;
759
+ if (fn.kind !== "SelectorExpr" || fn.expr.kind !== "Ident") return;
760
+ const ns = scope.lookup(fn.expr.name);
761
+ if (ns?.kind !== "namespace" || ns._target !== "wasm") return;
762
+ const member = ns.members[fn.field];
763
+ if (member?.kind !== "func") return;
764
+ this.warn(
765
+ `'${fn.expr.name}.${fn.field}' crosses the JS→WASM boundary inside a loop`,
766
+ fn.expr, // CallExpr nodes carry no position; the callee Ident does
767
+ "hint: each call copies its arguments; move the loop into the wasm package or pass gofront/shared buffers",
768
+ );
769
+ },
770
+
771
+ _checkSharedAllocSite(expr, scope) {
772
+ const f = expr.func;
773
+ if (
774
+ f.kind !== "SelectorExpr" ||
775
+ f.expr.kind !== "Ident" ||
776
+ f.expr.name !== "shared" ||
777
+ !f.field.startsWith("New") ||
778
+ scope.lookup("shared") !== this.globals.lookup("shared")
779
+ )
780
+ return;
781
+ if (this._sharedAllocOk) return;
782
+ const why =
783
+ this.target === "wasm"
784
+ ? "shared buffers are allocated once at startup"
785
+ : "shared buffers are allocated once at startup so the code stays portable to wasm packages";
786
+ this.err(
787
+ `shared.${f.field} must be called from a package-level var initializer or init(); ${why}`,
788
+ expr,
789
+ );
790
+ },
791
+
792
+ _derivedReturnType(mode, argTypes) {
793
+ const arg = argTypes[0];
794
+ const under = arg?.kind === "named" ? arg.underlying : arg;
795
+ switch (mode) {
796
+ case "arg0":
797
+ return under ? arg : null;
798
+ case "elemOfArg0":
799
+ return under?.elem ?? null;
800
+ case "keysOfArg0":
801
+ return under?.key ? { kind: "slice", elem: under.key } : null;
802
+ case "valuesOfArg0": {
803
+ const v = under?.value ?? under?.elem;
804
+ return v ? { kind: "slice", elem: v } : null;
805
+ }
806
+ default:
807
+ return null;
808
+ }
809
+ },
810
+
585
811
  _resolveGenericFnType(fnType, argTypes, expr, scope) {
586
812
  const typeArgs = this.inferTypeArgs(fnType, argTypes);
587
813
  if (!typeArgs) {
@@ -599,6 +825,10 @@ export const expressionCheckMethods = {
599
825
  : null;
600
826
  if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
601
827
  }
828
+ expr._typeArgs = typeArgs;
829
+ if (expr.func) expr.func._typeArgs = typeArgs;
830
+ expr._genericFnType = fnType;
831
+ if (expr.func) expr.func._genericFnType = fnType;
602
832
  return this.instantiateGenericFunc(fnType, typeArgs);
603
833
  },
604
834
 
@@ -620,13 +850,18 @@ export const expressionCheckMethods = {
620
850
 
621
851
  _checkCallArgs(fnType, argTypes, expr) {
622
852
  this._checkArgCount(fnType, argTypes, expr);
623
- for (let i = 0; i < fnType.params.length && i < argTypes.length; i++) {
853
+ const maxLen = fnType.variadic
854
+ ? argTypes.length
855
+ : Math.min(fnType.params.length, argTypes.length);
856
+ for (let i = 0; i < maxLen; i++) {
624
857
  const paramIdx =
625
858
  fnType.variadic && i >= fnType.params.length - 1
626
859
  ? fnType.params.length - 1
627
860
  : i;
628
861
  if (expr.args[i]?._spread) continue;
629
- this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
862
+ const targetParam = fnType.params[paramIdx];
863
+ const node = expr._multiForward ? null : expr.args[i];
864
+ this.assertAssignable(targetParam, argTypes[i], node);
630
865
  }
631
866
  },
632
867
 
@@ -650,6 +885,11 @@ export const expressionCheckMethods = {
650
885
  if (name === "len") {
651
886
  const size = this._constLenFromType(argTypes[0]);
652
887
  if (size != null) expr._constLen = size;
888
+ } else if (sharedInfo(argTypes[0])) {
889
+ this.err(
890
+ `cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
891
+ expr,
892
+ );
653
893
  }
654
894
  return INT;
655
895
  },
@@ -45,6 +45,7 @@ export class TypeChecker {
45
45
  this.types = new Map(); // named types
46
46
  this.globals = new Scope();
47
47
  this.errors = [];
48
+ this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
48
49
  this.target = "js";
49
50
  this.pkgName = null;
50
51
  this.blockers = new Map();
@@ -77,7 +78,8 @@ export class TypeChecker {
77
78
  // Add an imported GoFront package as a qualified namespace.
78
79
  // e.g. addPackageNamespace('utils', symbolsMap, typesMap)
79
80
  // lets callers type-check `utils.Foo` via SelectorExpr.
80
- addPackageNamespace(pkgName, symbols, types) {
81
+ // `target` is the imported package's compile target ("js"/"wasm"/"both").
82
+ addPackageNamespace(pkgName, symbols, types, target = null) {
81
83
  const members = {};
82
84
  for (const [name, type] of symbols) members[name] = type;
83
85
  // _gofront: true marks this as a GoFront package — exported identifier rules apply
@@ -86,6 +88,7 @@ export class TypeChecker {
86
88
  name: pkgName,
87
89
  members,
88
90
  _gofront: true,
91
+ _target: target,
89
92
  });
90
93
  for (const [name, type] of types) {
91
94
  this.types.set(name, type);
@@ -171,6 +174,21 @@ export class TypeChecker {
171
174
  return TAINTED_ANY; // tainted recovery type — suppresses downstream cascade errors
172
175
  }
173
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
+
174
192
  _reportUnused(scope, node) {
175
193
  for (const name of scope.unusedLocals()) {
176
194
  this.err(`'${name}' declared and not used`, node);
@@ -524,6 +542,7 @@ export class TypeChecker {
524
542
  ? existing
525
543
  : { kind: "named", name: decl.name };
526
544
  named.underlying = underlying;
545
+ named._target = this.target;
527
546
  named._generic = {
528
547
  typeParams: typeParamTypes,
529
548
  declNode: decl,
@@ -549,6 +568,7 @@ export class TypeChecker {
549
568
  ? existing
550
569
  : { kind: "named", name: decl.name };
551
570
  named.underlying = underlying;
571
+ named._target = this.target;
552
572
  if (underlying.kind === "struct") {
553
573
  underlying.name = decl.name;
554
574
  underlying.methods = new Map();
@@ -777,7 +797,10 @@ export class TypeChecker {
777
797
  inner.define(p.name, this.resolveTypeNode(p.type, inner));
778
798
  }
779
799
  const returnType = this._setupFuncReturnType(decl, inner, outer);
800
+ const savedSharedAlloc = this._sharedAllocOk;
801
+ this._sharedAllocOk = decl.name === "init";
780
802
  this._runFuncBody(decl, inner, returnType);
803
+ this._sharedAllocOk = savedSharedAlloc;
781
804
  this._checkMissingReturn(decl, returnType, "function");
782
805
  }
783
806
 
@@ -870,6 +893,8 @@ export class TypeChecker {
870
893
  }
871
894
 
872
895
  checkVarDecl(decl, scope) {
896
+ const savedSharedAlloc = this._sharedAllocOk;
897
+ this._sharedAllocOk = scope === this.globals;
873
898
  for (const spec of decl.decls) {
874
899
  let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
875
900
  if (spec.value) {
@@ -883,6 +908,7 @@ export class TypeChecker {
883
908
  if (!type) type = ANY;
884
909
  for (const name of spec.names) scope.defineLocal(name, type);
885
910
  }
911
+ this._sharedAllocOk = savedSharedAlloc;
886
912
  }
887
913
 
888
914
  checkConstDecl(decl, scope) {