gofront 1.9.0 → 1.9.2

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 (43) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/backend/js/expressions.js +329 -58
  4. package/src/backend/js/index.js +90 -10
  5. package/src/backend/js/runtime.js +51 -9
  6. package/src/backend/js/statements.js +193 -84
  7. package/src/backend/js/type-info.js +123 -8
  8. package/src/backend/shared/fmt-types.js +30 -1
  9. package/src/backend/wasm/emit-builtins.js +250 -9
  10. package/src/backend/wasm/emit-exprs.js +63 -22
  11. package/src/backend/wasm/emit-shared.js +4 -1
  12. package/src/backend/wasm/emit-stmts.js +239 -205
  13. package/src/backend/wasm/emit.js +73 -26
  14. package/src/backend/wasm/globals.js +1 -0
  15. package/src/backend/wasm/index.js +8 -0
  16. package/src/backend/wasm/module-emitter.js +211 -57
  17. package/src/backend/wasm/monomorph.js +36 -2
  18. package/src/backend/wasm/natives.js +4 -0
  19. package/src/backend/wasm/types.js +32 -3
  20. package/src/backend/wasm/variadic.js +6 -1
  21. package/src/cli/minifier.js +20 -6
  22. package/src/compiler.js +6 -1
  23. package/src/lexer/lexer.js +1 -0
  24. package/src/link/stdlib.js +1 -0
  25. package/src/link/type-names.js +72 -16
  26. package/src/lower/embedding.js +43 -15
  27. package/src/lower/fmt-formatters.js +38 -14
  28. package/src/lower/functions.js +223 -7
  29. package/src/lower/gen-types.js +21 -2
  30. package/src/lower/index.js +7 -1
  31. package/src/lower/json-codecs.js +114 -6
  32. package/src/parser/declarations.js +4 -2
  33. package/src/parser/expressions.js +25 -41
  34. package/src/parser/statements.js +107 -0
  35. package/src/parser/types.js +11 -0
  36. package/src/typechecker/assignability.js +94 -12
  37. package/src/typechecker/constants.js +124 -2
  38. package/src/typechecker/expressions.js +171 -17
  39. package/src/typechecker/index.js +158 -29
  40. package/src/typechecker/operand-text.js +1 -0
  41. package/src/typechecker/resolve.js +308 -39
  42. package/src/typechecker/statements.js +171 -21
  43. package/src/typechecker/types.js +84 -2
@@ -44,6 +44,7 @@ import {
44
44
  UNTYPED_COMPLEX,
45
45
  UNTYPED_FLOAT,
46
46
  UNTYPED_INT,
47
+ UNTYPED_RUNE,
47
48
  UNTYPED_STRING,
48
49
  VOID,
49
50
  } from "./types.js";
@@ -71,7 +72,7 @@ function minMaxType(at) {
71
72
  // Each value is (self, name, expr, argTypes, scope) => returnType.
72
73
  const BUILTIN_CHECK = {
73
74
  len: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
74
- cap: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
75
+ cap: (s, n, e, at, sc) => s._checkBuiltinLen(n, e, at, sc),
75
76
  append: (s, _n, e, at) => {
76
77
  const st = at[0] ?? ANY;
77
78
  if (isArray(st)) {
@@ -93,6 +94,11 @@ const BUILTIN_CHECK = {
93
94
  s._rejectSeqAsSlice(at[i], e.args[i], "a spread argument");
94
95
  if (!e.args[i]._spread && at[i]) {
95
96
  if (is64) e.args[i]._is64Bit = true;
97
+ s._tagComplexConst(
98
+ s.resolveType(sb.elem),
99
+ s.resolveType(at[i]),
100
+ e.args[i],
101
+ );
96
102
  s._markIfaceBox(
97
103
  s.resolveType(sb.elem),
98
104
  s.resolveType(at[i]),
@@ -106,7 +112,10 @@ const BUILTIN_CHECK = {
106
112
  copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
107
113
  delete: (s, _n, e, at) => {
108
114
  const mt = s._deepUnderlying(at[0]);
109
- if (mt?.kind === "map" && e.args[1]) s._markIfaceKey(mt, e.args[1], at[1]);
115
+ if (mt?.kind === "map" && e.args[1]) {
116
+ s._tagFloat32Const(s.resolveType(mt.key), at[1], e.args[1]);
117
+ s._markIfaceKey(mt, e.args[1], at[1]);
118
+ }
110
119
  return VOID;
111
120
  },
112
121
  make: (s, _n, e, _at, sc) => {
@@ -117,6 +126,8 @@ const BUILTIN_CHECK = {
117
126
  // `make(S, n)` with S a type parameter makes its core type.
118
127
  const core = made.kind === "typeParam" ? s._coreType(made, sc) : null;
119
128
  if (core) ta._coreType = core;
129
+ for (const size of e.args.slice(1, 3))
130
+ s._checkIntegerConstOperand(size, "size", sc);
120
131
  return made;
121
132
  },
122
133
  new: (s, _n, e, _at, sc) => {
@@ -134,14 +145,37 @@ const BUILTIN_CHECK = {
134
145
  },
135
146
  recover: () => ANY,
136
147
  error: () => ERROR,
137
- min: (_s, _n, _e, at) => minMaxType(at),
138
- max: (_s, _n, _e, at) => minMaxType(at),
148
+ min: (s, _n, e, at) => s._checkMinMax(e, at),
149
+ max: (s, _n, e, at) => s._checkMinMax(e, at),
139
150
  clear: () => VOID,
140
151
  complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
141
152
  real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
142
153
  imag: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
143
154
  };
144
155
 
156
+ // Type kinds that are never valid as an index, slice bound or `make` size.
157
+ const NON_INTEGER_KINDS = new Set([
158
+ "struct",
159
+ "pointer",
160
+ "slice",
161
+ "map",
162
+ "func",
163
+ "array",
164
+ "interface",
165
+ "simd",
166
+ ]);
167
+
168
+ // Basic types that are never valid as an index, slice bound or `make` size.
169
+ const NON_INTEGER_BASICS = new Set([
170
+ "float32",
171
+ "float64",
172
+ "complex64",
173
+ "complex128",
174
+ "bool",
175
+ "string",
176
+ "any",
177
+ ]);
178
+
145
179
  // Method-name dispatch for _checkExpr — string values add no static call edges.
146
180
  const CHECK_EXPR_DELEGATE = {
147
181
  UnaryExpr: "_checkUnaryExpr",
@@ -171,7 +205,7 @@ export const expressionCheckMethods = {
171
205
  case "BasicLit": {
172
206
  switch (expr.litKind) {
173
207
  case "INT":
174
- return UNTYPED_INT;
208
+ return expr.rune ? UNTYPED_RUNE : UNTYPED_INT;
175
209
  case "FLOAT":
176
210
  return UNTYPED_FLOAT;
177
211
  case "STRING":
@@ -260,6 +294,10 @@ export const expressionCheckMethods = {
260
294
  this._retypeUntypedShift(lt, rt, expr.right);
261
295
  this._retypeUntypedShift(rt, lt, expr.left);
262
296
  }
297
+ this._tagFloat32Const(lt, rt, expr.right);
298
+ this._tagFloat32Const(rt, lt, expr.left);
299
+ this._tagComplex64Const(lt, rt, expr.right);
300
+ this._tagComplex64Const(rt, lt, expr.left);
263
301
  if (is64BitInt(lt) && rt?.kind === "untyped" && expr.right)
264
302
  expr.right._is64Bit = true;
265
303
  if (is64BitInt(rt) && lt?.kind === "untyped" && expr.left)
@@ -310,7 +348,8 @@ export const expressionCheckMethods = {
310
348
 
311
349
  _checkBoolUnaryOp(ot, expr) {
312
350
  if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
313
- return BOOL;
351
+ // `!` keeps an untyped or defined bool type.
352
+ return isBool(ot) ? ot : BOOL;
314
353
  },
315
354
 
316
355
  _checkNumericUnaryOp(ot, expr) {
@@ -826,6 +865,15 @@ export const expressionCheckMethods = {
826
865
  // with its dynamic type, so `L("1")`, `N(1)`, `"1"` and `1` stay distinct.
827
866
  // A named basic value is boxed first, exactly as in any interface
828
867
  // conversion; other values are keyed as they are.
868
+ // Untyped constant operands take the result type, so a float32 result
869
+ // rounds them like the typed operands.
870
+ _checkMinMax(e, at) {
871
+ const rt = minMaxType(at);
872
+ for (let i = 0; i < e.args.length; i++)
873
+ this._tagFloat32Const(rt, at[i], e.args[i]);
874
+ return rt;
875
+ },
876
+
829
877
  _markIfaceKey(mapType, node, srcType) {
830
878
  const key = this.resolveType(mapType.key);
831
879
  const kb = key?.kind === "named" ? this.resolveType(key.underlying) : key;
@@ -866,6 +914,8 @@ export const expressionCheckMethods = {
866
914
  if (bt?._tainted) return TAINTED_ANY;
867
915
  if (isAny(bt)) return ANY;
868
916
  const btu = this._coreUnderlying(bt, scope);
917
+ if (btu.kind !== "map")
918
+ this._checkIntegerConstOperand(expr.index, "index", scope);
869
919
  if (btu.kind === "slice" || btu.kind === "array") {
870
920
  this._checkArrayBoundsExpr(btu, bt, expr, scope);
871
921
  return btu.elem;
@@ -955,15 +1005,61 @@ export const expressionCheckMethods = {
955
1005
  return t.base;
956
1006
  },
957
1007
 
1008
+ // An index, slice bound or `make` size that is a constant must be an
1009
+ // integer (`s[1 == 1]`, `make([]int, "a")`).
1010
+ // A typed operand must have an integer type too (`s[f]` with `f float64`).
1011
+ _checkIntegerConstOperand(operand, what, scope) {
1012
+ const kind = this._nonIntegerConst(operand, scope);
1013
+ if (kind) {
1014
+ this.err(
1015
+ `invalid argument: ${what} ${exprText(operand)} (${kind}) must be integer`,
1016
+ operand,
1017
+ );
1018
+ return;
1019
+ }
1020
+ const t = operand?._type;
1021
+ if (!this._isNonIntegerType(t, scope)) return;
1022
+ this.err(
1023
+ `invalid argument: ${what} ${exprText(operand)} (${operandKind(operand, typeStr(t), false)}) must be integer`,
1024
+ operand,
1025
+ );
1026
+ },
1027
+
1028
+ // Whether `t` is a type (or a defined type over one) that is not an
1029
+ // integer: a float, complex, bool, string, `any`, struct, pointer, slice,
1030
+ // map, func, array, interface, simd vector or a type parameter whose type
1031
+ // set is not all integers. Untyped constants are judged by
1032
+ // `_nonIntegerConst`, so only untyped non-integers that reach here
1033
+ // (`s[a == b]`) count.
1034
+ _isNonIntegerType(t, scope) {
1035
+ if (!t || t._tainted) return false;
1036
+ // A type parameter is an integer only when every type in its type set
1037
+ // is (`~int | ~uint8`); `any`, `comparable` and method-only
1038
+ // constraints have no integer type set.
1039
+ if (t.kind === "typeParam") {
1040
+ const terms = this._constraintTerms(t.constraint, new Set(), scope);
1041
+ return !(terms?.length > 0 && terms.every(isInteger));
1042
+ }
1043
+ if (t.kind === "untyped")
1044
+ return (
1045
+ t.base === "bool" || t.base === "string" || t.base === "complex128"
1046
+ );
1047
+ const u = this._deepUnderlying(t);
1048
+ if (u?.kind === "basic") return NON_INTEGER_BASICS.has(u.name);
1049
+ return NON_INTEGER_KINDS.has(u?.kind);
1050
+ },
1051
+
958
1052
  _checkSliceExpr(expr, scope) {
959
1053
  const bt = this._derefArrayPtr(
960
1054
  expr,
961
1055
  "expr",
962
1056
  this.checkExpr(expr.expr, scope),
963
1057
  );
964
- if (expr.low) this.checkExpr(expr.low, scope);
965
- if (expr.high) this.checkExpr(expr.high, scope);
966
- if (expr.max) this.checkExpr(expr.max, scope);
1058
+ for (const bound of [expr.low, expr.high, expr.max]) {
1059
+ if (!bound) continue;
1060
+ this.checkExpr(bound, scope);
1061
+ this._checkIntegerConstOperand(bound, "index", scope);
1062
+ }
967
1063
  if (bt?._tainted) return TAINTED_ANY;
968
1064
  if (isAny(bt)) return ANY;
969
1065
  const btu = this._coreUnderlying(bt, scope);
@@ -1070,6 +1166,16 @@ export const expressionCheckMethods = {
1070
1166
  // `any(x)` / `error(x)`: box x as an implicit conversion would.
1071
1167
  this._markIfaceBox(targetType, srcType, expr.expr);
1072
1168
  this._rejectNilConversion(srcType, targetType, expr);
1169
+ if (this._checkIfaceConversion(srcType, targetType, expr, expr.expr))
1170
+ return targetType;
1171
+ if (
1172
+ srcType &&
1173
+ targetType &&
1174
+ !isAny(srcType) &&
1175
+ !isAny(targetType) &&
1176
+ this._checkIfaceTargetConversion(srcType, targetType, expr.expr)
1177
+ )
1178
+ return targetType;
1073
1179
  const srcResolved =
1074
1180
  srcType?.kind === "named" ? srcType.underlying : srcType;
1075
1181
  const tgtResolved =
@@ -1090,6 +1196,8 @@ export const expressionCheckMethods = {
1090
1196
  expr,
1091
1197
  );
1092
1198
  }
1199
+ // `complex64(1 + 0.01i)`: the constant's components round to float32.
1200
+ this._tagComplex64Const(targetType, srcType, expr.expr);
1093
1201
  if (tgtResolved?.kind === "slice")
1094
1202
  this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
1095
1203
  if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
@@ -1138,8 +1246,25 @@ export const expressionCheckMethods = {
1138
1246
  return true;
1139
1247
  },
1140
1248
 
1249
+ // `I(x)` with I an interface type converts exactly when x is assignable to
1250
+ // I, so the method-set check (and its wording) is the assignment one.
1251
+ _checkIfaceTargetConversion(srcType, targetType, operand) {
1252
+ // `error("msg")` builds an error value (GoFront extension).
1253
+ if (targetType === ERROR && isString(srcType)) return false;
1254
+ const base = this.resolveType(
1255
+ targetType.kind === "named" ? targetType.underlying : targetType,
1256
+ );
1257
+ if (base?.kind !== "interface" || !operand) return false;
1258
+ this.assertAssignable(targetType, srcType, operand, "conversion");
1259
+ return true;
1260
+ },
1261
+
1141
1262
  _checkConversionValidity(srcType, targetType, expr) {
1263
+ if (this._checkIfaceConversion(srcType, targetType, expr, expr.args?.[0]))
1264
+ return;
1142
1265
  if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
1266
+ if (this._checkIfaceTargetConversion(srcType, targetType, expr.args?.[0]))
1267
+ return;
1143
1268
  const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
1144
1269
  const tgtResolved =
1145
1270
  targetType.kind === "named" ? targetType.underlying : targetType;
@@ -1813,6 +1938,14 @@ export const expressionCheckMethods = {
1813
1938
  const node = expr._multiForward ? null : expr.args[i];
1814
1939
  this.assertAssignable(targetParam, argTypes[i], node, "argument");
1815
1940
  }
1941
+ // `g(f())`: per-element box marks for lowering to split the forward.
1942
+ if (
1943
+ expr._multiForward &&
1944
+ !fnType.variadic &&
1945
+ !this._skipIfaceMark &&
1946
+ fnType.params.length === argTypes.length
1947
+ )
1948
+ this._markForwardedElems(fnType.params, argTypes, expr.args[0]);
1816
1949
 
1817
1950
  this._checkSimdLaneArgs(expr, scope);
1818
1951
  },
@@ -1878,7 +2011,28 @@ export const expressionCheckMethods = {
1878
2011
  return null;
1879
2012
  },
1880
2013
 
1881
- _checkBuiltinLen(name, expr, argTypes) {
2014
+ _capOperandKind(operand, type) {
2015
+ const untypedConst = type?.kind === "untyped" && type.base !== "nil";
2016
+ return untypedConst
2017
+ ? operandKind(operand, type.base, true)
2018
+ : operandKind(operand, typeStr(type), false);
2019
+ },
2020
+
2021
+ // `cap` takes a slice or an array (a pointer to an array is dereferenced
2022
+ // before this check). A type parameter needs a slice or array core type;
2023
+ // one with no single core type is left to instantiation. Unknown and
2024
+ // `any` operands (JS interop) are not judged.
2025
+ _capOperandOk(t, scope) {
2026
+ if (!t || t._tainted || isAny(t)) return true;
2027
+ if (t.kind === "typeParam") {
2028
+ const core = this._coreType(t, scope);
2029
+ return !core || core.kind === "slice" || core.kind === "array";
2030
+ }
2031
+ const kind = this._deepUnderlying(t)?.kind;
2032
+ return kind === "slice" || kind === "array";
2033
+ },
2034
+
2035
+ _checkBuiltinLen(name, expr, argTypes, scope) {
1882
2036
  this._rejectSeqAsSlice(argTypes[0], expr.args?.[0], `${name}()`);
1883
2037
  const operand = expr.args?.[0];
1884
2038
  if (expr.args?.length === 1)
@@ -1891,17 +2045,14 @@ export const expressionCheckMethods = {
1891
2045
  if (expr.args.length === 1) expr.args[0] = operand;
1892
2046
  }
1893
2047
  if (name === "cap") {
1894
- if (
1895
- (argTypes[0]?.underlying ?? argTypes[0])?.kind === "map" ||
1896
- isString(argTypes[0])
1897
- ) {
2048
+ if (sharedInfo(argTypes[0])) {
1898
2049
  this.err(
1899
- `invalid argument: ${exprText(expr.args?.[0])} (${operandKind(expr.args?.[0], typeStr(argTypes[0]), false)}) for cap`,
2050
+ `cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
1900
2051
  expr,
1901
2052
  );
1902
- } else if (sharedInfo(argTypes[0])) {
2053
+ } else if (!this._capOperandOk(argTypes[0], scope)) {
1903
2054
  this.err(
1904
- `cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
2055
+ `invalid argument: ${exprText(expr.args?.[0])} (${this._capOperandKind(expr.args?.[0], argTypes[0])}) for cap`,
1905
2056
  expr,
1906
2057
  );
1907
2058
  }
@@ -1935,6 +2086,9 @@ export const expressionCheckMethods = {
1935
2086
  expr,
1936
2087
  );
1937
2088
  }
2089
+ // An untyped operand takes type float32 (`complex(f, 0.01)`).
2090
+ this._tagFloat32Const(FLOAT32, crt, expr.args[0]);
2091
+ this._tagFloat32Const(FLOAT32, cit, expr.args[1]);
1938
2092
  return COMPLEX64;
1939
2093
  }
1940
2094
  return COMPLEX128;
@@ -29,6 +29,7 @@ import {
29
29
  isBool,
30
30
  isVoid,
31
31
  NATIVES,
32
+ promotedMethods,
32
33
  Scope,
33
34
  TAINTED_ANY,
34
35
  TypeCheckError,
@@ -99,6 +100,50 @@ const JS_BUILTIN_CLASSES = new Set([
99
100
  "WebAssembly",
100
101
  ]);
101
102
 
103
+ // The promoted-method entry for candidate `cand`, reusing `prev` when
104
+ // nothing changed so the method keeps its identity across passes. A pointer
105
+ // method reached through an embedded pointer is in the value method set.
106
+ function promotedEntry(cand, prev) {
107
+ if (cand.ambiguous) return { depth: cand.depth, ambiguous: true };
108
+ if (
109
+ !prev?.ambiguous &&
110
+ prev?.source === cand.source &&
111
+ prev.viaPtr === cand.viaPtr &&
112
+ prev.depth === cand.depth
113
+ )
114
+ return prev;
115
+ const mType =
116
+ cand.viaPtr && cand.source?._ptrRecv
117
+ ? { ...cand.source, _ptrRecv: false }
118
+ : cand.source;
119
+ return { ...cand, mType };
120
+ }
121
+
122
+ // Offers every method of the embedded interface `iface` at depth 1.
123
+ function offerInterfaceMethods(iface, offer) {
124
+ for (const [mName, mType] of iface.methods ?? [])
125
+ offer(mName, 1, mType, false);
126
+ }
127
+
128
+ // Replaces the promoted methods `old` with `next` in `methods`, leaving
129
+ // the struct's own methods alone; returns whether anything changed.
130
+ function applyPromotions(methods, old, next, isOwn) {
131
+ let changed = old.size !== next.size;
132
+ for (const [mName, prev] of old) {
133
+ const entry = next.get(mName);
134
+ if (entry?.mType) continue;
135
+ if (prev.mType && !isOwn(mName)) methods.delete(mName);
136
+ if (entry?.ambiguous !== prev.ambiguous || entry?.depth !== prev.depth)
137
+ changed = true;
138
+ }
139
+ for (const [mName, entry] of next) {
140
+ if (entry.ambiguous) continue;
141
+ if (old.get(mName)?.mType !== entry.mType) changed = true;
142
+ methods.set(mName, entry.mType);
143
+ }
144
+ return changed;
145
+ }
146
+
102
147
  export class TypeChecker {
103
148
  constructor() {
104
149
  this.types = new Map(); // named types
@@ -130,6 +175,8 @@ export class TypeChecker {
130
175
  this._constExprs = new Map(); // name -> AST initializer expr
131
176
  this._importedConsts = new Map(); // dot-imported const name -> value
132
177
  this._externalNames = new Set(); // package-scope names from .d.ts / npm definitions
178
+ this._importedTypes = new WeakSet(); // named types reached through an imported package
179
+ this._pendingConstraintUses = []; // constraint-interface checks waiting on a type declaration
133
180
  this._setupGlobals();
134
181
  // Register error as a named type (interface)
135
182
  this.types.set("error", ERROR);
@@ -189,6 +236,7 @@ export class TypeChecker {
189
236
  // otherwise reuse the imported placeholder. It is reached as `pkg.Pt`.
190
237
  if (type.name === name) this.types.set(name, type);
191
238
  this.types.set(`${pkgName}.${name}`, type);
239
+ this._importedTypes.add(type);
192
240
  }
193
241
  }
194
242
 
@@ -383,13 +431,17 @@ export class TypeChecker {
383
431
 
384
432
  this._checkDirectivesAndImportsPass(programs);
385
433
  this._registerConstExprs(programs);
434
+ this._registerVarSpecs(programs);
435
+ this._markConstraintPositions(programs);
386
436
  this._collectTypesPass(programs);
437
+ this._flushConstraintUses();
387
438
  this._collectFuncsPass(programs);
388
439
  this._checkGlobalTypeNamesPass(programs);
389
440
  this._promoteEmbeddedMethods();
390
441
  this._collectVarsConstsPass(programs);
391
442
  this._checkTopDeclsPass(programs);
392
443
  this._reportInitCycles(programs);
444
+ this._flushConstraintUses();
393
445
 
394
446
  if (this.target === "both") {
395
447
  this._checkMutablePackageVars(programs);
@@ -707,6 +759,19 @@ export class TypeChecker {
707
759
  }
708
760
  }
709
761
 
762
+ // Package variables by name, so an array length such as `[len(base)]T`
763
+ // can use the type of one that is declared later.
764
+ _registerVarSpecs(programs) {
765
+ this._pkgVarSpecs = new Map();
766
+ for (const p of programs)
767
+ for (const d of p.decls)
768
+ if (d.kind === "VarDecl")
769
+ for (const spec of d.decls)
770
+ spec.names.forEach((name, index) => {
771
+ this._pkgVarSpecs.set(name, { spec, index, busy: false });
772
+ });
773
+ }
774
+
710
775
  _collectVarsConstsPass(programs) {
711
776
  for (const p of programs) {
712
777
  this._setCurrentFile(p);
@@ -798,45 +863,100 @@ export class TypeChecker {
798
863
  }
799
864
  }
800
865
  const count = () =>
801
- structs.reduce(
802
- (n, st) => n + (st?.methods?.size ?? 0) + (st?.fields?.size ?? 0),
803
- 0,
804
- );
866
+ structs.reduce((n, st) => n + (st?.fields?.size ?? 0), 0);
805
867
  let before;
868
+ let changed;
806
869
  do {
807
870
  before = count();
808
- for (const st of structs) this._promoteEmbeddedInto(st);
809
- } while (count() !== before);
871
+ changed = false;
872
+ for (const st of structs)
873
+ if (this._promoteEmbeddedInto(st)) changed = true;
874
+ } while (changed || count() !== before);
810
875
  }
811
876
 
877
+ // Recomputes the methods `struct` gets from its embedded fields, as Go's
878
+ // method set: the shallowest method of each name wins, a method declared
879
+ // on the struct itself shadows every promoted one, and a pointer method
880
+ // reached through an embedded pointer belongs to the value method set.
881
+ // Returns whether the promoted methods changed.
812
882
  _promoteEmbeddedInto(struct) {
813
- if (struct?.kind !== "struct" || !struct._embeds) return;
814
- for (const embed of struct._embeds) {
815
- const inner = embed.kind === "pointer" ? embed.base : embed;
816
- const base = inner.kind === "named" ? inner.underlying : inner;
817
- // Fields of an embedded type declared after the embedder were not
818
- // there when the embedder's fields were collected.
819
- if (base?.kind === "struct" && struct.fields)
820
- for (const [k, v] of base.fields)
821
- if (!struct.fields.has(k)) struct.fields.set(k, v);
822
- // A named non-struct type (`time.Duration`) keeps its methods on
823
- // the named type itself.
824
- const methods =
825
- base?.kind === "struct"
826
- ? base.methods
827
- : inner.kind === "named" && base?.kind !== "interface"
828
- ? inner.methods
829
- : null;
830
- if (!methods) continue;
831
- for (const [mName, mType] of methods.entries()) {
832
- if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
833
- }
883
+ if (struct?.kind !== "struct" || !struct._embeds) return false;
884
+ const old = promotedMethods.get(struct) ?? new Map();
885
+ const best = new Map();
886
+ for (const embed of struct._embeds)
887
+ this._offerEmbedMethods(struct, embed, best);
888
+ if (best.size === 0 && old.size === 0) return false;
889
+ struct.methods ??= new Map();
890
+ // A method declared on the struct (or set since the last pass) is
891
+ // not one this pass promoted.
892
+ const isOwn = (mName) =>
893
+ struct.methods.has(mName) &&
894
+ struct.methods.get(mName) !== old.get(mName)?.mType;
895
+ const next = new Map();
896
+ for (const [mName, cand] of best)
897
+ if (!isOwn(mName)) next.set(mName, promotedEntry(cand, old.get(mName)));
898
+ promotedMethods.set(struct, next);
899
+ return applyPromotions(struct.methods, old, next, isOwn);
900
+ }
901
+
902
+ // Adds the methods one embedded field offers to `best` (name -> the
903
+ // shallowest candidate), and copies its fields into the embedder.
904
+ _offerEmbedMethods(struct, embed, best) {
905
+ const isPtr = embed.kind === "pointer";
906
+ const inner = isPtr ? embed.base : embed;
907
+ const base = inner.kind === "named" ? inner.underlying : inner;
908
+ // Two candidates at the same depth make the name ambiguous, as does
909
+ // an ambiguous name of the embedded type.
910
+ const offer = (name, depth, source, viaPtr, ambiguous = false) => {
911
+ const cur = best.get(name);
912
+ if (!cur || depth < cur.depth)
913
+ best.set(name, { depth, source, viaPtr, ambiguous });
914
+ else if (depth === cur.depth) cur.ambiguous = true;
915
+ };
916
+ if (base?.kind === "struct") {
917
+ this._offerStructEmbed(struct, base, isPtr, offer);
918
+ return;
919
+ }
920
+ // An embedded interface (named, literal or `error`) promotes every
921
+ // method of its method set, as a value method.
922
+ if (base?.kind === "interface") {
923
+ if (!isPtr) offerInterfaceMethods(base, offer);
924
+ return;
925
+ }
926
+ // A named non-struct type (`time.Duration`) keeps its methods on the
927
+ // named type itself.
928
+ if (inner.kind === "named")
929
+ for (const [mName, mType] of inner.methods ?? [])
930
+ offer(mName, 1, mType, isPtr);
931
+ }
932
+
933
+ // Offers the methods of the embedded struct `base`, and copies its fields
934
+ // into the embedder.
935
+ _offerStructEmbed(struct, base, isPtr, offer) {
936
+ // Fields of an embedded type declared after the embedder were not
937
+ // there when the embedder's fields were collected.
938
+ if (struct.fields)
939
+ for (const [k, v] of base.fields)
940
+ if (!struct.fields.has(k)) struct.fields.set(k, v);
941
+ const innerPromoted = promotedMethods.get(base);
942
+ for (const [mName, mType] of base.methods ?? []) {
943
+ const p = innerPromoted?.get(mName);
944
+ offer(
945
+ mName,
946
+ 1 + (p?.depth ?? 0),
947
+ p?.source ?? mType,
948
+ isPtr || Boolean(p?.viaPtr),
949
+ );
834
950
  }
951
+ for (const [mName, p] of innerPromoted ?? [])
952
+ if (p.ambiguous) offer(mName, 1 + p.depth, null, false, true);
835
953
  }
836
954
 
837
955
  // ── Type collection ──────────────────────────────────────────
838
956
 
839
957
  collectType(decl) {
958
+ // `type N interface{ ~int }` and `type D N` declare constraints.
959
+ this._markConstraintPos(decl.type);
840
960
  if (decl.typeParams && decl.isAlias) {
841
961
  this._collectGenericAlias(decl);
842
962
  return;
@@ -897,8 +1017,10 @@ export class TypeChecker {
897
1017
  return;
898
1018
  }
899
1019
  const existing = this.types.get(decl.name);
1020
+ // An imported package's type is never reused: a local declaration of
1021
+ // the same name gets its own type and leaves `pkg.T` untouched.
900
1022
  const named =
901
- existing?.kind === "named"
1023
+ existing?.kind === "named" && !this._importedTypes.has(existing)
902
1024
  ? existing
903
1025
  : { kind: "named", name: decl.name };
904
1026
  named.underlying = underlying;
@@ -917,7 +1039,8 @@ export class TypeChecker {
917
1039
  }
918
1040
 
919
1041
  _registerLocalTypePlaceholder(decl) {
920
- if (decl.isAlias || this.types.has(decl.name)) return;
1042
+ const prior = this.types.get(decl.name);
1043
+ if (decl.isAlias || (prior && !this._importedTypes.has(prior))) return;
921
1044
  const placeholder = { kind: "named", name: decl.name, underlying: null };
922
1045
  this.types.set(decl.name, placeholder);
923
1046
  this.globals.define(decl.name, placeholder);
@@ -1510,6 +1633,12 @@ export class TypeChecker {
1510
1633
  );
1511
1634
  // `const D Duration = 1` is a BigInt in JS.
1512
1635
  if (spec.type && expr) this._tag64BitNode(this.resolveType(type), expr);
1636
+ if (spec.type && expr)
1637
+ this._tagFloat32Const(
1638
+ this.resolveType(type),
1639
+ valTypes[i] ?? valTypes[0],
1640
+ expr,
1641
+ );
1513
1642
  scope.defineConst(name, type, expr);
1514
1643
  }
1515
1644
  }
@@ -16,6 +16,7 @@ function litText(node) {
16
16
  const PRINTERS = {
17
17
  Ident: (n) => n.name,
18
18
  BasicLit: litText,
19
+ ImagLit: (n) => `${n.value}i`,
19
20
  ParenExpr: (n) => `(${exprText(n.expr)})`,
20
21
  SelectorExpr: (n) => `${exprText(n.expr)}.${n.field}`,
21
22
  UnaryExpr: (n) => `${n.op}${exprText(n.operand)}`,