gofront 1.6.0 → 1.7.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 (80) hide show
  1. package/README.md +35 -39
  2. package/package.json +37 -4
  3. package/src/backend/js/expressions.js +559 -144
  4. package/src/backend/js/index.js +174 -49
  5. package/src/backend/js/runtime.js +1518 -54
  6. package/src/backend/js/statements.js +91 -73
  7. package/src/backend/js/stdlib/base64.js +12 -0
  8. package/src/backend/js/stdlib/binary.js +102 -0
  9. package/src/backend/js/stdlib/bits.js +15 -1
  10. package/src/backend/js/stdlib/builder.js +28 -9
  11. package/src/backend/js/stdlib/bytes.js +75 -18
  12. package/src/backend/js/stdlib/errors.js +25 -1
  13. package/src/backend/js/stdlib/fmt.js +70 -13
  14. package/src/backend/js/stdlib/hex.js +17 -0
  15. package/src/backend/js/stdlib/html.js +1 -1
  16. package/src/backend/js/stdlib/index.js +28 -0
  17. package/src/backend/js/stdlib/io.js +105 -3
  18. package/src/backend/js/stdlib/json.js +972 -0
  19. package/src/backend/js/stdlib/math.js +13 -1
  20. package/src/backend/js/stdlib/path.js +18 -4
  21. package/src/backend/js/stdlib/rand.js +10 -5
  22. package/src/backend/js/stdlib/sha256.js +12 -0
  23. package/src/backend/js/stdlib/simd.js +24 -0
  24. package/src/backend/js/stdlib/slices.js +52 -26
  25. package/src/backend/js/stdlib/strconv.js +21 -5
  26. package/src/backend/js/stdlib/strings.js +148 -15
  27. package/src/backend/js/stdlib/sync.js +13 -0
  28. package/src/backend/js/stdlib/time.js +90 -19
  29. package/src/backend/js/stdlib/unicode.js +2 -2
  30. package/src/backend/wasm/boundary.js +1225 -41
  31. package/src/backend/wasm/const-fold.js +205 -0
  32. package/src/backend/wasm/emit-builtins.js +220 -76
  33. package/src/backend/wasm/emit-exprs.js +475 -276
  34. package/src/backend/wasm/emit-maps.js +144 -8
  35. package/src/backend/wasm/emit-stdlib.js +12 -3005
  36. package/src/backend/wasm/emit-stmts.js +264 -270
  37. package/src/backend/wasm/emit.js +124 -87
  38. package/src/backend/wasm/encode.js +140 -29
  39. package/src/backend/wasm/index.js +1001 -32
  40. package/src/backend/wasm/optimize.js +17 -6
  41. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  42. package/src/backend/wasm/stdlib/core.js +8903 -0
  43. package/src/backend/wasm/stdlib/index.js +43 -0
  44. package/src/backend/wasm/stdlib/io.js +2161 -0
  45. package/src/backend/wasm/stdlib/packages.js +2400 -0
  46. package/src/backend/wasm/stdlib/pure.js +720 -0
  47. package/src/backend/wasm/stdlib/simd.js +234 -0
  48. package/src/backend/wasm/stdlib/time.js +1200 -0
  49. package/src/backend/wasm/types.js +174 -1
  50. package/src/backend/wasm/wat.js +37 -0
  51. package/src/cli-core.js +1 -1
  52. package/src/compiler.js +18 -4
  53. package/src/dts-parser.js +150 -140
  54. package/src/index.js +2 -6
  55. package/src/lexer.js +80 -29
  56. package/src/lower/boxing.js +1 -7
  57. package/src/lower/captures.js +2 -6
  58. package/src/lower/embedding.js +2 -1
  59. package/src/lower/ownership.js +30 -9
  60. package/src/minifier.js +20 -68
  61. package/src/parser/declarations.js +3 -6
  62. package/src/parser/expressions.js +3 -8
  63. package/src/parser/index.js +18 -3
  64. package/src/parser/statements.js +25 -38
  65. package/src/parser/struct-tag.js +144 -0
  66. package/src/parser/types.js +64 -8
  67. package/src/resolver.js +2 -27
  68. package/src/templ-lexer.js +29 -63
  69. package/src/templ-parser.js +22 -45
  70. package/src/typechecker/assignability.js +126 -13
  71. package/src/typechecker/expressions.js +168 -16
  72. package/src/typechecker/index.js +35 -8
  73. package/src/typechecker/resolve.js +187 -8
  74. package/src/typechecker/statements.js +68 -9
  75. package/src/typechecker/stdlib/core.js +280 -89
  76. package/src/typechecker/stdlib/extended.js +490 -1
  77. package/src/typechecker/stdlib/simd.js +73 -0
  78. package/src/typechecker/stdlib.js +2 -0
  79. package/src/typechecker/types.js +362 -9
  80. package/src/vendor.js +86 -11
@@ -228,6 +228,7 @@ export const expressionCheckMethods = {
228
228
  _isStructOrNamedMethod(baseType, base, field) {
229
229
  return (
230
230
  (base?.kind === "struct" && base.methods?.has(field)) ||
231
+ (base?.kind === "simd" && base.methods?.has(field)) ||
231
232
  (baseType.kind === "named" && baseType.methods?.has(field))
232
233
  );
233
234
  },
@@ -266,6 +267,16 @@ export const expressionCheckMethods = {
266
267
  if (this._isStructOrNamedMethod(baseType, base, expr.field))
267
268
  expr._isMethodValue = true;
268
269
  }
270
+ if (
271
+ baseType?.kind === "namespace" &&
272
+ this.types.has(`${baseType.name}.${expr.field}`) &&
273
+ !(
274
+ baseType.name === "binary" &&
275
+ (expr.field === "LittleEndian" || expr.field === "BigEndian")
276
+ )
277
+ ) {
278
+ expr._isTypeRef = true;
279
+ }
269
280
  return ft;
270
281
  },
271
282
 
@@ -464,6 +475,83 @@ export const expressionCheckMethods = {
464
475
  return targetType;
465
476
  },
466
477
 
478
+ _checkConversionValidity(srcType, targetType, expr) {
479
+ if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
480
+ const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
481
+ const tgtResolved =
482
+ targetType.kind === "named" ? targetType.underlying : targetType;
483
+
484
+ if (isNumeric(targetType) && isComplex(srcType)) {
485
+ this.err(
486
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
487
+ expr,
488
+ );
489
+ return;
490
+ }
491
+ if (isComplex(targetType) && !isComplexOrNumeric(srcType)) {
492
+ this.err(
493
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
494
+ expr,
495
+ );
496
+ return;
497
+ }
498
+ if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
499
+ this._checkElemTypeCompatible(
500
+ tgtResolved.elem,
501
+ srcResolved.elem,
502
+ srcType,
503
+ targetType,
504
+ expr,
505
+ );
506
+ return;
507
+ }
508
+ if (this._isInvalidArrayConv(tgtResolved, srcResolved, srcType)) {
509
+ this.err(
510
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
511
+ expr,
512
+ );
513
+ return;
514
+ }
515
+ if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
516
+ if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
517
+ if (isString(srcResolved) && isString(tgtResolved)) return;
518
+ if (isBool(srcResolved) && isBool(tgtResolved)) return;
519
+ if (
520
+ isString(srcResolved) &&
521
+ tgtResolved?.kind === "slice" &&
522
+ (tgtResolved.elem?.name === "byte" ||
523
+ tgtResolved.elem?.name === "rune" ||
524
+ tgtResolved.elem?.name === "uint8" ||
525
+ tgtResolved.elem?.name === "int32")
526
+ )
527
+ return;
528
+ if (
529
+ isString(tgtResolved) &&
530
+ (isNumeric(srcResolved) ||
531
+ (srcResolved?.kind === "slice" &&
532
+ (srcResolved.elem?.name === "byte" ||
533
+ srcResolved.elem?.name === "rune" ||
534
+ srcResolved.elem?.name === "uint8" ||
535
+ srcResolved.elem?.name === "int32")))
536
+ )
537
+ return;
538
+ if (srcResolved?.kind === "pointer" && tgtResolved?.kind === "pointer") {
539
+ const sBase =
540
+ srcResolved.base?.kind === "named"
541
+ ? srcResolved.base.underlying
542
+ : srcResolved.base;
543
+ const tBase =
544
+ tgtResolved.base?.kind === "named"
545
+ ? tgtResolved.base.underlying
546
+ : tgtResolved.base;
547
+ if (typeStr(sBase) === typeStr(tBase)) return;
548
+ }
549
+ this.err(
550
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
551
+ expr,
552
+ );
553
+ },
554
+
467
555
  _checkTypeAssertExpr(expr, scope) {
468
556
  const srcType = this.checkExpr(expr.expr, scope);
469
557
  const targetType = this.resolveTypeNode(expr.type, scope);
@@ -564,7 +652,8 @@ export const expressionCheckMethods = {
564
652
  return expr;
565
653
  },
566
654
 
567
- _constIntValue(expr) {
655
+ _constIntValue(expr, scope = null) {
656
+ if (!expr) return null;
568
657
  if (expr.kind === "BasicLit" && expr.litKind === "INT")
569
658
  return Number(expr.value);
570
659
  if (
@@ -575,6 +664,19 @@ export const expressionCheckMethods = {
575
664
  ) {
576
665
  return -Number(expr.operand.value);
577
666
  }
667
+ if (expr.kind === "UnaryExpr" && expr.op === "+") {
668
+ return this._constIntValue(expr.operand, scope);
669
+ }
670
+ if (expr.kind === "Ident") {
671
+ const c =
672
+ scope?.lookupConstExpr?.(expr.name) ??
673
+ this.globals.lookupConstExpr?.(expr.name) ??
674
+ this._constExprs?.get(expr.name);
675
+ if (c) return this._constIntValue(c, scope);
676
+ }
677
+ if (expr.kind === "ParenExpr") {
678
+ return this._constIntValue(expr.expr, scope);
679
+ }
578
680
  return null;
579
681
  },
580
682
 
@@ -629,6 +731,14 @@ export const expressionCheckMethods = {
629
731
  if (fnType.kind === "named" && expr.func._isTypeRef) {
630
732
  expr._isTypeConversion = true;
631
733
  expr._conversionTargetType = fnType;
734
+ if (expr.args.length !== 1) {
735
+ return this.err(
736
+ `cannot convert ${expr.args.length} arguments to type ${typeStr(fnType)}`,
737
+ expr,
738
+ );
739
+ }
740
+ const argType = argTypes[0];
741
+ this._checkConversionValidity(argType, fnType, expr);
632
742
  return fnType;
633
743
  }
634
744
 
@@ -638,7 +748,7 @@ export const expressionCheckMethods = {
638
748
  return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
639
749
  }
640
750
 
641
- this._checkCallArgs(fnType, argTypes, expr);
751
+ this._checkCallArgs(fnType, argTypes, expr, scope);
642
752
 
643
753
  // Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
644
754
  // how their result derives from the first argument.
@@ -702,6 +812,8 @@ export const expressionCheckMethods = {
702
812
  switch (mode) {
703
813
  case "arg0":
704
814
  return under ? arg : null;
815
+ case "elemOfArg0":
816
+ return under?.elem ?? null;
705
817
  case "keysOfArg0":
706
818
  return under?.key ? { kind: "slice", elem: under.key } : null;
707
819
  case "valuesOfArg0": {
@@ -753,15 +865,44 @@ export const expressionCheckMethods = {
753
865
  );
754
866
  },
755
867
 
756
- _checkCallArgs(fnType, argTypes, expr) {
868
+ _checkCallArgs(fnType, argTypes, expr, scope = null) {
757
869
  this._checkArgCount(fnType, argTypes, expr);
758
- for (let i = 0; i < fnType.params.length && i < argTypes.length; i++) {
870
+ const maxLen = fnType.variadic
871
+ ? argTypes.length
872
+ : Math.min(fnType.params.length, argTypes.length);
873
+ for (let i = 0; i < maxLen; i++) {
759
874
  const paramIdx =
760
875
  fnType.variadic && i >= fnType.params.length - 1
761
876
  ? fnType.params.length - 1
762
877
  : i;
763
878
  if (expr.args[i]?._spread) continue;
764
- this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
879
+ const targetParam = fnType.params[paramIdx];
880
+ const node = expr._multiForward ? null : expr.args[i];
881
+ this.assertAssignable(targetParam, argTypes[i], node);
882
+ }
883
+
884
+ // Validate Lane and WithLane constant integer lane index
885
+ if (expr.func?.kind === "SelectorExpr") {
886
+ const recvType = expr.func.expr?._type;
887
+ const isSimd =
888
+ recvType?.name === "simd.F32x4" ||
889
+ recvType?.name === "F32x4" ||
890
+ recvType?.underlying?.kind === "simd";
891
+ if (
892
+ isSimd &&
893
+ (expr.func.field === "Lane" || expr.func.field === "WithLane")
894
+ ) {
895
+ const laneArg = expr.args[0];
896
+ if (laneArg) {
897
+ const idx = this._constIntValue(laneArg, scope);
898
+ if (idx === null || idx < 0 || idx > 3) {
899
+ this.err(
900
+ "lane index must be a constant integer in range [0, 3]",
901
+ laneArg,
902
+ );
903
+ }
904
+ }
905
+ }
765
906
  }
766
907
  },
767
908
 
@@ -859,6 +1000,8 @@ export const expressionCheckMethods = {
859
1000
  this._inferArraySize(expr, typeNode, base);
860
1001
  if (base?.kind === "struct")
861
1002
  return this._checkStructCompositeLit(expr, scope, t, base);
1003
+ if (base?.kind === "simd")
1004
+ return this._checkSimdCompositeLit(expr, scope, t, base);
862
1005
  if (this._isSliceOrArray(base))
863
1006
  return this._checkSliceArrayCompositeLit(expr, scope, t, base, typeNode);
864
1007
  if (base?.kind === "map")
@@ -866,6 +1009,17 @@ export const expressionCheckMethods = {
866
1009
  return this._checkUnknownCompositeLit(expr, scope, t);
867
1010
  },
868
1011
 
1012
+ _checkSimdCompositeLit(expr, scope, t, base) {
1013
+ if (expr.elems.length > 4) {
1014
+ this.err("too many values in simd.F32x4 literal", expr);
1015
+ }
1016
+ for (const elem of expr.elems) {
1017
+ const vt = this.checkExpr(elem, scope);
1018
+ this.assertAssignable(base.elem ?? FLOAT32, vt, elem);
1019
+ }
1020
+ return t;
1021
+ },
1022
+
869
1023
  _checkStructPositionalElems(expr, scope, t, base) {
870
1024
  const fieldNames = [...(base.fields?.keys() ?? [])];
871
1025
  for (let i = 0; i < expr.elems.length; i++) {
@@ -887,21 +1041,19 @@ export const expressionCheckMethods = {
887
1041
  for (const elem of expr.elems) {
888
1042
  if (elem.kind === "KeyValueExpr") {
889
1043
  const keyName = elem.key.name;
890
- const fieldType = base.fields?.get(keyName);
1044
+ const embed = base._embeds?.find((e) => {
1045
+ const inner = e.kind === "pointer" ? e.base : e;
1046
+ const name =
1047
+ inner.kind === "named" ? inner.name.split(".").pop() : null;
1048
+ return name === keyName;
1049
+ });
1050
+ if (embed) elem._isEmbedInit = true;
1051
+ const fieldType = base.fields?.get(keyName) ?? embed;
891
1052
  if (fieldType) {
892
1053
  const vt = this.checkExpr(elem.value, scope);
893
1054
  this.assertAssignable(fieldType, vt, elem.value);
894
1055
  } else {
895
- const embed = base._embeds?.find(
896
- (e) => (e.kind === "named" ? e.name : null) === keyName,
897
- );
898
- if (embed) {
899
- const vt = this.checkExpr(elem.value, scope);
900
- this.assertAssignable(embed, vt, elem.value);
901
- elem._isEmbedInit = true;
902
- } else {
903
- this.err(`Unknown field '${keyName}'`, elem.key);
904
- }
1056
+ this.err(`Unknown field '${keyName}'`, elem.key);
905
1057
  }
906
1058
  } else {
907
1059
  this.checkExpr(elem, scope);
@@ -57,6 +57,7 @@ export class TypeChecker {
57
57
  this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
58
58
  this._deferCount = 0; // tracks defer usage in current function body
59
59
  this._imports = []; // tracked imports for unused-import detection
60
+ this._constExprs = new Map(); // name -> AST initializer expr
60
61
  this._setupGlobals();
61
62
  // Register error as a named type (interface)
62
63
  this.types.set("error", ERROR);
@@ -220,7 +221,11 @@ export class TypeChecker {
220
221
  const type = spec.type
221
222
  ? this.resolveTypeNode(spec.type, this.globals)
222
223
  : ANY;
223
- for (const name of spec.names) this.globals.defineConst(name, type);
224
+ for (let i = 0; i < spec.names.length; i++) {
225
+ const name = spec.names[i];
226
+ const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
227
+ this.globals.defineConst(name, type, expr);
228
+ }
224
229
  }
225
230
  }
226
231
 
@@ -510,7 +515,8 @@ export class TypeChecker {
510
515
  const struct = type.underlying;
511
516
  if (struct?.kind === "struct" && struct._embeds) {
512
517
  for (const embed of struct._embeds) {
513
- const base = embed.kind === "named" ? embed.underlying : embed;
518
+ const inner = embed.kind === "pointer" ? embed.base : embed;
519
+ const base = inner.kind === "named" ? inner.underlying : inner;
514
520
  if (base?.kind !== "struct" || !base.methods) continue;
515
521
  for (const [mName, mType] of base.methods.entries()) {
516
522
  if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
@@ -899,14 +905,31 @@ export class TypeChecker {
899
905
  let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
900
906
  if (spec.value) {
901
907
  const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
902
- if (!type) type = defaultType(valTypes[0]) ?? ANY;
908
+ const isTuple = valTypes.length === 1 && valTypes[0]?.kind === "tuple";
909
+ const valTypesFlat = isTuple ? valTypes[0].types : valTypes;
910
+ if (spec.names.length !== valTypesFlat.length) {
911
+ const vars =
912
+ spec.names.length === 1
913
+ ? "1 variable"
914
+ : `${spec.names.length} variables`;
915
+ const vals = isTuple
916
+ ? `returns ${valTypesFlat.length} values`
917
+ : valTypesFlat.length === 1
918
+ ? "1 value"
919
+ : `${valTypesFlat.length} values`;
920
+ this.err(`assignment mismatch: ${vars} but ${vals}`, spec);
921
+ }
903
922
  for (let i = 0; i < spec.names.length; i++) {
904
- const vt = valTypes[i] ?? ANY;
905
- this.assertAssignable(type, vt, spec.value[i]);
923
+ const vt = valTypesFlat[i] ?? ANY;
924
+ const varType = type ?? defaultType(vt) ?? ANY;
925
+ if (type)
926
+ this.assertAssignable(type, vt, spec.value[i] ?? spec.value[0]);
927
+ scope.defineLocal(spec.names[i], varType);
906
928
  }
929
+ } else {
930
+ if (!type) type = ANY;
931
+ for (const name of spec.names) scope.defineLocal(name, type);
907
932
  }
908
- if (!type) type = ANY;
909
- for (const name of spec.names) scope.defineLocal(name, type);
910
933
  }
911
934
  this._sharedAllocOk = savedSharedAlloc;
912
935
  }
@@ -918,7 +941,11 @@ export class TypeChecker {
918
941
  const type = spec.type
919
942
  ? this.resolveTypeNode(spec.type, scope)
920
943
  : (valTypes[0] ?? ANY);
921
- for (const name of spec.names) scope.defineConst(name, type);
944
+ for (let i = 0; i < spec.names.length; i++) {
945
+ const name = spec.names[i];
946
+ const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
947
+ scope.defineConst(name, type, expr);
948
+ }
922
949
  }
923
950
  }
924
951
 
@@ -1,6 +1,7 @@
1
1
  // Type resolution methods — resolveTypeNode, resolveType, fieldType, generics.
2
2
  // Installed as a mixin on TypeChecker.prototype.
3
3
 
4
+ import { parseJsonTag } from "../parser/struct-tag.js";
4
5
  import {
5
6
  ANY,
6
7
  BASIC_TYPES,
@@ -97,20 +98,29 @@ export const resolveMethods = {
97
98
  }
98
99
  return this.err(`Unknown type '${node.name}'`, node);
99
100
  }
100
- case "GenericTypeName": {
101
- const base = this.types.get(node.name);
102
- if (!base) return this.err(`Unknown type '${node.name}'`, node);
101
+ case "GenericTypeName":
102
+ case "InstantiationExpr":
103
+ case "IndexExpr": {
104
+ const name =
105
+ node.name ?? (node.expr?.kind === "Ident" ? node.expr.name : null);
106
+ const typeArgs = node.typeArgs ?? (node.index ? [node.index] : []);
107
+ const base = name
108
+ ? this.types.get(name)
109
+ : node.expr
110
+ ? this.resolveTypeNode(node.expr, scope)
111
+ : null;
112
+ if (!base) return this.err(`Unknown type '${name ?? "?"}'`, node);
103
113
  if (base.kind === "named" && base._generic) {
104
114
  const res = this.instantiateGenericType(
105
115
  base._generic,
106
- node.typeArgs,
116
+ typeArgs,
107
117
  scope,
108
118
  );
109
119
  node._typeArgs = res.typeArgs;
110
120
  node._genericType = base;
111
121
  return res;
112
122
  }
113
- return this.err(`Type '${node.name}' is not generic`, node);
123
+ return this.err(`Type '${name ?? "?"}' is not generic`, node);
114
124
  }
115
125
  case "TypeParam": {
116
126
  const constraint = node.constraint
@@ -200,10 +210,28 @@ export const resolveMethods = {
200
210
  _resolveStructType(node, scope) {
201
211
  const fields = new Map();
202
212
  const embeds = [];
213
+ const structFields = [];
203
214
  for (const f of node.fields) {
204
215
  const ft = this.resolveTypeNode(f.type, scope);
216
+ structFields.push({
217
+ names: f.names,
218
+ type: ft,
219
+ tag: f.tag ?? null,
220
+ embedded: Boolean(f.embedded),
221
+ astType: f.type,
222
+ });
205
223
  if (f.embedded) {
206
- const base = ft.kind === "named" ? ft.underlying : ft;
224
+ const inner = ft.kind === "pointer" ? ft.base : ft;
225
+ const embedName = (
226
+ f.astType?.name ??
227
+ f.type?.name ??
228
+ f.type?.base?.name ??
229
+ (inner.kind === "named" ? inner.name : "")
230
+ )
231
+ .split(".")
232
+ .pop();
233
+ if (embedName) fields.set(embedName, ft);
234
+ const base = inner.kind === "named" ? inner.underlying : inner;
207
235
  if (base?.kind === "struct") {
208
236
  for (const [k, v] of base.fields.entries()) fields.set(k, v);
209
237
  }
@@ -212,7 +240,13 @@ export const resolveMethods = {
212
240
  for (const n of f.names) fields.set(n, ft);
213
241
  }
214
242
  }
215
- return { kind: "struct", fields, methods: new Map(), _embeds: embeds };
243
+ return {
244
+ kind: "struct",
245
+ fields,
246
+ methods: new Map(),
247
+ _embeds: embeds,
248
+ _structFields: structFields,
249
+ };
216
250
  },
217
251
 
218
252
  _resolveInterfaceType(node, scope) {
@@ -291,6 +325,7 @@ export const resolveMethods = {
291
325
  name: generic.declNode.name,
292
326
  typeArgs,
293
327
  underlying: instantiated,
328
+ methods: instantiated.methods,
294
329
  };
295
330
  },
296
331
 
@@ -381,6 +416,14 @@ export const resolveMethods = {
381
416
  const found = this.types.get(t.alias);
382
417
  if (found) return found;
383
418
  }
419
+ if (
420
+ t.kind === "named" &&
421
+ (!t.typeArgs || t.typeArgs.length === 0) &&
422
+ (!t.underlying || !t.methods)
423
+ ) {
424
+ const found = this.types.get(t.name);
425
+ if (found) return found;
426
+ }
384
427
  return t;
385
428
  },
386
429
 
@@ -416,6 +459,7 @@ export const resolveMethods = {
416
459
  case "namespace":
417
460
  return this._fieldTypeNamespace(base, field, node);
418
461
  case "shared":
462
+ case "simd":
419
463
  if (base.methods?.has(field)) return base.methods.get(field);
420
464
  return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
421
465
  case "slice":
@@ -432,7 +476,19 @@ export const resolveMethods = {
432
476
  },
433
477
 
434
478
  _fieldTypeStruct(base, baseType, field, node) {
435
- if (base.fields?.has(field)) return base.fields.get(field);
479
+ if (base.fields?.has(field)) {
480
+ if (
481
+ field.startsWith("_") &&
482
+ (baseType?.name?.startsWith("strings.") ||
483
+ baseType?.name?.startsWith("bytes."))
484
+ ) {
485
+ return this.err(
486
+ `Cannot refer to unexported field '${field}' on ${typeStr(baseType)}`,
487
+ node,
488
+ );
489
+ }
490
+ return base.fields.get(field);
491
+ }
436
492
  if (base.methods?.has(field)) return base.methods.get(field);
437
493
  return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
438
494
  },
@@ -507,4 +563,127 @@ export const resolveMethods = {
507
563
  }
508
564
  return this.err(`No member '${field}' in namespace ${base.name}`, node);
509
565
  },
566
+
567
+ resolveJsonFields(structType) {
568
+ return resolveJsonFields(structType);
569
+ },
510
570
  };
571
+
572
+ function isExported(name) {
573
+ if (!name || typeof name !== "string") return false;
574
+ const code = name.charCodeAt(0);
575
+ return code >= 65 && code <= 90;
576
+ }
577
+
578
+ function _processJsonField(f, depth, path, visited, queue, candidates) {
579
+ const tagInfo = parseJsonTag(f.tag);
580
+ if (tagInfo.ignored) return;
581
+
582
+ if (f.embedded) {
583
+ const isPtr = f.type.kind === "pointer" || f.type.kind === "PointerType";
584
+ const inner = isPtr ? f.type.base : f.type;
585
+ const embedStruct = inner.kind === "named" ? inner.underlying : inner;
586
+ const embedName = (
587
+ f.astType?.name ??
588
+ f.astType?.base?.name ??
589
+ inner.name ??
590
+ ""
591
+ )
592
+ .split(".")
593
+ .pop();
594
+
595
+ if (tagInfo.name !== null) {
596
+ if (isExported(embedName)) {
597
+ candidates.push({
598
+ jsonKey: tagInfo.name,
599
+ goName: embedName,
600
+ type: f.type,
601
+ omitempty: tagInfo.omitempty,
602
+ depth,
603
+ tagged: true,
604
+ isEmbedded: true,
605
+ path: [...path, { name: embedName, isPtr, typeName: embedName }],
606
+ });
607
+ }
608
+ } else if (embedStruct?.kind === "struct") {
609
+ if (!visited.has(embedStruct)) {
610
+ const nextVisited = new Set(visited);
611
+ nextVisited.add(embedStruct);
612
+ queue.push({
613
+ struct: embedStruct,
614
+ depth: depth + 1,
615
+ path: [...path, { name: embedName, isPtr, typeName: embedName }],
616
+ visited: nextVisited,
617
+ });
618
+ }
619
+ }
620
+ return;
621
+ }
622
+
623
+ for (const name of f.names) {
624
+ if (!isExported(name)) continue;
625
+ candidates.push({
626
+ jsonKey: tagInfo.name !== null ? tagInfo.name : name,
627
+ goName: name,
628
+ type: f.type,
629
+ omitempty: tagInfo.omitempty,
630
+ quoted: tagInfo.options.includes("string"),
631
+ depth,
632
+ tagged: tagInfo.name !== null,
633
+ path: [...path, { name, isPtr: false }],
634
+ });
635
+ }
636
+ }
637
+
638
+ function _resolveCandidateCollisions(candidates) {
639
+ const byKey = new Map();
640
+ for (const cand of candidates) {
641
+ if (!byKey.has(cand.jsonKey)) byKey.set(cand.jsonKey, []);
642
+ byKey.get(cand.jsonKey).push(cand);
643
+ }
644
+
645
+ const winningFields = [];
646
+ for (const [, group] of byKey.entries()) {
647
+ let minDepth = Infinity;
648
+ for (const c of group) {
649
+ if (c.depth < minDepth) minDepth = c.depth;
650
+ }
651
+
652
+ const atMin = group.filter((c) => c.depth === minDepth);
653
+ if (atMin.length === 1) {
654
+ winningFields.push(atMin[0]);
655
+ } else {
656
+ const tagged = atMin.filter((c) => c.tagged);
657
+ if (tagged.length === 1) winningFields.push(tagged[0]);
658
+ }
659
+ }
660
+
661
+ winningFields.sort((a, b) => candidates.indexOf(a) - candidates.indexOf(b));
662
+ return winningFields;
663
+ }
664
+
665
+ export function resolveJsonFields(structType) {
666
+ if (!structType) return [];
667
+ const underlying =
668
+ structType.kind === "named" ? structType.underlying : structType;
669
+ if (underlying?.kind !== "struct") return [];
670
+ if (underlying._jsonFields) return underlying._jsonFields;
671
+
672
+ const queue = [
673
+ { struct: underlying, depth: 0, path: [], visited: new Set([underlying]) },
674
+ ];
675
+ const candidates = [];
676
+
677
+ while (queue.length > 0) {
678
+ const { struct: curr, depth, path, visited } = queue.shift();
679
+ if (curr?.kind !== "struct") continue;
680
+
681
+ for (const f of curr._structFields || []) {
682
+ _processJsonField(f, depth, path, visited, queue, candidates);
683
+ }
684
+ }
685
+
686
+ const winning = _resolveCandidateCollisions(candidates);
687
+ underlying._jsonFields = winning;
688
+ return winning;
689
+ }