gofront 1.6.1 → 1.8.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 (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -3,10 +3,16 @@
3
3
  // call expression dispatch (static, method, closure, package).
4
4
 
5
5
  import {
6
+ anonIfaceName,
6
7
  getMapKeyValTypes,
7
8
  isArrayType,
9
+ isBoolGoType,
10
+ isComplex,
11
+ isComplex64,
8
12
  isMapType,
9
13
  isNonEmptyInterface,
14
+ isScalarType,
15
+ isSigned,
10
16
  isSliceType,
11
17
  isStringType,
12
18
  isStructType,
@@ -228,7 +234,12 @@ export class BuiltinsEmitter {
228
234
  this.emitPanic("runtime error: makeslice: len out of range");
229
235
  this.pushInstruction("end");
230
236
 
231
- if (isStructType(elemGoType, this.mod.checker, this.mod)) {
237
+ if (
238
+ isStructType(elemGoType, this.mod.checker, this.mod) ||
239
+ isComplex(elemGoType) ||
240
+ (elemGoType?.kind !== "pointer" &&
241
+ isArrayType(elemGoType, this.mod.checker))
242
+ ) {
232
243
  const arrTmp = this.acquireTemp({
233
244
  kind: "ref",
234
245
  nullable: true,
@@ -302,6 +313,49 @@ export class BuiltinsEmitter {
302
313
  }
303
314
  }
304
315
 
316
+ _emitBuiltinNew(call) {
317
+ const targetType =
318
+ call._type?.base ??
319
+ (call.args[0]?.kind === "TypeExpr"
320
+ ? call.args[0].type
321
+ : call.args[0]?._type) ??
322
+ call.args[0];
323
+ if (isStructType(targetType, this.mod.checker, this.mod)) {
324
+ let structInfo = this._resolveStructInfo({ _type: targetType });
325
+ if (!structInfo) {
326
+ const typeName =
327
+ targetType.kind === "named" ? targetType.name : targetType.name;
328
+ if (typeName) structInfo = this.mod.getStructType(typeName);
329
+ }
330
+ if (!structInfo) {
331
+ throw new Error(
332
+ `Unknown struct in new: ${targetType.name ?? targetType}`,
333
+ );
334
+ }
335
+ for (const f of structInfo.fields) {
336
+ this.emitZeroValue(f.goType, f.wType);
337
+ }
338
+ this.pushInstruction({
339
+ op: "struct.new",
340
+ typeIndex: structInfo.typeIndex,
341
+ });
342
+ return;
343
+ }
344
+ if (isArrayType(targetType, this.mod.checker)) {
345
+ const wType = this.toWasmType(targetType);
346
+ this.emitZeroValue(targetType, wType);
347
+ return;
348
+ }
349
+ const box = this.mod.getBoxType(targetType);
350
+ const valWType = this.toWasmType(targetType);
351
+ this.emitZeroValue(targetType, valWType);
352
+ this.pushInstruction({
353
+ op: "struct.new",
354
+ typeIndex: box.typeIndex,
355
+ typeName: box.name,
356
+ });
357
+ }
358
+
305
359
  // With `newLen` already computed: when it exceeds the old capacity, allocate
306
360
  // a doubled (min 2, at least newLen) backing array and copy the old elements
307
361
  // into it at offset 0; otherwise reuse the old array, offset and capacity.
@@ -374,6 +428,17 @@ export class BuiltinsEmitter {
374
428
  this.pushInstruction("end");
375
429
  }
376
430
 
431
+ // The source operand of `append(b, x...)` and `copy(b, x)`: a string
432
+ // (the only non-slice form Go allows) is converted to a fresh byte slice.
433
+ _emitSliceOperand(node, elemGoType, sliceWType) {
434
+ if (isStringType(node._type, this.mod.checker)) {
435
+ this.emitExpr(node, "externref");
436
+ this._emitStringToByteSlice({ kind: "slice", elem: elemGoType });
437
+ return;
438
+ }
439
+ this.emitExpr(node, sliceWType);
440
+ }
441
+
377
442
  _emitBuiltinAppend(call) {
378
443
  const { args } = call;
379
444
  const sNode = args[0];
@@ -428,7 +493,7 @@ export class BuiltinsEmitter {
428
493
 
429
494
  if (args.length === 2 && args[1]._spread) {
430
495
  const s2Tmp = this.acquireTemp(sliceWType);
431
- this.emitExpr(args[1], sliceWType);
496
+ this._emitSliceOperand(args[1], elemGoType, sliceWType);
432
497
  this.pushInstruction({ op: "local.set", index: s2Tmp });
433
498
 
434
499
  const s2ArrTmp = this.acquireTemp(arrWType);
@@ -487,7 +552,7 @@ export class BuiltinsEmitter {
487
552
  this.pushInstruction("i32.add");
488
553
  }
489
554
  this.emitExpr(elemNode, arrInfo.elemWType);
490
- this._emitCopyIfValueStruct(elemNode, elemGoType, arrInfo.elemWType);
555
+ this._emitCopyIfValue(elemNode, elemGoType, arrInfo.elemWType);
491
556
  this.pushInstruction({
492
557
  op: "array.set",
493
558
  typeIndex: arrInfo.typeIndex,
@@ -538,7 +603,7 @@ export class BuiltinsEmitter {
538
603
  this.pushInstruction({ op: "local.set", index: dstTmp });
539
604
 
540
605
  const srcTmp = this.acquireTemp(sliceWType);
541
- this.emitExpr(srcNode, sliceWType);
606
+ this._emitSliceOperand(srcNode, elemGoType, sliceWType);
542
607
  this.pushInstruction({ op: "local.set", index: srcTmp });
543
608
 
544
609
  const nTmp = this.acquireTemp("i32");
@@ -667,7 +732,7 @@ export class BuiltinsEmitter {
667
732
  const arg = args[i];
668
733
  const isLast = i === args.length - 1 && isPrintln;
669
734
  const isBool =
670
- arg._type?.name === "bool" ||
735
+ isBoolGoType(arg._type) ||
671
736
  (arg.kind === "BasicLit" && arg.litKind === "BOOL");
672
737
  const wType = toWasmType(arg._type, this.mod.checker);
673
738
  this.emitExpr(arg, wType);
@@ -795,6 +860,136 @@ export class BuiltinsEmitter {
795
860
  }
796
861
  }
797
862
 
863
+ _emitBuiltinComplex(call, targetWasmType) {
864
+ const arg0 = call.args[0];
865
+ const arg1 = call.args[1];
866
+ const callType = call._type;
867
+ const is64 =
868
+ callType?.name === "complex64" ||
869
+ arg0._type?.name === "float32" ||
870
+ arg1._type?.name === "float32" ||
871
+ targetWasmType?.typeName === "$complex64";
872
+ const complexName = is64 ? "complex64" : "complex128";
873
+ const info = this.mod.getComplexType(complexName);
874
+ this.emitExpr(arg0, info.elemWType);
875
+ this.emitExpr(arg1, info.elemWType);
876
+ this.pushInstruction({
877
+ op: "struct.new",
878
+ typeIndex: info.typeIndex,
879
+ typeName: info.name,
880
+ });
881
+ }
882
+
883
+ _emitBuiltinReal(call, targetWasmType) {
884
+ const arg = call.args[0];
885
+ const argType = arg._type ?? this._resolveExprGoType(arg);
886
+ const is64 = isComplex64(argType);
887
+ const complexName = is64 ? "complex64" : "complex128";
888
+ const info = this.mod.getComplexType(complexName);
889
+ const complexWType = {
890
+ kind: "ref",
891
+ nullable: true,
892
+ typeIndex: info.typeIndex,
893
+ typeName: info.name,
894
+ };
895
+ this.emitExpr(arg, complexWType);
896
+ this.pushInstruction({
897
+ op: "struct.get",
898
+ typeIndex: info.typeIndex,
899
+ typeName: info.name,
900
+ fieldIndex: 0,
901
+ });
902
+ if (targetWasmType && targetWasmType !== info.elemWType) {
903
+ if (info.elemWType === "f32" && targetWasmType === "f64") {
904
+ this.pushInstruction("f64.promote_f32");
905
+ } else if (info.elemWType === "f64" && targetWasmType === "f32") {
906
+ this.pushInstruction("f32.demote_f64");
907
+ }
908
+ }
909
+ }
910
+
911
+ _emitBuiltinImag(call, targetWasmType) {
912
+ const arg = call.args[0];
913
+ const argType = arg._type ?? this._resolveExprGoType(arg);
914
+ const is64 = isComplex64(argType);
915
+ const complexName = is64 ? "complex64" : "complex128";
916
+ const info = this.mod.getComplexType(complexName);
917
+ const complexWType = {
918
+ kind: "ref",
919
+ nullable: true,
920
+ typeIndex: info.typeIndex,
921
+ typeName: info.name,
922
+ };
923
+ this.emitExpr(arg, complexWType);
924
+ this.pushInstruction({
925
+ op: "struct.get",
926
+ typeIndex: info.typeIndex,
927
+ typeName: info.name,
928
+ fieldIndex: 1,
929
+ });
930
+ if (targetWasmType && targetWasmType !== info.elemWType) {
931
+ if (info.elemWType === "f32" && targetWasmType === "f64") {
932
+ this.pushInstruction("f64.promote_f32");
933
+ } else if (info.elemWType === "f64" && targetWasmType === "f32") {
934
+ this.pushInstruction("f32.demote_f64");
935
+ }
936
+ }
937
+ }
938
+
939
+ // min/max fold left over the operands: floats use f32/f64.min/max (NaN
940
+ // propagates, -0 < +0), integers `select` on lt/gt, strings compare via the
941
+ // string import. A single operand is the result itself.
942
+ _emitBuiltinMinMax(call, targetWasmType) {
943
+ const isMin = call.func.name === "min";
944
+ const goType = call._type ?? this._resolveExprGoType(call.args[0]);
945
+ const wType =
946
+ goType?.kind === "untyped" && typeof targetWasmType === "string"
947
+ ? targetWasmType
948
+ : toWasmType(goType, this.mod.checker);
949
+ const [first, ...rest] = call.args;
950
+ this.emitExpr(first, wType);
951
+ if (rest.length === 0) return;
952
+ const acc = this.acquireTemp(wType);
953
+ const next = this.acquireTemp(wType);
954
+ const isStr = isStringType(goType, this.mod.checker);
955
+ const isFloatType = wType === "f32" || wType === "f64";
956
+ const cmp = isMin ? "lt" : "gt";
957
+ for (const arg of rest) {
958
+ this.pushInstruction({ op: "local.set", index: acc });
959
+ this.emitExpr(arg, wType);
960
+ this.pushInstruction({ op: "local.set", index: next });
961
+ if (isFloatType) {
962
+ this.pushInstruction({ op: "local.get", index: acc });
963
+ this.pushInstruction({ op: "local.get", index: next });
964
+ this.pushInstruction(`${wType}.${isMin ? "min" : "max"}`);
965
+ continue;
966
+ }
967
+ if (isStr) {
968
+ // next < acc (min) / next > acc (max) ? next : acc
969
+ this.pushInstruction({ op: "local.get", index: next });
970
+ this.pushInstruction({ op: "local.get", index: acc });
971
+ this.pushInstruction({
972
+ op: "call",
973
+ funcIndex: this.mod.getStringCmpImportIndex(isMin ? "<" : ">"),
974
+ });
975
+ this.pushInstruction({ op: "if", blockType: wType });
976
+ this.pushInstruction({ op: "local.get", index: next });
977
+ this.pushInstruction("else");
978
+ this.pushInstruction({ op: "local.get", index: acc });
979
+ this.pushInstruction("end");
980
+ continue;
981
+ }
982
+ this.pushInstruction({ op: "local.get", index: next });
983
+ this.pushInstruction({ op: "local.get", index: acc });
984
+ this.pushInstruction({ op: "local.get", index: next });
985
+ this.pushInstruction({ op: "local.get", index: acc });
986
+ this.pushInstruction(`${wType}.${cmp}_${isSigned(goType) ? "s" : "u"}`);
987
+ this.pushInstruction("select");
988
+ }
989
+ this.releaseTemp(next, wType);
990
+ this.releaseTemp(acc, wType);
991
+ }
992
+
798
993
  _emitBuiltinCall(call, targetWasmType) {
799
994
  const { func } = call;
800
995
  if (func.kind !== "Ident") return false;
@@ -808,6 +1003,9 @@ export class BuiltinsEmitter {
808
1003
  case "cap":
809
1004
  this._emitBuiltinCap(call, targetWasmType);
810
1005
  return true;
1006
+ case "new":
1007
+ this._emitBuiltinNew(call);
1008
+ return true;
811
1009
  case "make":
812
1010
  this._emitBuiltinMake(call);
813
1011
  return true;
@@ -833,6 +1031,19 @@ export class BuiltinsEmitter {
833
1031
  case "recover":
834
1032
  this._emitBuiltinRecover(call, targetWasmType);
835
1033
  return true;
1034
+ case "complex":
1035
+ this._emitBuiltinComplex(call, targetWasmType);
1036
+ return true;
1037
+ case "real":
1038
+ this._emitBuiltinReal(call, targetWasmType);
1039
+ return true;
1040
+ case "imag":
1041
+ this._emitBuiltinImag(call, targetWasmType);
1042
+ return true;
1043
+ case "min":
1044
+ case "max":
1045
+ this._emitBuiltinMinMax(call, targetWasmType);
1046
+ return true;
836
1047
  default:
837
1048
  return false;
838
1049
  }
@@ -845,6 +1056,12 @@ export class BuiltinsEmitter {
845
1056
  this.emitTestingCall(func, args);
846
1057
  return true;
847
1058
  }
1059
+ if (
1060
+ func.expr?.name &&
1061
+ (this.resolveLocal(func.expr.name) ||
1062
+ this.mod.resolveGlobal(func.expr.name))
1063
+ )
1064
+ return false;
848
1065
  const pkgName = func.expr?.name;
849
1066
  switch (pkgName) {
850
1067
  case "math":
@@ -903,6 +1120,14 @@ export class BuiltinsEmitter {
903
1120
  this.emitBytesNewReader(call, targetWasmType);
904
1121
  return true;
905
1122
  }
1123
+ if (func.field === "NewBuffer") {
1124
+ this.emitBytesNewBuffer(call, targetWasmType);
1125
+ return true;
1126
+ }
1127
+ if (func.field === "NewBufferString") {
1128
+ this.emitBytesNewBufferString(call, targetWasmType);
1129
+ return true;
1130
+ }
906
1131
  this.emitBytesCall(func.field, args, targetWasmType);
907
1132
  return true;
908
1133
  case "path":
@@ -930,6 +1155,9 @@ export class BuiltinsEmitter {
930
1155
  case "base64":
931
1156
  this.emitBase64Call(func.field, call, targetWasmType);
932
1157
  return true;
1158
+ case "simd":
1159
+ this.emitSimdPackageCall(func.field, args, targetWasmType);
1160
+ return true;
933
1161
  default:
934
1162
  return false;
935
1163
  }
@@ -946,6 +1174,23 @@ export class BuiltinsEmitter {
946
1174
  return true;
947
1175
  }
948
1176
  const recvTypeName = this._getReceiverTypeName(recvType, func.expr);
1177
+ if (
1178
+ recvTypeName === "simd.F32x4" ||
1179
+ recvTypeName === "gofront/simd.F32x4" ||
1180
+ recvType?.kind === "simd" ||
1181
+ recvType?.underlying?.kind === "simd" ||
1182
+ recvType?.name === "simd.F32x4"
1183
+ ) {
1184
+ return this.emitSimdMethodCall(
1185
+ func.field,
1186
+ func.expr,
1187
+ args,
1188
+ targetWasmType,
1189
+ recvTypeName === "simd.Mask32x4" ||
1190
+ recvType?.underlying?.simd === "Mask32x4" ||
1191
+ recvType?.simd === "Mask32x4",
1192
+ );
1193
+ }
949
1194
  if (
950
1195
  this._emitStdlibReceiverMethod(
951
1196
  recvTypeName,
@@ -966,7 +1211,7 @@ export class BuiltinsEmitter {
966
1211
  return true;
967
1212
  }
968
1213
  if (isNonEmptyInterface(recvType, this.mod.checker)) {
969
- const ifaceName = recvTypeName ?? "anon";
1214
+ const ifaceName = recvTypeName ?? anonIfaceName(recvType);
970
1215
  const dispatchName = `__dispatch_${ifaceName}_${func.field}`;
971
1216
  const targetFuncIdx = this.mod.resolveFuncIndex(dispatchName);
972
1217
  if (targetFuncIdx !== null) {
@@ -977,7 +1222,7 @@ export class BuiltinsEmitter {
977
1222
  const pType = targetParamTypes[i + 1] ?? null;
978
1223
  const pGoType = paramGoTypes[i + 1] ?? null;
979
1224
  this.emitExpr(args[i], pType);
980
- this._emitCopyIfValueStruct(args[i], pGoType, pType);
1225
+ this._emitCopyIfValue(args[i], pGoType, pType);
981
1226
  }
982
1227
  this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
983
1228
  return true;
@@ -999,14 +1244,20 @@ export class BuiltinsEmitter {
999
1244
  if (targetFuncIdx !== null) {
1000
1245
  const recvWType = targetParamTypes[0] ?? null;
1001
1246
  const recvGoType = this.mod.getFuncParamGoTypes(methodName)[0] ?? null;
1002
- this.emitExpr(func.expr, recvWType);
1003
- this._emitCopyIfValueStruct(func.expr, recvGoType, recvWType);
1247
+ this.emitExpr(this._recvOperand(func, recvGoType), recvWType);
1248
+ // `p.M()` with a value receiver reads `*p`: a nil `p` panics here.
1249
+ if (
1250
+ recvGoType?.kind !== "pointer" &&
1251
+ isStructType(recvGoType, this.mod.checker, this.mod)
1252
+ )
1253
+ this._emitNilCheck(func.expr._type);
1254
+ this._emitCopyIfValue(func.expr, recvGoType, recvWType);
1004
1255
  const paramGoTypes = this.mod.getFuncParamGoTypes(methodName);
1005
1256
  for (let i = 0; i < args.length; i++) {
1006
1257
  const pType = targetParamTypes[i + 1] ?? null;
1007
1258
  const pGoType = paramGoTypes[i + 1] ?? null;
1008
1259
  this.emitExpr(args[i], pType);
1009
- this._emitCopyIfValueStruct(args[i], pGoType, pType);
1260
+ this._emitCopyIfValue(args[i], pGoType, pType);
1010
1261
  }
1011
1262
  this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
1012
1263
  return true;
@@ -1015,6 +1266,43 @@ export class BuiltinsEmitter {
1015
1266
  return false;
1016
1267
  }
1017
1268
 
1269
+ // The receiver operand of a method call: `p.Get()` with a value receiver
1270
+ // and `p *N` (a named scalar or slice) passes `*p`.
1271
+ _recvOperand(func, recvGoType) {
1272
+ const t = func.expr._type;
1273
+ // A pointer receiver on `*p` gets `p` itself, so the method mutates *p.
1274
+ if (recvGoType?.kind === "pointer") {
1275
+ const pointer = this._structDerefPointer(func.expr);
1276
+ if (pointer) return pointer;
1277
+ }
1278
+ if (
1279
+ t?.kind === "pointer" &&
1280
+ recvGoType?.kind !== "pointer" &&
1281
+ (isScalarType(t.base, this.mod.checker, this.mod) ||
1282
+ isSliceType(t.base, this.mod.checker))
1283
+ )
1284
+ return { kind: "UnaryExpr", op: "*", operand: func.expr, _type: t.base };
1285
+ return this._ptrRecvOperand(func);
1286
+ }
1287
+
1288
+ // `x.M()` with a pointer receiver on a named scalar value passes `&x`
1289
+ // (the typechecker boxed `x`).
1290
+ _ptrRecvOperand(func) {
1291
+ const t = func.expr._type;
1292
+ if (!func._type?._ptrRecv || t?.kind !== "named") return func.expr;
1293
+ if (
1294
+ !isScalarType(t, this.mod.checker, this.mod) &&
1295
+ !isSliceType(t, this.mod.checker)
1296
+ )
1297
+ return func.expr;
1298
+ return {
1299
+ kind: "UnaryExpr",
1300
+ op: "&",
1301
+ operand: func.expr,
1302
+ _type: { kind: "pointer", base: t },
1303
+ };
1304
+ }
1305
+
1018
1306
  _emitStdlibReceiverMethod(
1019
1307
  recvTypeName,
1020
1308
  field,
@@ -1050,11 +1338,28 @@ export class BuiltinsEmitter {
1050
1338
  this.emitDurationMethodCall(field, expr, args, targetWasmType);
1051
1339
  return true;
1052
1340
  }
1341
+ if (
1342
+ recvTypeName === "time.Month" ||
1343
+ recvTypeName === "time.Weekday" ||
1344
+ recvTypeName === "time.Location"
1345
+ ) {
1346
+ this.emitTimeAuxMethodCall(
1347
+ recvTypeName,
1348
+ field,
1349
+ expr,
1350
+ args,
1351
+ targetWasmType,
1352
+ );
1353
+ return true;
1354
+ }
1053
1355
  if (recvTypeName === "io.LimitedReader") {
1054
1356
  this.emitIoLimitedReaderMethodCall(field, expr, args, targetWasmType);
1055
1357
  return true;
1056
1358
  }
1057
- if (recvTypeName === "sync.Mutex" || recvTypeName === "sync.RWMutex") {
1359
+ if (
1360
+ (recvTypeName === "sync.Mutex" || recvTypeName === "sync.RWMutex") &&
1361
+ field !== "RLocker"
1362
+ ) {
1058
1363
  this.emitSyncMutexMethodCall(field, expr, args, targetWasmType);
1059
1364
  return true;
1060
1365
  }
@@ -1137,7 +1442,7 @@ export class BuiltinsEmitter {
1137
1442
  const pType = paramTypes[i] ?? null;
1138
1443
  const pGoType = paramGoTypes[i] ?? null;
1139
1444
  this.emitExpr(arg, pType);
1140
- this._emitCopyIfValueStruct(arg, pGoType, pType);
1445
+ this._emitCopyIfValue(arg, pGoType, pType);
1141
1446
  }
1142
1447
  this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
1143
1448
  return true;
@@ -1158,6 +1463,8 @@ export class BuiltinsEmitter {
1158
1463
 
1159
1464
  const closureTmp = this.acquireTemp(closureWType);
1160
1465
  this.emitExpr(func, closureWType);
1466
+ // Calling a nil func value panics like a nil pointer dereference.
1467
+ this._emitNilBranch();
1161
1468
  this.pushInstruction({ op: "local.set", index: closureTmp });
1162
1469
 
1163
1470
  // Push param 0: env (anyref)
@@ -1174,7 +1481,7 @@ export class BuiltinsEmitter {
1174
1481
  const pWType = closureInfo.paramWTypes[i] ?? null;
1175
1482
  const pGoType = closureInfo.sig.params[i] ?? null;
1176
1483
  this.emitExpr(arg, pWType);
1177
- this._emitCopyIfValueStruct(arg, pGoType, pWType);
1484
+ this._emitCopyIfValue(arg, pGoType, pWType);
1178
1485
  }
1179
1486
 
1180
1487
  // Push funcref