gofront 1.6.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -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,19 +457,40 @@ 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
 
228
490
  _isStructOrNamedMethod(baseType, base, field) {
229
491
  return (
230
492
  (base?.kind === "struct" && base.methods?.has(field)) ||
493
+ (base?.kind === "simd" && base.methods?.has(field)) ||
231
494
  (baseType.kind === "named" && baseType.methods?.has(field))
232
495
  );
233
496
  },
@@ -237,51 +500,147 @@ export const expressionCheckMethods = {
237
500
  return base?.methods?.get(field) ?? baseType.methods?.get(field);
238
501
  },
239
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
+
240
618
  _checkSelectorExpr(expr, scope) {
619
+ expr.expr._typeRefOk = true;
241
620
  const baseType = this.checkExpr(expr.expr, scope);
242
621
  if (baseType?._tainted) return TAINTED_ANY;
622
+ const exclErr = this._checkExcludedSelectorExpr(expr, baseType);
623
+ if (exclErr) return exclErr;
243
624
  if (expr.expr._isTypeRef) {
244
- let base = baseType.kind === "named" ? baseType.underlying : baseType;
245
- base = this.resolveType(base);
246
- const methodType = this._resolveMethodExprType(
247
- baseType,
248
- base,
249
- expr.field,
250
- );
251
- if (methodType) {
252
- expr._isMethodExpr = true;
253
- expr._methodExprRecv = baseType;
254
- return {
255
- kind: "func",
256
- params: [baseType, ...(methodType.params ?? [])],
257
- returns: methodType.returns ?? [],
258
- variadic: methodType.variadic,
259
- };
260
- }
625
+ const mExpr = this._checkMethodExprSelector(expr, baseType);
626
+ if (mExpr) return mExpr;
261
627
  }
262
628
  const ft = this.fieldType(baseType, expr.field, expr);
263
- if (ft?.kind === "func") {
264
- let base = baseType.kind === "named" ? baseType.underlying : baseType;
265
- base = this.resolveType(base);
266
- if (this._isStructOrNamedMethod(baseType, base, expr.field))
267
- expr._isMethodValue = true;
268
- }
629
+ if (ft?.kind === "func")
630
+ this._markMethodValueSelector(expr, baseType, ft, scope);
269
631
  if (
270
632
  baseType?.kind === "namespace" &&
271
633
  this.types.has(`${baseType.name}.${expr.field}`) &&
272
- !(
273
- baseType.name === "binary" &&
274
- (expr.field === "LittleEndian" || expr.field === "BigEndian")
275
- )
634
+ baseType.name !== "binary"
276
635
  ) {
277
636
  expr._isTypeRef = true;
278
637
  }
279
638
  return ft;
280
639
  },
281
640
 
282
- _checkArrayBoundsExpr(btu, bt, expr) {
641
+ _checkArrayBoundsExpr(btu, bt, expr, scope) {
283
642
  if (btu.kind !== "array" || btu.size == null) return;
284
- const idx = this._constIntValue(expr.index);
643
+ const idx = this._constIntValue(expr.index, scope);
285
644
  if (idx === null) return;
286
645
  if (idx < 0)
287
646
  this.err(`invalid array index ${idx} (index must not be negative)`, expr);
@@ -292,26 +651,97 @@ export const expressionCheckMethods = {
292
651
  );
293
652
  },
294
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
+
295
717
  _checkIndexExpr(expr, scope) {
296
- const bt = this.checkExpr(expr.expr, scope);
297
- 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);
298
730
  if (bt?._tainted) return TAINTED_ANY;
299
731
  if (isAny(bt)) return ANY;
300
- const btu =
301
- bt.kind === "named"
302
- ? (this.resolveType(bt.underlying) ?? bt.underlying)
303
- : bt;
732
+ const btu = this._deepUnderlying(bt);
304
733
  if (btu.kind === "slice" || btu.kind === "array") {
305
- this._checkArrayBoundsExpr(btu, bt, expr);
734
+ this._checkArrayBoundsExpr(btu, bt, expr, scope);
306
735
  return btu.elem;
307
736
  }
308
737
  if (btu.kind === "map") {
309
738
  expr._mapValueType = btu.value;
739
+ this._markIfaceKey(btu, expr.index, indexType);
310
740
  return btu.value;
311
741
  }
312
742
  if (btu.kind === "shared") {
313
743
  expr._sharedElem = btu.elem;
314
- const idx = this._constIntValue(expr.index);
744
+ const idx = this._constIntValue(expr.index, scope);
315
745
  if (idx !== null && idx < 0)
316
746
  this.err(`invalid index ${idx} (index must not be negative)`, expr);
317
747
  return btu.elem;
@@ -320,6 +750,30 @@ export const expressionCheckMethods = {
320
750
  return this.err(`Cannot index type ${typeStr(bt)}`, expr);
321
751
  },
322
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
+
323
777
  // copy(dst, src): element types must be identical when either side is a
324
778
  // shared buffer (the wasm emitter uses one element type for both sides).
325
779
  _checkBuiltinCopy(e, at) {
@@ -345,15 +799,33 @@ export const expressionCheckMethods = {
345
799
  return INT;
346
800
  },
347
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
+
348
815
  _checkSliceExpr(expr, scope) {
349
- const bt = this.checkExpr(expr.expr, scope);
816
+ const bt = this._derefArrayPtr(
817
+ expr,
818
+ "expr",
819
+ this.checkExpr(expr.expr, scope),
820
+ );
350
821
  if (expr.low) this.checkExpr(expr.low, scope);
351
822
  if (expr.high) this.checkExpr(expr.high, scope);
352
823
  if (expr.max) this.checkExpr(expr.max, scope);
353
824
  if (bt?._tainted) return TAINTED_ANY;
354
825
  if (isAny(bt)) return ANY;
355
- if (bt.kind === "slice" || bt.kind === "array")
356
- 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 };
357
829
  if (isString(bt)) return STRING;
358
830
  if (sharedInfo(bt))
359
831
  return this.err(
@@ -386,7 +858,7 @@ export const expressionCheckMethods = {
386
858
  }
387
859
  const inner = new Scope(scope);
388
860
  for (const p of expr.params)
389
- inner.define(p.name, p.type ? this.resolveTypeNode(p.type, scope) : ANY);
861
+ inner.define(p.name, p.type ? this._paramType(p, scope) : ANY);
390
862
  const ret = expr.returnType
391
863
  ? this.resolveTypeNode(expr.returnType, scope)
392
864
  : VOID;
@@ -403,6 +875,7 @@ export const expressionCheckMethods = {
403
875
  kind: "func",
404
876
  params: paramTypes,
405
877
  returns: [ret],
878
+ variadic: expr.params.at(-1)?.variadic === true,
406
879
  async: expr.async,
407
880
  };
408
881
  },
@@ -428,6 +901,19 @@ export const expressionCheckMethods = {
428
901
  );
429
902
  },
430
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
+
431
917
  _isInvalidArrayConv(tgt, src, srcType) {
432
918
  return tgt?.kind === "array" && src?.kind !== "array" && !isAny(srcType);
433
919
  },
@@ -435,6 +921,8 @@ export const expressionCheckMethods = {
435
921
  _checkTypeConversion(expr, scope) {
436
922
  const srcType = this.checkExpr(expr.expr, scope);
437
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);
438
926
  const srcResolved =
439
927
  srcType?.kind === "named" ? srcType.underlying : srcType;
440
928
  const tgtResolved =
@@ -455,6 +943,8 @@ export const expressionCheckMethods = {
455
943
  expr,
456
944
  );
457
945
  }
946
+ if (tgtResolved?.kind === "slice")
947
+ this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
458
948
  if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
459
949
  this._checkElemTypeCompatible(
460
950
  tgtResolved.elem,
@@ -494,6 +984,8 @@ export const expressionCheckMethods = {
494
984
  );
495
985
  return;
496
986
  }
987
+ if (tgtResolved?.kind === "slice")
988
+ this._rejectSeqAsSlice(srcType, expr.args?.[0], "a slice conversion");
497
989
  if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
498
990
  this._checkElemTypeCompatible(
499
991
  tgtResolved.elem,
@@ -512,7 +1004,15 @@ export const expressionCheckMethods = {
512
1004
  return;
513
1005
  }
514
1006
  if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
1007
+ if (this._sameStructFields(srcResolved, tgtResolved)) return;
515
1008
  if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
1009
+ if (isString(srcResolved) && isString(tgtResolved)) return;
1010
+ if (isBool(srcResolved) && isBool(tgtResolved)) return;
1011
+ if (
1012
+ (isComplex(srcResolved) || isNumeric(srcResolved)) &&
1013
+ isComplex(tgtResolved)
1014
+ )
1015
+ return;
516
1016
  if (
517
1017
  isString(srcResolved) &&
518
1018
  tgtResolved?.kind === "slice" &&
@@ -586,19 +1086,31 @@ export const expressionCheckMethods = {
586
1086
  },
587
1087
 
588
1088
  _checkRangeExpr(expr, scope) {
589
- const collType = this.checkExpr(expr.expr, scope);
1089
+ const collType = this._derefArrayPtr(
1090
+ expr,
1091
+ "expr",
1092
+ this.checkExpr(expr.expr, scope),
1093
+ );
590
1094
  const resolved =
591
1095
  collType?.kind === "named" ? collType.underlying : collType;
592
1096
  return this._rangeCollTypeTuple(resolved, collType);
593
1097
  },
594
1098
 
595
1099
  _checkInstantiationExpr(expr, scope) {
1100
+ const typeRef = this._genericTypeRef(expr, scope);
1101
+ if (typeRef) return typeRef;
596
1102
  // Generic instantiation: Foo[int] or Foo[int, string]
597
1103
  const baseType = this.checkExpr(expr.expr, scope);
598
1104
  if (baseType?.kind === "generic") {
599
1105
  const typeArgs = expr.typeArgs.map((ta) =>
600
1106
  this.resolveTypeNode(ta, scope),
601
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
+ }
602
1114
  // Check constraints
603
1115
  for (
604
1116
  let i = 0;
@@ -622,9 +1134,28 @@ export const expressionCheckMethods = {
622
1134
  // rewrite the node in place into an IndexExpr and check that instead.
623
1135
  const indexExpr = this._instantiationAsIndex(expr, baseType);
624
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
+ );
625
1142
  return baseType;
626
1143
  },
627
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
+
628
1159
  _instantiationAsIndex(expr, baseType) {
629
1160
  if (!baseType || expr.typeArgs.length !== 1) return null;
630
1161
  const ta = expr.typeArgs[0];
@@ -649,18 +1180,97 @@ export const expressionCheckMethods = {
649
1180
  return expr;
650
1181
  },
651
1182
 
652
- _constIntValue(expr) {
653
- if (expr.kind === "BasicLit" && expr.litKind === "INT")
654
- return Number(expr.value);
655
- if (
656
- expr.kind === "UnaryExpr" &&
657
- expr.op === "-" &&
658
- expr.operand?.kind === "BasicLit" &&
659
- expr.operand.litKind === "INT"
660
- ) {
661
- return -Number(expr.operand.value);
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++;
1228
+ }
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;
662
1273
  }
663
- return null;
664
1274
  },
665
1275
 
666
1276
  _resolveCallArgTypes(expr, scope) {
@@ -688,11 +1298,88 @@ export const expressionCheckMethods = {
688
1298
  if (!resolved) return null;
689
1299
  fnType = resolved;
690
1300
  }
691
- 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
+ );
692
1369
  },
693
1370
 
694
1371
  checkCall(expr, scope) {
1372
+ expr.func._typeRefOk = true;
695
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;
696
1383
  this._checkSharedAllocSite(expr, scope);
697
1384
  this._checkBoundaryCallInLoop(expr, scope);
698
1385
  if (fnType.kind === "builtin") {
@@ -707,6 +1394,9 @@ export const expressionCheckMethods = {
707
1394
  const argTypes = this._resolveCallArgTypes(expr, scope);
708
1395
 
709
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]);
710
1400
  if (isAny(fnType)) return ANY;
711
1401
 
712
1402
  // Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
@@ -725,13 +1415,17 @@ export const expressionCheckMethods = {
725
1415
  return fnType;
726
1416
  }
727
1417
 
1418
+ if (fnType.kind === "typeParam" && expr.func._isTypeRef) {
1419
+ return this._checkTypeParamConversion(fnType, argTypes, expr);
1420
+ }
1421
+
728
1422
  fnType = this._resolveCallFnType(fnType, argTypes, expr, scope);
729
1423
  if (!fnType) return ANY;
730
1424
  if (fnType.kind !== "func") {
731
1425
  return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
732
1426
  }
733
1427
 
734
- this._checkCallArgs(fnType, argTypes, expr);
1428
+ this._checkCallArgs(fnType, argTypes, expr, scope);
735
1429
 
736
1430
  // Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
737
1431
  // how their result derives from the first argument.
@@ -797,19 +1491,26 @@ export const expressionCheckMethods = {
797
1491
  return under ? arg : null;
798
1492
  case "elemOfArg0":
799
1493
  return under?.elem ?? null;
800
- case "keysOfArg0":
801
- return under?.key ? { kind: "slice", elem: under.key } : null;
802
- case "valuesOfArg0": {
803
- const v = under?.value ?? under?.elem;
804
- return v ? { kind: "slice", elem: v } : null;
805
- }
806
1494
  default:
807
1495
  return null;
808
1496
  }
809
1497
  },
810
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
+
811
1509
  _resolveGenericFnType(fnType, argTypes, expr, scope) {
812
- const typeArgs = this.inferTypeArgs(fnType, argTypes);
1510
+ const typeArgs = this.inferTypeArgs(
1511
+ fnType,
1512
+ this._inferArgTypes(expr, argTypes),
1513
+ );
813
1514
  if (!typeArgs) {
814
1515
  this.err(
815
1516
  `Cannot infer type arguments for generic function '${fnType.name}'`,
@@ -848,8 +1549,28 @@ export const expressionCheckMethods = {
848
1549
  );
849
1550
  },
850
1551
 
851
- _checkCallArgs(fnType, argTypes, expr) {
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
+
1571
+ _checkCallArgs(fnType, argTypes, expr, scope = null) {
852
1572
  this._checkArgCount(fnType, argTypes, expr);
1573
+ this._checkMultiForwardVariadic(fnType, expr);
853
1574
  const maxLen = fnType.variadic
854
1575
  ? argTypes.length
855
1576
  : Math.min(fnType.params.length, argTypes.length);
@@ -858,11 +1579,61 @@ export const expressionCheckMethods = {
858
1579
  fnType.variadic && i >= fnType.params.length - 1
859
1580
  ? fnType.params.length - 1
860
1581
  : i;
861
- 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
+ }
862
1586
  const targetParam = fnType.params[paramIdx];
863
1587
  const node = expr._multiForward ? null : expr.args[i];
864
- this.assertAssignable(targetParam, argTypes[i], node);
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;
865
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
+ );
1624
+ }
1625
+ }
1626
+ },
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
+ );
866
1637
  },
867
1638
 
868
1639
  checkBuiltin(name, expr, argTypes, scope) {
@@ -882,6 +1653,9 @@ export const expressionCheckMethods = {
882
1653
  },
883
1654
 
884
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]);
885
1659
  if (name === "len") {
886
1660
  const size = this._constLenFromType(argTypes[0]);
887
1661
  if (size != null) expr._constLen = size;
@@ -911,6 +1685,17 @@ export const expressionCheckMethods = {
911
1685
  }
912
1686
  if (crt.kind === "untyped" && cit.kind === "untyped")
913
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
+ }
914
1699
  return COMPLEX128;
915
1700
  },
916
1701
 
@@ -925,6 +1710,7 @@ export const expressionCheckMethods = {
925
1710
  return ANY;
926
1711
  }
927
1712
  if (zt.kind === "untyped") return UNTYPED_FLOAT;
1713
+ if (isComplex64(zt)) return FLOAT32;
928
1714
  return FLOAT64;
929
1715
  },
930
1716
 
@@ -956,9 +1742,16 @@ export const expressionCheckMethods = {
956
1742
  : this.resolveTypeNode(expr.typeExpr, scope);
957
1743
  const base = t.kind === "named" ? t.underlying : t;
958
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);
959
1750
  this._inferArraySize(expr, typeNode, base);
960
1751
  if (base?.kind === "struct")
961
1752
  return this._checkStructCompositeLit(expr, scope, t, base);
1753
+ if (base?.kind === "simd")
1754
+ return this._checkSimdCompositeLit(expr, scope, t, base);
962
1755
  if (this._isSliceOrArray(base))
963
1756
  return this._checkSliceArrayCompositeLit(expr, scope, t, base, typeNode);
964
1757
  if (base?.kind === "map")
@@ -966,8 +1759,38 @@ export const expressionCheckMethods = {
966
1759
  return this._checkUnknownCompositeLit(expr, scope, t);
967
1760
  },
968
1761
 
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
+ }
1768
+ if (expr.elems.length > 4) {
1769
+ this.err("too many values in simd.F32x4 literal", expr);
1770
+ }
1771
+ for (const elem of expr.elems) {
1772
+ const vt = this.checkExpr(elem, scope);
1773
+ this.assertAssignable(base.elem ?? FLOAT32, vt, elem);
1774
+ }
1775
+ return t;
1776
+ },
1777
+
969
1778
  _checkStructPositionalElems(expr, scope, t, base) {
970
- 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() ?? [])];
971
1794
  for (let i = 0; i < expr.elems.length; i++) {
972
1795
  const elem = expr.elems[i];
973
1796
  const fieldName = fieldNames[i];
@@ -987,21 +1810,19 @@ export const expressionCheckMethods = {
987
1810
  for (const elem of expr.elems) {
988
1811
  if (elem.kind === "KeyValueExpr") {
989
1812
  const keyName = elem.key.name;
990
- const fieldType = base.fields?.get(keyName);
1813
+ const embed = base._embeds?.find((e) => {
1814
+ const inner = e.kind === "pointer" ? e.base : e;
1815
+ const name =
1816
+ inner.kind === "named" ? inner.name.split(".").pop() : null;
1817
+ return name === keyName;
1818
+ });
1819
+ if (embed) elem._isEmbedInit = true;
1820
+ const fieldType = base.fields?.get(keyName) ?? embed;
991
1821
  if (fieldType) {
992
1822
  const vt = this.checkExpr(elem.value, scope);
993
1823
  this.assertAssignable(fieldType, vt, elem.value);
994
1824
  } else {
995
- const embed = base._embeds?.find(
996
- (e) => (e.kind === "named" ? e.name : null) === keyName,
997
- );
998
- if (embed) {
999
- const vt = this.checkExpr(elem.value, scope);
1000
- this.assertAssignable(embed, vt, elem.value);
1001
- elem._isEmbedInit = true;
1002
- } else {
1003
- this.err(`Unknown field '${keyName}'`, elem.key);
1004
- }
1825
+ this.err(`Unknown field '${keyName}'`, elem.key);
1005
1826
  }
1006
1827
  } else {
1007
1828
  this.checkExpr(elem, scope);
@@ -1026,8 +1847,19 @@ export const expressionCheckMethods = {
1026
1847
  const et = this.checkCompositeLit(elem, scope, base.elem);
1027
1848
  elem._type = et;
1028
1849
  } else if (elem.kind === "KeyValueExpr") {
1029
- const vt = this.checkExpr(elem.value, scope);
1030
- 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
+ }
1031
1863
  } else {
1032
1864
  const et = this.checkExpr(elem, scope);
1033
1865
  this.assertAssignable(base.elem, et, elem);
@@ -1051,7 +1883,13 @@ export const expressionCheckMethods = {
1051
1883
  _checkMapCompositeLit(expr, scope, t, base) {
1052
1884
  for (const elem of expr.elems) {
1053
1885
  if (elem.kind === "KeyValueExpr") {
1054
- 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
+ }
1055
1893
  // value may be an implicit composite lit
1056
1894
  let vt;
1057
1895
  if (
@@ -1064,6 +1902,7 @@ export const expressionCheckMethods = {
1064
1902
  vt = this.checkExpr(elem.value, scope);
1065
1903
  }
1066
1904
  this.assertAssignable(base.key, kt, elem.key);
1905
+ this._markIfaceKey(base, elem.key, kt);
1067
1906
  this.assertAssignable(base.value, vt, elem.value);
1068
1907
  }
1069
1908
  }
@@ -1071,15 +1910,37 @@ export const expressionCheckMethods = {
1071
1910
  },
1072
1911
  };
1073
1912
 
1074
- function computeMaxIndex(elems) {
1075
- let sequential = 0;
1076
- let maxKeyed = -1;
1077
- for (const e of elems) {
1078
- if (e.kind === "KeyValueExpr" && e.key?.value !== undefined) {
1079
- maxKeyed = Math.max(maxKeyed, Number(e.key.value));
1080
- } else {
1081
- sequential++;
1082
- }
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";
1083
1941
  }
1084
- return Math.max(sequential - 1, maxKeyed);
1942
+ }
1943
+
1944
+ function computeMaxIndex(elems) {
1945
+ return compositeLitMaxIndex(elems);
1085
1946
  }