gofront 1.7.0 → 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 (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  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 +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  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 +10 -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 +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  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 +512 -77
  34. package/src/backend/wasm/encode.js +43 -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/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -1,24 +1,32 @@
1
1
  // TypeChecker expression-checking methods — installed as a mixin on TypeChecker.prototype.
2
2
 
3
+ import { EXCLUDED_STDLIB_MEMBERS } from "./stdlib/excluded.js";
3
4
  import { sharedInfo } from "./stdlib/shared.js";
4
5
  import {
5
6
  ANY,
6
7
  BOOL,
7
8
  BROWSER_GLOBALS,
9
+ COMPLEX64,
8
10
  COMPLEX128,
11
+ compositeLitMaxIndex,
12
+ displayTypeName,
9
13
  ERROR,
14
+ FLOAT32,
10
15
  FLOAT64,
11
16
  INT,
12
17
  isAny,
13
18
  isArray,
14
19
  isBool,
15
20
  isComplex,
21
+ isComplex64,
16
22
  isComplexOrNumeric,
23
+ isInteger,
17
24
  isNumeric,
18
25
  isString,
19
26
  NIL,
20
27
  RUNE,
21
28
  Scope,
29
+ SIZED_INT_NAMES,
22
30
  STRING,
23
31
  TAINTED_ANY,
24
32
  typeStr,
@@ -32,6 +40,14 @@ import {
32
40
 
33
41
  /** @typedef {import('./index.js').TypeChecker} TypeChecker */
34
42
 
43
+ // min/max: the type of the first typed operand; with only untyped constants,
44
+ // an untyped float if any operand is one (Go's "largest kind" rule).
45
+ function minMaxType(at) {
46
+ const typed = at.find((t) => t && t.kind !== "untyped");
47
+ if (typed) return typed;
48
+ return at.find((t) => t?.base === "float64") ?? at[0] ?? ANY;
49
+ }
50
+
35
51
  // Dispatch table for builtin function type checking.
36
52
  // Each value is (self, name, expr, argTypes, scope) => returnType.
37
53
  const BUILTIN_CHECK = {
@@ -53,6 +69,8 @@ const BUILTIN_CHECK = {
53
69
  const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
54
70
  if (sb?.kind === "slice") {
55
71
  for (let i = 1; i < e.args.length; i++) {
72
+ if (e.args[i]._spread)
73
+ s._rejectSeqAsSlice(at[i], e.args[i], "a spread argument");
56
74
  if (!e.args[i]._spread && at[i])
57
75
  s._markIfaceBox(
58
76
  s.resolveType(sb.elem),
@@ -64,7 +82,11 @@ const BUILTIN_CHECK = {
64
82
  return st;
65
83
  },
66
84
  copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
67
- delete: () => VOID,
85
+ delete: (s, _n, e, at) => {
86
+ const mt = s._deepUnderlying(at[0]);
87
+ if (mt?.kind === "map" && e.args[1]) s._markIfaceKey(mt, e.args[1], at[1]);
88
+ return VOID;
89
+ },
68
90
  make: (s, _n, e, _at, sc) => {
69
91
  if (e.args.length < 1) return ANY;
70
92
  const ta = e.args[0];
@@ -73,16 +95,17 @@ const BUILTIN_CHECK = {
73
95
  },
74
96
  new: (s, _n, e, _at, sc) => {
75
97
  if (e.args.length < 1) return ANY;
76
- const inner = s.resolveTypeNode(e.args[0], sc);
98
+ const ta = e.args[0];
99
+ const inner = s.resolveTypeNode(ta.kind === "TypeExpr" ? ta.type : ta, sc);
77
100
  return { kind: "pointer", base: inner };
78
101
  },
79
- print: () => VOID,
80
- println: () => VOID,
102
+ print: (s, n, e, at) => s._checkPrintArgs(n, e, at),
103
+ println: (s, n, e, at) => s._checkPrintArgs(n, e, at),
81
104
  panic: () => VOID,
82
105
  recover: () => ANY,
83
106
  error: () => ERROR,
84
- min: (_s, _n, _e, at) => at[0] ?? ANY,
85
- max: (_s, _n, _e, at) => at[0] ?? ANY,
107
+ min: (_s, _n, _e, at) => minMaxType(at),
108
+ max: (_s, _n, _e, at) => minMaxType(at),
86
109
  clear: () => VOID,
87
110
  complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
88
111
  real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
@@ -109,6 +132,7 @@ export const expressionCheckMethods = {
109
132
  checkExpr(expr, scope) {
110
133
  const t = this._checkExpr(expr, scope);
111
134
  expr._type = t;
135
+ this._noteConst(expr, scope);
112
136
  return t;
113
137
  },
114
138
 
@@ -134,11 +158,28 @@ export const expressionCheckMethods = {
134
158
  case "Ident": {
135
159
  const t = scope.lookup(expr.name);
136
160
  if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
137
- if (this.types.has(expr.name)) expr._isTypeRef = true;
161
+ if (this.types.has(expr.name) || t.kind === "typeParam")
162
+ expr._isTypeRef = true;
163
+ // A Go declaration that shadows a browser global: JS output renames
164
+ // it so generated code keeps reaching the real global. Package
165
+ // namespaces (`import console "./util"`) are never renamed: JS
166
+ // selects their members directly (bundledPackages).
167
+ if (BROWSER_GLOBALS.has(expr.name) && t.kind !== "namespace") {
168
+ const owner = scope.lookupScope(expr.name);
169
+ if (
170
+ owner !== this.universe &&
171
+ !(owner === this.globals && this._externalNames.has(expr.name))
172
+ )
173
+ expr._shadowsGlobal = true;
174
+ }
175
+ // Go-source stdlib loading: record package-scope references so the
176
+ // loader can mangle them (see compiler.js loadStdlibPackage).
177
+ if (this._pkgRefs && scope.lookupScope(expr.name) === this.globals)
178
+ this._pkgRefs.add(`${this._currentFile}:${expr._line}:${expr._col}`);
138
179
  if (
139
180
  (this.target === "wasm" || this.target === "both") &&
140
181
  BROWSER_GLOBALS.has(expr.name) &&
141
- scope.lookup(expr.name) === this.globals.lookup(expr.name)
182
+ scope.lookupScope(expr.name) === this.universe
142
183
  ) {
143
184
  this.recordBlocker("browser global", expr.name);
144
185
  this.err(
@@ -163,6 +204,7 @@ export const expressionCheckMethods = {
163
204
  if (lt?._tainted || rt?._tainted) return TAINTED_ANY;
164
205
  if (isAny(lt) || isAny(rt))
165
206
  return this.binaryResultType(expr.op, ANY, ANY, expr);
207
+ this._checkConstOperands(expr.op, lt, rt, expr);
166
208
  return this.binaryResultType(expr.op, lt, rt, expr);
167
209
  }
168
210
  case "AwaitExpr": {
@@ -177,6 +219,11 @@ export const expressionCheckMethods = {
177
219
  }
178
220
  case "InstantiationExpr":
179
221
  return this._checkInstantiationExpr(expr, scope);
222
+ case "TypeName":
223
+ // A type in expression position: monomorphisation rewrites
224
+ // `Box[int]` in `(*Box[int]).Set` to the TypeName `Box$int`.
225
+ expr._isTypeRef = true;
226
+ return this._typeRefOperand(expr, this.resolveTypeNode(expr, scope));
180
227
  default: {
181
228
  const m = CHECK_EXPR_DELEGATE[expr.kind];
182
229
  if (m) return this[m](expr, scope);
@@ -186,6 +233,13 @@ export const expressionCheckMethods = {
186
233
  return ANY;
187
234
  },
188
235
 
236
+ // A type ref (`*T`, `Box[int]`) is a value only as the base of a method
237
+ // expression (`(*T).M`), a conversion callee or the operand of `*`.
238
+ _typeRefOperand(expr, t) {
239
+ if (expr._typeRefOk) return t;
240
+ return this.err(`${typeStr(t)} (type) is not an expression`, expr);
241
+ },
242
+
189
243
  _checkDeref(ot, expr) {
190
244
  if (ot === ANY) return ANY;
191
245
  if (ot.kind === "pointer") return ot.base;
@@ -201,12 +255,200 @@ export const expressionCheckMethods = {
201
255
  },
202
256
 
203
257
  _checkNumericUnaryOp(ot, expr) {
258
+ if (ot?.kind === "typeParam") {
259
+ if (!this._typeParamAllows(ot, isComplexOrNumeric))
260
+ this.err(`${expr.op} requires numeric`, expr);
261
+ return ot;
262
+ }
204
263
  if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
205
264
  this.err(`${expr.op} requires numeric`, expr);
206
265
  return ot;
207
266
  },
208
267
 
268
+ // `^x` on a type parameter needs every type in its constraint to be an
269
+ // integer. Other operands keep the existing (unchecked) behaviour.
270
+ _checkComplementUnaryOp(ot, expr) {
271
+ if (ot?.kind === "typeParam" && !this._typeParamAllows(ot, isInteger))
272
+ this.err("^ requires integer", expr);
273
+ return ot;
274
+ },
275
+
276
+ // Whether every type in the type set of type parameter `tp`'s constraint
277
+ // satisfies `pred`. A constraint without a type set (any, comparable,
278
+ // method-only interfaces) allows nothing.
279
+ _typeParamAllows(tp, pred) {
280
+ const terms = this._constraintTerms(tp.constraint, new Set());
281
+ return terms !== null && terms.length > 0 && terms.every(pred);
282
+ },
283
+
284
+ _constraintTerms(c, seen) {
285
+ if (!c || seen.has(c)) return null;
286
+ seen.add(c);
287
+ // Receiver type parameters keep their constraint as an AST node.
288
+ if (c.kind === "TypeName" || c.kind === "InterfaceType")
289
+ return this._constraintTerms(this.resolveTypeNode(c, this.globals), seen);
290
+ if (c.kind === "named") return this._constraintTerms(c.underlying, seen);
291
+ if (c.kind === "interface") return this._interfaceTerms(c, seen);
292
+ if (c.kind === "typeParam")
293
+ return this._constraintTerms(c.constraint, seen);
294
+ if (c.kind !== "UnionConstraint") return isAny(c) ? null : [c];
295
+ const out = [];
296
+ for (const term of c.terms) {
297
+ const t = this.resolveTypeNode(term.type, this.globals);
298
+ const inner =
299
+ t?.kind === "named" && t.underlying?.kind === "interface"
300
+ ? this._constraintTerms(t, seen)
301
+ : [t];
302
+ if (inner === null) return null;
303
+ out.push(...inner);
304
+ }
305
+ return out;
306
+ },
307
+
308
+ // An interface's type set is the intersection of its own union and the
309
+ // type sets of the interfaces it embeds; null when it has none.
310
+ _interfaceTerms(iface, seen) {
311
+ const sets = [iface.unionConstraint, ...(iface._typeSetEmbeds ?? [])]
312
+ .filter(Boolean)
313
+ .map((s) => this._constraintTerms(s, new Set(seen)));
314
+ if (sets.length === 0 || sets.includes(null)) return null;
315
+ const [first, ...rest] = sets;
316
+ return first.filter((t) =>
317
+ rest.every((set) => set.some((u) => typeStr(u) === typeStr(t))),
318
+ );
319
+ },
320
+
321
+ // Only structs and arrays are their own managed objects in wasm; every other
322
+ // value (numbers, strings, slices, maps, interfaces, pointers, funcs, SIMD
323
+ // vectors) lives inline in its parent struct, array or global, so wasm has
324
+ // no stable address for it. Type parameters are checked per instantiation.
325
+ _isScalarStorageType(t) {
326
+ const u = this.resolveType(t?.kind === "named" ? t.underlying : t) ?? t;
327
+ const k = u?.kind;
328
+ if (!k || u._tainted) return false;
329
+ return k !== "struct" && k !== "array" && k !== "typeParam";
330
+ },
331
+
332
+ // True when `operand` is a struct field, array/slice element or package-level
333
+ // variable holding a scalar, in a package compiled to wasm.
334
+ _isScalarInteriorOperand(operand, type, scope) {
335
+ if (!this._isWasmPackage()) return false;
336
+ let node = operand;
337
+ while (node?.kind === "ParenExpr") node = node.expr;
338
+ if (!node) return false;
339
+ if (node.kind === "Ident") {
340
+ if (scope.lookupScope(node.name) !== this.globals) return false;
341
+ if (scope.isConst?.(node.name)) return false;
342
+ const sym = this.globals.symbols.get(node.name);
343
+ if (sym?.kind === "func" || sym?.kind === "namespace") return false;
344
+ } else if (node.kind !== "SelectorExpr" && node.kind !== "IndexExpr") {
345
+ return false;
346
+ }
347
+ if (node.kind === "SelectorExpr" && node.expr?._isTypeRef) return false;
348
+ if (node.kind === "SelectorExpr" && node.expr?._type?.kind === "namespace")
349
+ return false;
350
+ return this._isScalarStorageType(type);
351
+ },
352
+
353
+ // Postfix selectors and index expressions carry no position; report at
354
+ // their innermost positioned operand.
355
+ _positioned(node) {
356
+ let at = node;
357
+ while (at && !at._line && !at.line) at = at.operand ?? at.expr;
358
+ return at ?? node;
359
+ },
360
+
361
+ _errScalarInteriorAddress(node, type) {
362
+ return this.err(
363
+ `cannot take the address of a field, element or package-level variable of type ${typeStr(type)} in a ${this.target} package; only struct and array values have a stable address in WebAssembly. Pass the parent struct pointer or the index instead, or copy the value to a local variable`,
364
+ this._positioned(node),
365
+ );
366
+ },
367
+
368
+ // Go: map elements are not addressable, nor are fields and array elements
369
+ // reached from one by value (`&m[k]`, `&m[k].f`, `&m[k][i]` on an array).
370
+ _isMapElementOperand(operand) {
371
+ let node = operand;
372
+ while (node?.kind === "ParenExpr") node = node.expr;
373
+ if (node?.kind !== "SelectorExpr" && node?.kind !== "IndexExpr")
374
+ return false;
375
+ const bt = node.expr?._type;
376
+ const b = this.resolveType(bt?.kind === "named" ? bt.underlying : bt) ?? bt;
377
+ if (node.kind === "IndexExpr" && b?.kind === "map") return true;
378
+ if (b?.kind === "pointer" || b?.kind === "slice") return false;
379
+ if (node.kind === "SelectorExpr" && b?.kind !== "struct") return false;
380
+ if (
381
+ node.kind === "SelectorExpr" &&
382
+ this._promotedThroughPointer(bt, node.field)
383
+ )
384
+ return false;
385
+ return this._isMapElementOperand(node.expr);
386
+ },
387
+
388
+ // Whether field or method `name` of struct type `t` is promoted through an
389
+ // embedded pointer, so selecting it dereferences and is addressable.
390
+ _promotedThroughPointer(t, name) {
391
+ return this._selectorPath(t, name, 0) === "pointer";
392
+ },
393
+
394
+ // "pointer" when `name` is reached through an embedded pointer, "value"
395
+ // when it is found without one, null when `t` has no such member.
396
+ _selectorPath(t, name, depth) {
397
+ if (!t || depth > 8) return null;
398
+ if (t.kind === "named" && t.methods?.has(name)) return "value";
399
+ const st = this.resolveType(t.kind === "named" ? t.underlying : t) ?? t;
400
+ if (st?.kind !== "struct") return null;
401
+ for (const f of st._structFields ?? [])
402
+ if (!f.embedded && f.names?.includes(name)) return "value";
403
+ for (const e of st._embeds ?? []) {
404
+ const found = this._selectorPath(
405
+ e.kind === "pointer" ? e.base : e,
406
+ name,
407
+ depth + 1,
408
+ );
409
+ if (found) return e.kind === "pointer" ? "pointer" : found;
410
+ }
411
+ return null;
412
+ },
413
+
414
+ _errMapElementAddress(node) {
415
+ return this.err(
416
+ "cannot take the address of a map element; copy it to a local variable, or store pointers in the map",
417
+ this._positioned(node),
418
+ );
419
+ },
420
+
421
+ _isWasmPackage() {
422
+ return this.target === "wasm" || this.target === "both";
423
+ },
424
+
425
+ _isFuncType(t) {
426
+ const u = this.resolveType(t?.kind === "named" ? t.underlying : t) ?? t;
427
+ return u?.kind === "func";
428
+ },
429
+
430
+ // The WASM backend represents a func value by its closure reference and has
431
+ // no box for it, so a pointer to a func variable would lose writes.
432
+ _errFuncAddress(node, type) {
433
+ return this.err(
434
+ `cannot take the address of a variable of func type ${typeStr(type)} in a ${this.target} package; return the new func, or pass a pointer to a struct that holds it`,
435
+ this._positioned(node),
436
+ );
437
+ },
438
+
209
439
  _checkAddressOfExpr(expr, scope, ot) {
440
+ if (this._isMapElementOperand(expr.operand)) {
441
+ this._errMapElementAddress(expr);
442
+ return { kind: "pointer", base: ot };
443
+ }
444
+ if (this._isScalarInteriorOperand(expr.operand, ot, scope)) {
445
+ this._errScalarInteriorAddress(expr, ot);
446
+ return { kind: "pointer", base: ot };
447
+ }
448
+ if (this._isWasmPackage() && this._isFuncType(ot)) {
449
+ this._errFuncAddress(expr, ot);
450
+ return { kind: "pointer", base: ot };
451
+ }
210
452
  if (expr.operand.kind === "Ident") {
211
453
  expr.operand._addressTaken = true;
212
454
  const sym = scope.lookup(expr.operand.name);
@@ -215,13 +457,33 @@ export const expressionCheckMethods = {
215
457
  return { kind: "pointer", base: ot };
216
458
  },
217
459
 
460
+ // Go constants have no negative zero: `-0.0` is plain 0, so it is rewritten
461
+ // to a unary plus that both backends compile to +0.
462
+ _foldNegativeZeroLiteral(expr) {
463
+ const o = expr.operand;
464
+ if (expr.op !== "-" || o?.kind !== "BasicLit") return;
465
+ if (o.litKind !== "INT" && o.litKind !== "FLOAT") return;
466
+ if (Number(String(o.value).replaceAll("_", "")) === 0) expr.op = "+";
467
+ },
468
+
218
469
  _checkUnaryExpr(expr, scope) {
470
+ this._foldNegativeZeroLiteral(expr);
471
+ if (expr.op === "*") expr.operand._typeRefOk = true;
219
472
  const ot = this.checkExpr(expr.operand, scope);
220
473
  if (expr.op === "!") return this._checkBoolUnaryOp(ot, expr);
221
474
  if (expr.op === "-" || expr.op === "+")
222
475
  return this._checkNumericUnaryOp(ot, expr);
476
+ if (expr.op === "^") return this._checkComplementUnaryOp(ot, expr);
223
477
  if (expr.op === "&") return this._checkAddressOfExpr(expr, scope, ot);
224
- if (expr.op === "*") return this._checkDeref(ot, expr);
478
+ if (expr.op === "*") {
479
+ // `(*T).M`: the operand names a type, so `*T` is the pointer type
480
+ // of a method expression, not a dereference.
481
+ if (expr.operand._isTypeRef) {
482
+ expr._isTypeRef = true;
483
+ return this._typeRefOperand(expr, { kind: "pointer", base: ot });
484
+ }
485
+ return this._checkDeref(ot, expr);
486
+ }
225
487
  return ot;
226
488
  },
227
489
 
@@ -238,51 +500,147 @@ export const expressionCheckMethods = {
238
500
  return base?.methods?.get(field) ?? baseType.methods?.get(field);
239
501
  },
240
502
 
503
+ // Instantiated generic methods are substituted copies without `_ptrRecv`,
504
+ // so read it from the generic declaration's method.
505
+ _methodExprPtrRecv(recvBase, methodType, field) {
506
+ if (!methodType) return false;
507
+ if (Object.hasOwn(methodType, "_ptrRecv")) return methodType._ptrRecv;
508
+ const generic = recvBase.typeArgs
509
+ ? this.types.get(recvBase.name)?._generic
510
+ : null;
511
+ return Boolean(generic?.methods?.get(field)?._ptrRecv);
512
+ },
513
+
514
+ // `x.M` or `x.M()` with a pointer receiver on a named scalar or slice local takes
515
+ // `&x`, so `x` must be boxed like an explicit `&x`.
516
+ _markPtrRecvAddress(expr, base, ft, scope) {
517
+ if (!ft._ptrRecv) return;
518
+ if (
519
+ !this._promotedThroughPointer(expr.expr._type, expr.field) &&
520
+ this._isMapElementOperand(expr.expr)
521
+ ) {
522
+ this._errMapElementAddress(expr.expr);
523
+ return;
524
+ }
525
+ const k = base?.kind;
526
+ if (k !== "basic" && k !== "slice" && k !== "map" && k !== "func") return;
527
+ if (this._isScalarInteriorOperand(expr.expr, expr.expr._type, scope)) {
528
+ this._errScalarInteriorAddress(expr, expr.expr._type);
529
+ return;
530
+ }
531
+ if (k === "func" && this._isWasmPackage()) {
532
+ this._errFuncAddress(expr, expr.expr._type);
533
+ return;
534
+ }
535
+ if (expr.expr.kind !== "Ident") return;
536
+ expr.expr._addressTaken = true;
537
+ const sym = scope.lookup(expr.expr.name);
538
+ if (sym) sym._addressTaken = true;
539
+ },
540
+
541
+ // Resolved per file from the import path, so a local package sharing an
542
+ // identifier with a stdlib one (or a stdlib import in another file) never matches.
543
+ _checkExcludedNamespaceMember(expr, baseType) {
544
+ const pkgPath = this._fileImports
545
+ ?.get(this._currentFile)
546
+ ?.get(baseType.name);
547
+ if (!pkgPath || pkgPath.startsWith(".")) return null;
548
+ const key = `${pkgPath}.${expr.field}`;
549
+ if (!EXCLUDED_STDLIB_MEMBERS.has(key)) return null;
550
+ return this.err(
551
+ `${baseType.name}.${expr.field} is not available: ${EXCLUDED_STDLIB_MEMBERS.get(key)}`,
552
+ expr,
553
+ );
554
+ },
555
+
556
+ _checkExcludedSelectorExpr(expr, baseType) {
557
+ if (baseType?.kind === "namespace") {
558
+ return this._checkExcludedNamespaceMember(expr, baseType);
559
+ }
560
+ const recvCheck =
561
+ baseType?.kind === "pointer"
562
+ ? (this.resolveType(baseType.base) ?? baseType.base)
563
+ : baseType;
564
+ const recvName = recvCheck?.name;
565
+ // Stdlib named types are qualified (`pkg.T` or mangled `T$$pkg`); a
566
+ // bare name is a user type and never matches the exclusion table.
567
+ if (!recvName || !/[.$]/.test(recvName)) return null;
568
+ const dName = displayTypeName(recvName);
569
+ const reason =
570
+ EXCLUDED_STDLIB_MEMBERS.get(`${dName}.${expr.field}`) ??
571
+ EXCLUDED_STDLIB_MEMBERS.get(`${recvName}.${expr.field}`);
572
+ if (!reason) return null;
573
+ return this.err(`${dName}.${expr.field} is not available: ${reason}`, expr);
574
+ },
575
+
576
+ _checkMethodExprSelector(expr, baseType) {
577
+ const recvBase = baseType.kind === "pointer" ? baseType.base : baseType;
578
+ const base = this.resolveType(
579
+ recvBase.kind === "named" ? recvBase.underlying : recvBase,
580
+ );
581
+ const methodType = this._resolveMethodExprType(recvBase, base, expr.field);
582
+ if (
583
+ this._methodExprPtrRecv(recvBase, methodType, expr.field) &&
584
+ baseType.kind !== "pointer"
585
+ ) {
586
+ const t = typeStr(baseType);
587
+ return this.err(
588
+ `invalid method expression ${t}.${expr.field} (needs pointer receiver (*${t}).${expr.field})`,
589
+ expr,
590
+ );
591
+ }
592
+ if (!methodType) return null;
593
+ expr._isMethodExpr = true;
594
+ expr._methodExprRecv = baseType;
595
+ return {
596
+ kind: "func",
597
+ params: [baseType, ...(methodType.params ?? [])],
598
+ returns: methodType.returns ?? [],
599
+ variadic: methodType.variadic,
600
+ };
601
+ },
602
+
603
+ _markMethodValueSelector(expr, baseType, ft, scope) {
604
+ const recvT =
605
+ baseType.kind === "pointer"
606
+ ? (this.resolveType(baseType.base) ?? baseType.base)
607
+ : baseType;
608
+ const base = this.resolveType(
609
+ recvT?.kind === "named" ? recvT.underlying : recvT,
610
+ );
611
+ if (!recvT || !this._isStructOrNamedMethod(recvT, base, expr.field)) return;
612
+ expr._isMethodValue = true;
613
+ if (baseType.kind === "pointer")
614
+ expr._derefRecv = !this._methodExprPtrRecv(recvT, ft, expr.field);
615
+ else this._markPtrRecvAddress(expr, base, ft, scope);
616
+ },
617
+
241
618
  _checkSelectorExpr(expr, scope) {
619
+ expr.expr._typeRefOk = true;
242
620
  const baseType = this.checkExpr(expr.expr, scope);
243
621
  if (baseType?._tainted) return TAINTED_ANY;
622
+ const exclErr = this._checkExcludedSelectorExpr(expr, baseType);
623
+ if (exclErr) return exclErr;
244
624
  if (expr.expr._isTypeRef) {
245
- let base = baseType.kind === "named" ? baseType.underlying : baseType;
246
- base = this.resolveType(base);
247
- const methodType = this._resolveMethodExprType(
248
- baseType,
249
- base,
250
- expr.field,
251
- );
252
- if (methodType) {
253
- expr._isMethodExpr = true;
254
- expr._methodExprRecv = baseType;
255
- return {
256
- kind: "func",
257
- params: [baseType, ...(methodType.params ?? [])],
258
- returns: methodType.returns ?? [],
259
- variadic: methodType.variadic,
260
- };
261
- }
625
+ const mExpr = this._checkMethodExprSelector(expr, baseType);
626
+ if (mExpr) return mExpr;
262
627
  }
263
628
  const ft = this.fieldType(baseType, expr.field, expr);
264
- if (ft?.kind === "func") {
265
- let base = baseType.kind === "named" ? baseType.underlying : baseType;
266
- base = this.resolveType(base);
267
- if (this._isStructOrNamedMethod(baseType, base, expr.field))
268
- expr._isMethodValue = true;
269
- }
629
+ if (ft?.kind === "func")
630
+ this._markMethodValueSelector(expr, baseType, ft, scope);
270
631
  if (
271
632
  baseType?.kind === "namespace" &&
272
633
  this.types.has(`${baseType.name}.${expr.field}`) &&
273
- !(
274
- baseType.name === "binary" &&
275
- (expr.field === "LittleEndian" || expr.field === "BigEndian")
276
- )
634
+ baseType.name !== "binary"
277
635
  ) {
278
636
  expr._isTypeRef = true;
279
637
  }
280
638
  return ft;
281
639
  },
282
640
 
283
- _checkArrayBoundsExpr(btu, bt, expr) {
641
+ _checkArrayBoundsExpr(btu, bt, expr, scope) {
284
642
  if (btu.kind !== "array" || btu.size == null) return;
285
- const idx = this._constIntValue(expr.index);
643
+ const idx = this._constIntValue(expr.index, scope);
286
644
  if (idx === null) return;
287
645
  if (idx < 0)
288
646
  this.err(`invalid array index ${idx} (index must not be negative)`, expr);
@@ -293,26 +651,97 @@ export const expressionCheckMethods = {
293
651
  );
294
652
  },
295
653
 
654
+ // `Box[int]` in an expression (the receiver of a method expression such
655
+ // as `Box[int].Get` or `(*Box[int]).Set`) names the instantiated type.
656
+ // The node becomes an InstantiationExpr so monomorphisation rewrites it.
657
+ _genericTypeRef(expr, scope) {
658
+ const base = expr.expr;
659
+ let t;
660
+ if (base?.kind === "SelectorExpr" && base.expr?.kind === "Ident") {
661
+ // `iter.Seq[int]`: a generic type behind a package qualifier.
662
+ if (scope.lookup(base.expr.name)?.kind !== "namespace") return null;
663
+ t = this.types.get(`${base.expr.name}.${base.field}`);
664
+ if (!t?._generic || t._generic.isAlias) return null;
665
+ } else {
666
+ if (base?.kind !== "Ident") return null;
667
+ t = this.types.get(base.name);
668
+ if (!t?._generic || t._generic.isAlias || scope.lookup(base.name) !== t)
669
+ return null;
670
+ }
671
+ if (expr.kind === "IndexExpr") {
672
+ if (expr.index?.kind !== "Ident") return null;
673
+ expr.kind = "InstantiationExpr";
674
+ expr.typeArgs = [{ kind: "TypeName", name: expr.index.name }];
675
+ delete expr.index;
676
+ }
677
+ const resolved = this.resolveTypeNode(expr, scope);
678
+ expr._isTypeRef = true;
679
+ return this._typeRefOperand(expr, resolved);
680
+ },
681
+
682
+ // Underlying type with chains of defined types followed through
683
+ // (`type W V` where `V` is itself a named array).
684
+ _deepUnderlying(t) {
685
+ let u = t;
686
+ for (let i = 0; u?.kind === "named" && i < 32; i++)
687
+ u = this.resolveType(u.underlying) ?? u.underlying;
688
+ return u;
689
+ },
690
+
691
+ // A key of an interface-keyed map (`map[any]T`): the backend encodes it
692
+ // with its dynamic type, so `L("1")`, `N(1)`, `"1"` and `1` stay distinct.
693
+ // A named basic value is boxed first, exactly as in any interface
694
+ // conversion; other values are keyed as they are.
695
+ _markIfaceKey(mapType, node, srcType) {
696
+ const key = this.resolveType(mapType.key);
697
+ const kb = key?.kind === "named" ? this.resolveType(key.underlying) : key;
698
+ const src = srcType && this.resolveType(srcType);
699
+ if (node && src?.kind === "typeParam") {
700
+ // Generic code: whether the key is boxed depends on the type argument,
701
+ // so the backend decides at run time (`__ikt`).
702
+ if (key?.kind === "typeParam" || isAny(key) || kb?.kind === "interface") {
703
+ node._ifaceKeyDyn = key?.kind === "typeParam" ? "same" : "box";
704
+ node._ifaceKeyParam = src.name;
705
+ }
706
+ return;
707
+ }
708
+ if (!node || !srcType || (!isAny(key) && kb?.kind !== "interface")) return;
709
+ node._ifaceKey = true;
710
+ this._markIfaceBox(key, this.resolveType(srcType), node);
711
+ if (node._ifaceBox === "named" || node._ifaceBox === "named_ptr") return;
712
+ node._ifaceBox = undefined;
713
+ node._ifaceSrc = undefined;
714
+ node._ifacePtrFields = undefined;
715
+ },
716
+
296
717
  _checkIndexExpr(expr, scope) {
297
- const bt = this.checkExpr(expr.expr, scope);
298
- this.checkExpr(expr.index, scope);
718
+ const typeRef = this._genericTypeRef(expr, scope);
719
+ if (typeRef) return typeRef;
720
+ const bt = this._derefArrayPtr(
721
+ expr,
722
+ "expr",
723
+ this.checkExpr(expr.expr, scope),
724
+ );
725
+ if (bt?.kind === "generic") {
726
+ const inst = this._genericFuncValueAsInstantiation(expr);
727
+ if (inst) return this._checkInstantiationExpr(inst, scope);
728
+ }
729
+ const indexType = this.checkExpr(expr.index, scope);
299
730
  if (bt?._tainted) return TAINTED_ANY;
300
731
  if (isAny(bt)) return ANY;
301
- const btu =
302
- bt.kind === "named"
303
- ? (this.resolveType(bt.underlying) ?? bt.underlying)
304
- : bt;
732
+ const btu = this._deepUnderlying(bt);
305
733
  if (btu.kind === "slice" || btu.kind === "array") {
306
- this._checkArrayBoundsExpr(btu, bt, expr);
734
+ this._checkArrayBoundsExpr(btu, bt, expr, scope);
307
735
  return btu.elem;
308
736
  }
309
737
  if (btu.kind === "map") {
310
738
  expr._mapValueType = btu.value;
739
+ this._markIfaceKey(btu, expr.index, indexType);
311
740
  return btu.value;
312
741
  }
313
742
  if (btu.kind === "shared") {
314
743
  expr._sharedElem = btu.elem;
315
- const idx = this._constIntValue(expr.index);
744
+ const idx = this._constIntValue(expr.index, scope);
316
745
  if (idx !== null && idx < 0)
317
746
  this.err(`invalid index ${idx} (index must not be negative)`, expr);
318
747
  return btu.elem;
@@ -321,6 +750,30 @@ export const expressionCheckMethods = {
321
750
  return this.err(`Cannot index type ${typeStr(bt)}`, expr);
322
751
  },
323
752
 
753
+ // `Id[int]` used as a value reaches the typechecker as an IndexExpr (the
754
+ // parser only commits to type arguments before a call or literal). The
755
+ // node becomes an InstantiationExpr so both backends specialise it.
756
+ _genericFuncValueAsInstantiation(expr) {
757
+ const toTypeNode = (n) => {
758
+ if (n?.kind === "TypeExpr") return n.type;
759
+ const parts = [];
760
+ let cur = n;
761
+ while (cur?.kind === "SelectorExpr") {
762
+ parts.unshift(cur.field);
763
+ cur = cur.expr;
764
+ }
765
+ if (cur?.kind !== "Ident") return null;
766
+ parts.unshift(cur.name);
767
+ return { kind: "TypeName", name: parts.join("."), _line: n._line };
768
+ };
769
+ const typeArg = toTypeNode(expr.index);
770
+ if (!typeArg) return null;
771
+ expr.kind = "InstantiationExpr";
772
+ expr.typeArgs = [typeArg];
773
+ delete expr.index;
774
+ return expr;
775
+ },
776
+
324
777
  // copy(dst, src): element types must be identical when either side is a
325
778
  // shared buffer (the wasm emitter uses one element type for both sides).
326
779
  _checkBuiltinCopy(e, at) {
@@ -346,15 +799,33 @@ export const expressionCheckMethods = {
346
799
  return INT;
347
800
  },
348
801
 
802
+ // Go's shorthand for pointers to arrays: `p[i]`, `p[lo:hi]`, `len(p)` and
803
+ // `range p` mean `(*p)[i]` and so on. The operand is rewritten to an
804
+ // explicit dereference so every backend sees the array.
805
+ _derefArrayPtr(holder, key, t) {
806
+ if (t?.kind !== "pointer") return t;
807
+ const base = t.base?.kind === "named" ? this.resolveType(t.base) : t.base;
808
+ const under = base?.kind === "named" ? base.underlying : base;
809
+ if (under?.kind !== "array") return t;
810
+ holder[key] = { kind: "UnaryExpr", op: "*", operand: holder[key] };
811
+ holder[key]._type = t.base;
812
+ return t.base;
813
+ },
814
+
349
815
  _checkSliceExpr(expr, scope) {
350
- const bt = this.checkExpr(expr.expr, scope);
816
+ const bt = this._derefArrayPtr(
817
+ expr,
818
+ "expr",
819
+ this.checkExpr(expr.expr, scope),
820
+ );
351
821
  if (expr.low) this.checkExpr(expr.low, scope);
352
822
  if (expr.high) this.checkExpr(expr.high, scope);
353
823
  if (expr.max) this.checkExpr(expr.max, scope);
354
824
  if (bt?._tainted) return TAINTED_ANY;
355
825
  if (isAny(bt)) return ANY;
356
- if (bt.kind === "slice" || bt.kind === "array")
357
- return { kind: "slice", elem: bt.elem };
826
+ const btu = this._deepUnderlying(bt);
827
+ if (btu.kind === "slice" || btu.kind === "array")
828
+ return { kind: "slice", elem: btu.elem };
358
829
  if (isString(bt)) return STRING;
359
830
  if (sharedInfo(bt))
360
831
  return this.err(
@@ -387,7 +858,7 @@ export const expressionCheckMethods = {
387
858
  }
388
859
  const inner = new Scope(scope);
389
860
  for (const p of expr.params)
390
- inner.define(p.name, p.type ? this.resolveTypeNode(p.type, scope) : ANY);
861
+ inner.define(p.name, p.type ? this._paramType(p, scope) : ANY);
391
862
  const ret = expr.returnType
392
863
  ? this.resolveTypeNode(expr.returnType, scope)
393
864
  : VOID;
@@ -404,6 +875,7 @@ export const expressionCheckMethods = {
404
875
  kind: "func",
405
876
  params: paramTypes,
406
877
  returns: [ret],
878
+ variadic: expr.params.at(-1)?.variadic === true,
407
879
  async: expr.async,
408
880
  };
409
881
  },
@@ -429,6 +901,19 @@ export const expressionCheckMethods = {
429
901
  );
430
902
  },
431
903
 
904
+ // Struct types with identical field names and types convert to each other.
905
+ _sameStructFields(a, b) {
906
+ if (a?.kind !== "struct" || b?.kind !== "struct") return false;
907
+ if (a.fields.size !== b.fields.size) return false;
908
+ const bf = [...b.fields];
909
+ let i = 0;
910
+ for (const [name, type] of a.fields) {
911
+ const [bn, bt] = bf[i++];
912
+ if (name !== bn || typeStr(type) !== typeStr(bt)) return false;
913
+ }
914
+ return true;
915
+ },
916
+
432
917
  _isInvalidArrayConv(tgt, src, srcType) {
433
918
  return tgt?.kind === "array" && src?.kind !== "array" && !isAny(srcType);
434
919
  },
@@ -436,6 +921,8 @@ export const expressionCheckMethods = {
436
921
  _checkTypeConversion(expr, scope) {
437
922
  const srcType = this.checkExpr(expr.expr, scope);
438
923
  const targetType = this.resolveTypeNode(expr.targetType, scope);
924
+ // `any(x)` / `error(x)`: box x as an implicit conversion would.
925
+ this._markIfaceBox(targetType, srcType, expr.expr);
439
926
  const srcResolved =
440
927
  srcType?.kind === "named" ? srcType.underlying : srcType;
441
928
  const tgtResolved =
@@ -456,6 +943,8 @@ export const expressionCheckMethods = {
456
943
  expr,
457
944
  );
458
945
  }
946
+ if (tgtResolved?.kind === "slice")
947
+ this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
459
948
  if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
460
949
  this._checkElemTypeCompatible(
461
950
  tgtResolved.elem,
@@ -495,6 +984,8 @@ export const expressionCheckMethods = {
495
984
  );
496
985
  return;
497
986
  }
987
+ if (tgtResolved?.kind === "slice")
988
+ this._rejectSeqAsSlice(srcType, expr.args?.[0], "a slice conversion");
498
989
  if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
499
990
  this._checkElemTypeCompatible(
500
991
  tgtResolved.elem,
@@ -513,9 +1004,15 @@ export const expressionCheckMethods = {
513
1004
  return;
514
1005
  }
515
1006
  if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
1007
+ if (this._sameStructFields(srcResolved, tgtResolved)) return;
516
1008
  if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
517
1009
  if (isString(srcResolved) && isString(tgtResolved)) return;
518
1010
  if (isBool(srcResolved) && isBool(tgtResolved)) return;
1011
+ if (
1012
+ (isComplex(srcResolved) || isNumeric(srcResolved)) &&
1013
+ isComplex(tgtResolved)
1014
+ )
1015
+ return;
519
1016
  if (
520
1017
  isString(srcResolved) &&
521
1018
  tgtResolved?.kind === "slice" &&
@@ -589,19 +1086,31 @@ export const expressionCheckMethods = {
589
1086
  },
590
1087
 
591
1088
  _checkRangeExpr(expr, scope) {
592
- const collType = this.checkExpr(expr.expr, scope);
1089
+ const collType = this._derefArrayPtr(
1090
+ expr,
1091
+ "expr",
1092
+ this.checkExpr(expr.expr, scope),
1093
+ );
593
1094
  const resolved =
594
1095
  collType?.kind === "named" ? collType.underlying : collType;
595
1096
  return this._rangeCollTypeTuple(resolved, collType);
596
1097
  },
597
1098
 
598
1099
  _checkInstantiationExpr(expr, scope) {
1100
+ const typeRef = this._genericTypeRef(expr, scope);
1101
+ if (typeRef) return typeRef;
599
1102
  // Generic instantiation: Foo[int] or Foo[int, string]
600
1103
  const baseType = this.checkExpr(expr.expr, scope);
601
1104
  if (baseType?.kind === "generic") {
602
1105
  const typeArgs = expr.typeArgs.map((ta) =>
603
1106
  this.resolveTypeNode(ta, scope),
604
1107
  );
1108
+ if (typeArgs.length > baseType.typeParams.length) {
1109
+ return this.err(
1110
+ `got ${typeArgs.length} type arguments but ${baseType.name ?? "function"} has ${baseType.typeParams.length} type parameters`,
1111
+ expr,
1112
+ );
1113
+ }
605
1114
  // Check constraints
606
1115
  for (
607
1116
  let i = 0;
@@ -625,9 +1134,28 @@ export const expressionCheckMethods = {
625
1134
  // rewrite the node in place into an IndexExpr and check that instead.
626
1135
  const indexExpr = this._instantiationAsIndex(expr, baseType);
627
1136
  if (indexExpr) return this._checkIndexExpr(indexExpr, scope);
1137
+ if (baseType?.kind === "func" && !this._isPseudoGenericStdFunc(baseType))
1138
+ return this.err(
1139
+ `cannot instantiate ${this._calleeName(expr.expr)}: not a generic function`,
1140
+ expr,
1141
+ );
628
1142
  return baseType;
629
1143
  },
630
1144
 
1145
+ // Emitter-backed stdlib helpers (`slices.Sort`, `maps.Keys`) take `any`
1146
+ // parameters in place of type parameters and accept (and ignore) explicit
1147
+ // type arguments; a stdlib function with concrete parameters does not.
1148
+ _isPseudoGenericStdFunc(fnType) {
1149
+ return (fnType.params ?? []).some((p) => isAny(p));
1150
+ },
1151
+
1152
+ _calleeName(node) {
1153
+ if (node?.kind === "Ident") return node.name;
1154
+ if (node?.kind === "SelectorExpr")
1155
+ return `${this._calleeName(node.expr)}.${node.field}`;
1156
+ return "expression";
1157
+ },
1158
+
631
1159
  _instantiationAsIndex(expr, baseType) {
632
1160
  if (!baseType || expr.typeArgs.length !== 1) return null;
633
1161
  const ta = expr.typeArgs[0];
@@ -652,32 +1180,97 @@ export const expressionCheckMethods = {
652
1180
  return expr;
653
1181
  },
654
1182
 
655
- _constIntValue(expr, scope = null) {
656
- if (!expr) return null;
657
- if (expr.kind === "BasicLit" && expr.litKind === "INT")
658
- return Number(expr.value);
659
- if (
660
- expr.kind === "UnaryExpr" &&
661
- expr.op === "-" &&
662
- expr.operand?.kind === "BasicLit" &&
663
- expr.operand.litKind === "INT"
664
- ) {
665
- return -Number(expr.operand.value);
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);
1183
+ // `int(K)`, `uint8(K)`, `MyInt(K)`: a conversion to an integer type.
1184
+ _isIntConversion(expr) {
1185
+ if (expr.args?.length !== 1) return false;
1186
+ const target = expr._conversionTargetType;
1187
+ if (expr._isTypeConversion && target)
1188
+ return isInteger(target.kind === "named" ? target.underlying : target);
1189
+ return (
1190
+ expr.func?.kind === "Ident" && SIZED_INT_NAMES.includes(expr.func.name)
1191
+ );
1192
+ },
1193
+
1194
+ // Keyed array literal indices (Go spec): each key must be a non-negative
1195
+ // integer constant, and no index may appear twice. The resolved index is
1196
+ // stored on the KeyValueExpr as `_index` for compositeLitIndices.
1197
+ _checkArrayLitKeys(expr, scope) {
1198
+ const seen = new Set();
1199
+ let next = 0;
1200
+ let unknown = false;
1201
+ for (const elem of expr.elems) {
1202
+ if (elem.kind === "KeyValueExpr") {
1203
+ this.checkExpr(elem.key, scope);
1204
+ const idx = this._constIntValue(elem.key, scope);
1205
+ const text = constKeyText(elem.key);
1206
+ if (idx === null && this._isConstExpr(elem.key, scope)) {
1207
+ // A constant key the folder cannot evaluate (`len(arr)`) keeps
1208
+ // the literal/positional index of compositeLitIndices; later
1209
+ // indices are not duplicate-checked.
1210
+ unknown = true;
1211
+ next++;
1212
+ continue;
1213
+ }
1214
+ if (idx === null)
1215
+ this.err(`index ${text} must be integer constant`, elem.key);
1216
+ else if (idx < 0)
1217
+ this.err(
1218
+ `index ${text} must be non-negative integer constant`,
1219
+ elem.key,
1220
+ );
1221
+ else next = idx;
1222
+ elem._index = next;
1223
+ }
1224
+ if (!unknown && seen.has(next))
1225
+ this.err(`duplicate index ${next} in array or slice literal`, elem);
1226
+ seen.add(next);
1227
+ next++;
676
1228
  }
677
- if (expr.kind === "ParenExpr") {
678
- return this._constIntValue(expr.expr, scope);
1229
+ },
1230
+
1231
+ // Whether `e` is a constant expression (whose value the folder may not
1232
+ // know): literals, constants, operators, conversions and builtin calls.
1233
+ _isConstExpr(e, scope) {
1234
+ switch (e?.kind) {
1235
+ case "BasicLit":
1236
+ return true;
1237
+ case "ParenExpr":
1238
+ return this._isConstExpr(e.expr, scope);
1239
+ case "UnaryExpr":
1240
+ return (
1241
+ ["-", "+", "^", "!"].includes(e.op) &&
1242
+ this._isConstExpr(e.operand, scope)
1243
+ );
1244
+ case "BinaryExpr":
1245
+ return (
1246
+ this._isConstExpr(e.left, scope) && this._isConstExpr(e.right, scope)
1247
+ );
1248
+ case "Ident":
1249
+ return !!(scope ?? this.globals)
1250
+ .lookupScope(e.name)
1251
+ ?._consts.has(e.name);
1252
+ case "SelectorExpr": {
1253
+ // Package members: constant unless the package says otherwise.
1254
+ const ns =
1255
+ e.expr?.kind === "Ident"
1256
+ ? (scope ?? this.globals).lookup(e.expr.name)
1257
+ : null;
1258
+ if (ns?.kind !== "namespace") return false;
1259
+ return ns._consts ? ns._consts.has(e.field) : true;
1260
+ }
1261
+ case "TypeConversion":
1262
+ return this._isConstExpr(e.expr, scope);
1263
+ case "CallExpr":
1264
+ if (e._isTypeConversion || this._isIntConversion(e))
1265
+ return this._isConstExpr(e.args?.[0], scope);
1266
+ return (
1267
+ e.func?.kind === "Ident" &&
1268
+ CONST_BUILTINS.has(e.func.name) &&
1269
+ (scope ?? this.globals).lookup(e.func.name)?.kind === "builtin"
1270
+ );
1271
+ default:
1272
+ return false;
679
1273
  }
680
- return null;
681
1274
  },
682
1275
 
683
1276
  _resolveCallArgTypes(expr, scope) {
@@ -705,11 +1298,88 @@ export const expressionCheckMethods = {
705
1298
  if (!resolved) return null;
706
1299
  fnType = resolved;
707
1300
  }
708
- return fnType;
1301
+ return this._completePartialInstantiation(expr, argTypes) ?? fnType;
1302
+ },
1303
+
1304
+ // `F[int](x)` with fewer explicit type arguments than type parameters:
1305
+ // infer the rest from the call arguments and record the full list.
1306
+ _completePartialInstantiation(expr, argTypes) {
1307
+ const inst = expr.func;
1308
+ const generic = inst?.kind === "InstantiationExpr" && inst._genericFnType;
1309
+ if (!generic || inst._typeArgs.length >= generic.typeParams.length)
1310
+ return null;
1311
+ const typeArgs = this.inferTypeArgs(
1312
+ generic,
1313
+ this._inferArgTypes(expr, argTypes),
1314
+ inst._typeArgs,
1315
+ );
1316
+ if (!typeArgs) {
1317
+ this.err(
1318
+ `Cannot infer type arguments for generic function '${generic.name}'`,
1319
+ expr,
1320
+ );
1321
+ return null;
1322
+ }
1323
+ inst._typeArgs = typeArgs;
1324
+ if (inst.expr) inst.expr._typeArgs = typeArgs;
1325
+ return this.instantiateGenericFunc(generic, typeArgs);
1326
+ },
1327
+
1328
+ // `T(x)` with T a type parameter: only a numeric x converts (the constraint
1329
+ // decides the target at instantiation).
1330
+ _checkTypeParamConversion(tp, argTypes, expr) {
1331
+ expr._isTypeConversion = true;
1332
+ expr._conversionTargetType = tp;
1333
+ if (expr.args.length !== 1) {
1334
+ return this.err(
1335
+ `cannot convert ${expr.args.length} arguments to type ${typeStr(tp)}`,
1336
+ expr,
1337
+ );
1338
+ }
1339
+ if (isNumeric(argTypes[0]) && !this._constraintHasTypeSet(tp.constraint))
1340
+ return this.err(
1341
+ `cannot convert ${typeStr(argTypes[0])} to type ${typeStr(tp)}: ${typeStr(tp)} is not constrained to a set of convertible types`,
1342
+ expr,
1343
+ );
1344
+ if (
1345
+ !isNumeric(argTypes[0]) &&
1346
+ !isAny(argTypes[0]) &&
1347
+ argTypes[0]?.kind !== "typeParam"
1348
+ )
1349
+ this.err(
1350
+ `cannot convert ${typeStr(argTypes[0])} to ${typeStr(tp)}`,
1351
+ expr,
1352
+ );
1353
+ return tp;
1354
+ },
1355
+
1356
+ // A conversion to a type parameter needs a constraint that lists types
1357
+ // (`~int | ~float64`, or an interface embedding such a union); `any`,
1358
+ // `comparable` and method-only constraints allow every type argument.
1359
+ _constraintHasTypeSet(constraint) {
1360
+ if (!constraint) return true;
1361
+ if (constraint.kind === "UnionConstraint") return true;
1362
+ const c = this.resolveType(constraint);
1363
+ const base = c?.kind === "named" ? this.resolveType(c.underlying) : c;
1364
+ if (base?.kind !== "interface") return false;
1365
+ if (base.unionConstraint) return true;
1366
+ return (base._typeSetEmbeds ?? []).some((e) =>
1367
+ this._constraintHasTypeSet(e),
1368
+ );
709
1369
  },
710
1370
 
711
1371
  checkCall(expr, scope) {
1372
+ expr.func._typeRefOk = true;
712
1373
  let fnType = this.checkExpr(expr.func, scope);
1374
+ // `slices.Sort[[]int](xs)` on an emitter-backed (non-generic) stdlib
1375
+ // function: the type arguments are inferred, so call it as `slices.Sort(xs)`.
1376
+ if (
1377
+ expr.func.kind === "InstantiationExpr" &&
1378
+ !expr.func._genericFnType &&
1379
+ expr.func.expr?.kind === "SelectorExpr" &&
1380
+ fnType?.kind === "func"
1381
+ )
1382
+ expr.func = expr.func.expr;
713
1383
  this._checkSharedAllocSite(expr, scope);
714
1384
  this._checkBoundaryCallInLoop(expr, scope);
715
1385
  if (fnType.kind === "builtin") {
@@ -724,6 +1394,9 @@ export const expressionCheckMethods = {
724
1394
  const argTypes = this._resolveCallArgTypes(expr, scope);
725
1395
 
726
1396
  if (fnType?._tainted) return TAINTED_ANY;
1397
+ // `any(x)` / `error(x)` / `Iface(x)`: box x as an implicit conversion would.
1398
+ if (expr.func._isTypeRef && expr.args.length === 1 && argTypes[0])
1399
+ this._markIfaceBox(fnType, argTypes[0], expr.args[0]);
727
1400
  if (isAny(fnType)) return ANY;
728
1401
 
729
1402
  // Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
@@ -742,6 +1415,10 @@ export const expressionCheckMethods = {
742
1415
  return fnType;
743
1416
  }
744
1417
 
1418
+ if (fnType.kind === "typeParam" && expr.func._isTypeRef) {
1419
+ return this._checkTypeParamConversion(fnType, argTypes, expr);
1420
+ }
1421
+
745
1422
  fnType = this._resolveCallFnType(fnType, argTypes, expr, scope);
746
1423
  if (!fnType) return ANY;
747
1424
  if (fnType.kind !== "func") {
@@ -814,19 +1491,26 @@ export const expressionCheckMethods = {
814
1491
  return under ? arg : null;
815
1492
  case "elemOfArg0":
816
1493
  return under?.elem ?? null;
817
- case "keysOfArg0":
818
- return under?.key ? { kind: "slice", elem: under.key } : null;
819
- case "valuesOfArg0": {
820
- const v = under?.value ?? under?.elem;
821
- return v ? { kind: "slice", elem: v } : null;
822
- }
823
1494
  default:
824
1495
  return null;
825
1496
  }
826
1497
  },
827
1498
 
1499
+ // Argument types for inference: a spread argument `xs...` stands for its
1500
+ // elements, which is what the variadic parameter type describes.
1501
+ _inferArgTypes(expr, argTypes) {
1502
+ return argTypes.map((t, i) => {
1503
+ if (!expr.args?.[i]?._spread) return t;
1504
+ const u = this.resolveType(t?.kind === "named" ? t.underlying : t);
1505
+ return u?.kind === "slice" ? u.elem : t;
1506
+ });
1507
+ },
1508
+
828
1509
  _resolveGenericFnType(fnType, argTypes, expr, scope) {
829
- const typeArgs = this.inferTypeArgs(fnType, argTypes);
1510
+ const typeArgs = this.inferTypeArgs(
1511
+ fnType,
1512
+ this._inferArgTypes(expr, argTypes),
1513
+ );
830
1514
  if (!typeArgs) {
831
1515
  this.err(
832
1516
  `Cannot infer type arguments for generic function '${fnType.name}'`,
@@ -865,8 +1549,28 @@ export const expressionCheckMethods = {
865
1549
  );
866
1550
  },
867
1551
 
1552
+ // WASM packs a variadic call's trailing arguments into a slice, which it
1553
+ // cannot do for `f(g())` yet. Emitter-backed stdlib calls (`fmt.X`) are
1554
+ // not packed; GoFront and Go-source package functions are.
1555
+ _checkMultiForwardVariadic(fnType, expr) {
1556
+ if (!expr._multiForward || !fnType.variadic) return;
1557
+ if (this.target !== "wasm" && this.target !== "both") return;
1558
+ const ns =
1559
+ expr.func?.kind === "SelectorExpr" ? expr.func.expr?._type : null;
1560
+ const emitterBacked =
1561
+ ns?.kind === "namespace" &&
1562
+ !ns._gofront &&
1563
+ !ns._mangle?.has(expr.func.field);
1564
+ if (emitterBacked) return;
1565
+ this.err(
1566
+ "passing multiple return values to a variadic function is not supported in wasm packages; assign them to variables first",
1567
+ expr,
1568
+ );
1569
+ },
1570
+
868
1571
  _checkCallArgs(fnType, argTypes, expr, scope = null) {
869
1572
  this._checkArgCount(fnType, argTypes, expr);
1573
+ this._checkMultiForwardVariadic(fnType, expr);
870
1574
  const maxLen = fnType.variadic
871
1575
  ? argTypes.length
872
1576
  : Math.min(fnType.params.length, argTypes.length);
@@ -875,37 +1579,63 @@ export const expressionCheckMethods = {
875
1579
  fnType.variadic && i >= fnType.params.length - 1
876
1580
  ? fnType.params.length - 1
877
1581
  : i;
878
- if (expr.args[i]?._spread) continue;
1582
+ if (expr.args[i]?._spread) {
1583
+ this._rejectSeqAsSlice(argTypes[i], expr.args[i], "a spread argument");
1584
+ continue;
1585
+ }
879
1586
  const targetParam = fnType.params[paramIdx];
880
1587
  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
- }
1588
+ this.assertAssignable(targetParam, argTypes[i], node, "argument");
1589
+ }
1590
+
1591
+ this._checkSimdLaneArgs(expr, scope);
1592
+ },
1593
+
1594
+ // Lane, WithLane, Swizzle and Shuffle take constant lane indices.
1595
+ _checkSimdLaneArgs(expr, scope) {
1596
+ if (expr.func?.kind !== "SelectorExpr") return;
1597
+ const recv = expr.func.expr?._type;
1598
+ const field = expr.func.field;
1599
+ let first = 0;
1600
+ let count = 0;
1601
+ let max = 3;
1602
+ if (recv?.kind === "namespace" && recv.members?.Mask32x4) {
1603
+ if (field !== "Shuffle") return;
1604
+ first = 2;
1605
+ count = 4;
1606
+ max = 7;
1607
+ } else if (this._isSimdType(recv)) {
1608
+ if (field === "Lane" || field === "WithLane") count = 1;
1609
+ else if (field === "Swizzle") count = 4;
1610
+ else return;
1611
+ } else {
1612
+ return;
1613
+ }
1614
+ for (let i = first; i < first + count; i++) {
1615
+ const arg = expr.args[i];
1616
+ if (!arg) continue;
1617
+ const idx = this._constIntValue(arg, scope);
1618
+ if (idx !== null) arg._constInt = idx;
1619
+ if (idx === null || idx < 0 || idx > max) {
1620
+ this.err(
1621
+ `lane index must be a constant integer in range [0, ${max}]`,
1622
+ arg,
1623
+ );
905
1624
  }
906
1625
  }
907
1626
  },
908
1627
 
1628
+ _isSimdType(t) {
1629
+ return (
1630
+ t?.name === "simd.F32x4" ||
1631
+ t?.name === "F32x4" ||
1632
+ t?.name === "simd.Mask32x4" ||
1633
+ t?.name === "Mask32x4" ||
1634
+ t?.underlying?.kind === "simd" ||
1635
+ t?.kind === "simd"
1636
+ );
1637
+ },
1638
+
909
1639
  checkBuiltin(name, expr, argTypes, scope) {
910
1640
  const gen = BUILTIN_CHECK[name];
911
1641
  return gen ? gen(this, name, expr, argTypes, scope) : ANY;
@@ -923,6 +1653,9 @@ export const expressionCheckMethods = {
923
1653
  },
924
1654
 
925
1655
  _checkBuiltinLen(name, expr, argTypes) {
1656
+ this._rejectSeqAsSlice(argTypes[0], expr.args?.[0], `${name}()`);
1657
+ if (expr.args?.length === 1)
1658
+ argTypes[0] = this._derefArrayPtr(expr.args, 0, argTypes[0]);
926
1659
  if (name === "len") {
927
1660
  const size = this._constLenFromType(argTypes[0]);
928
1661
  if (size != null) expr._constLen = size;
@@ -952,6 +1685,17 @@ export const expressionCheckMethods = {
952
1685
  }
953
1686
  if (crt.kind === "untyped" && cit.kind === "untyped")
954
1687
  return UNTYPED_COMPLEX;
1688
+ if (crt.name === "float32" && cit.name === "float32") return COMPLEX64;
1689
+ if (crt.name === "float64" && cit.name === "float64") return COMPLEX128;
1690
+ if (crt.name === "float32" || cit.name === "float32") {
1691
+ if (crt.name === "float64" || cit.name === "float64") {
1692
+ this.err(
1693
+ `invalid complex call: mismatched float types (${typeStr(crt)} and ${typeStr(cit)})`,
1694
+ expr,
1695
+ );
1696
+ }
1697
+ return COMPLEX64;
1698
+ }
955
1699
  return COMPLEX128;
956
1700
  },
957
1701
 
@@ -966,6 +1710,7 @@ export const expressionCheckMethods = {
966
1710
  return ANY;
967
1711
  }
968
1712
  if (zt.kind === "untyped") return UNTYPED_FLOAT;
1713
+ if (isComplex64(zt)) return FLOAT32;
969
1714
  return FLOAT64;
970
1715
  },
971
1716
 
@@ -997,6 +1742,11 @@ export const expressionCheckMethods = {
997
1742
  : this.resolveTypeNode(expr.typeExpr, scope);
998
1743
  const base = t.kind === "named" ? t.underlying : t;
999
1744
  const typeNode = expr.typeExpr;
1745
+ if (expr.typeExpr === null && hintType && base?.kind === "basic") {
1746
+ this.err(`invalid composite literal type ${typeStr(t)}`, expr);
1747
+ return t;
1748
+ }
1749
+ if (this._isSliceOrArray(base)) this._checkArrayLitKeys(expr, scope);
1000
1750
  this._inferArraySize(expr, typeNode, base);
1001
1751
  if (base?.kind === "struct")
1002
1752
  return this._checkStructCompositeLit(expr, scope, t, base);
@@ -1010,6 +1760,11 @@ export const expressionCheckMethods = {
1010
1760
  },
1011
1761
 
1012
1762
  _checkSimdCompositeLit(expr, scope, t, base) {
1763
+ if (base.simd === "Mask32x4") {
1764
+ if (expr.elems.length > 0)
1765
+ this.err("simd.Mask32x4 literal cannot have values", expr);
1766
+ return t;
1767
+ }
1013
1768
  if (expr.elems.length > 4) {
1014
1769
  this.err("too many values in simd.F32x4 literal", expr);
1015
1770
  }
@@ -1021,7 +1776,21 @@ export const expressionCheckMethods = {
1021
1776
  },
1022
1777
 
1023
1778
  _checkStructPositionalElems(expr, scope, t, base) {
1024
- const fieldNames = [...(base.fields?.keys() ?? [])];
1779
+ const fieldNames = base._structFields
1780
+ ? base._structFields.flatMap((f) =>
1781
+ f.embedded
1782
+ ? [
1783
+ (
1784
+ (f.type?.kind === "pointer"
1785
+ ? f.type.base?.name
1786
+ : f.type?.name) ?? ""
1787
+ )
1788
+ .split(".")
1789
+ .pop(),
1790
+ ]
1791
+ : f.names,
1792
+ )
1793
+ : [...(base.fields?.keys() ?? [])];
1025
1794
  for (let i = 0; i < expr.elems.length; i++) {
1026
1795
  const elem = expr.elems[i];
1027
1796
  const fieldName = fieldNames[i];
@@ -1078,8 +1847,19 @@ export const expressionCheckMethods = {
1078
1847
  const et = this.checkCompositeLit(elem, scope, base.elem);
1079
1848
  elem._type = et;
1080
1849
  } else if (elem.kind === "KeyValueExpr") {
1081
- const vt = this.checkExpr(elem.value, scope);
1082
- this.assertAssignable(base.elem, vt, elem.value);
1850
+ if (
1851
+ elem.value.kind === "CompositeLit" &&
1852
+ elem.value.typeExpr === null
1853
+ ) {
1854
+ elem.value._type = this.checkCompositeLit(
1855
+ elem.value,
1856
+ scope,
1857
+ base.elem,
1858
+ );
1859
+ } else {
1860
+ const vt = this.checkExpr(elem.value, scope);
1861
+ this.assertAssignable(base.elem, vt, elem.value);
1862
+ }
1083
1863
  } else {
1084
1864
  const et = this.checkExpr(elem, scope);
1085
1865
  this.assertAssignable(base.elem, et, elem);
@@ -1103,7 +1883,13 @@ export const expressionCheckMethods = {
1103
1883
  _checkMapCompositeLit(expr, scope, t, base) {
1104
1884
  for (const elem of expr.elems) {
1105
1885
  if (elem.kind === "KeyValueExpr") {
1106
- const kt = this.checkExpr(elem.key, scope);
1886
+ let kt;
1887
+ if (elem.key.kind === "CompositeLit" && elem.key.typeExpr === null) {
1888
+ kt = this.checkCompositeLit(elem.key, scope, base.key);
1889
+ elem.key._type = kt;
1890
+ } else {
1891
+ kt = this.checkExpr(elem.key, scope);
1892
+ }
1107
1893
  // value may be an implicit composite lit
1108
1894
  let vt;
1109
1895
  if (
@@ -1116,6 +1902,7 @@ export const expressionCheckMethods = {
1116
1902
  vt = this.checkExpr(elem.value, scope);
1117
1903
  }
1118
1904
  this.assertAssignable(base.key, kt, elem.key);
1905
+ this._markIfaceKey(base, elem.key, kt);
1119
1906
  this.assertAssignable(base.value, vt, elem.value);
1120
1907
  }
1121
1908
  }
@@ -1123,15 +1910,37 @@ export const expressionCheckMethods = {
1123
1910
  },
1124
1911
  };
1125
1912
 
1126
- function computeMaxIndex(elems) {
1127
- let sequential = 0;
1128
- let maxKeyed = -1;
1129
- for (const e of elems) {
1130
- if (e.kind === "KeyValueExpr" && e.key?.value !== undefined) {
1131
- maxKeyed = Math.max(maxKeyed, Number(e.key.value));
1132
- } else {
1133
- sequential++;
1134
- }
1913
+ // Builtins whose calls may be constant expressions (`len("abc")`).
1914
+ const CONST_BUILTINS = new Set([
1915
+ "len",
1916
+ "cap",
1917
+ "real",
1918
+ "imag",
1919
+ "complex",
1920
+ "min",
1921
+ "max",
1922
+ ]);
1923
+
1924
+ // Source-like text of a literal key for diagnostics (`k`, `-1`, `K + 1`).
1925
+ function constKeyText(e) {
1926
+ switch (e?.kind) {
1927
+ case "Ident":
1928
+ return e.name;
1929
+ case "BasicLit":
1930
+ return String(e.value);
1931
+ case "UnaryExpr":
1932
+ return `${e.op}${constKeyText(e.operand)}`;
1933
+ case "BinaryExpr":
1934
+ return `${constKeyText(e.left)} ${e.op} ${constKeyText(e.right)}`;
1935
+ case "ParenExpr":
1936
+ return `(${constKeyText(e.expr)})`;
1937
+ case "SelectorExpr":
1938
+ return `${constKeyText(e.expr)}.${e.field}`;
1939
+ default:
1940
+ return "expression";
1135
1941
  }
1136
- return Math.max(sequential - 1, maxKeyed);
1942
+ }
1943
+
1944
+ function computeMaxIndex(elems) {
1945
+ return compositeLitMaxIndex(elems);
1137
1946
  }