gofront 1.7.0 → 1.9.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 (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -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 +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -1,27 +1,45 @@
1
1
  // TypeChecker expression-checking methods — installed as a mixin on TypeChecker.prototype.
2
2
 
3
+ import { exprText, operandKind } from "./operand-text.js";
4
+ import {
5
+ EXCLUDED_STDLIB_MEMBERS,
6
+ EXCLUDED_STDLIB_PACKAGES,
7
+ } from "./stdlib/excluded.js";
3
8
  import { sharedInfo } from "./stdlib/shared.js";
4
9
  import {
5
10
  ANY,
11
+ BASIC_TYPES,
6
12
  BOOL,
7
13
  BROWSER_GLOBALS,
14
+ COMPLEX64,
8
15
  COMPLEX128,
16
+ compositeLitMaxIndex,
17
+ displayTypeName,
9
18
  ERROR,
19
+ embeddedFieldName,
20
+ FLOAT32,
10
21
  FLOAT64,
22
+ FMT_PRINT_FUNCS,
11
23
  INT,
24
+ is64BitInt,
12
25
  isAny,
13
26
  isArray,
14
27
  isBool,
15
28
  isComplex,
29
+ isComplex64,
16
30
  isComplexOrNumeric,
31
+ isInteger,
32
+ isNil,
17
33
  isNumeric,
18
34
  isString,
19
35
  NIL,
20
36
  RUNE,
21
37
  Scope,
38
+ SIZED_INT_NAMES,
22
39
  STRING,
23
40
  TAINTED_ANY,
24
41
  typeStr,
42
+ UINT8,
25
43
  UNTYPED_BOOL,
26
44
  UNTYPED_COMPLEX,
27
45
  UNTYPED_FLOAT,
@@ -30,8 +48,25 @@ import {
30
48
  VOID,
31
49
  } from "./types.js";
32
50
 
51
+ const NILABLE_KINDS = new Set([
52
+ "slice",
53
+ "map",
54
+ "pointer",
55
+ "func",
56
+ "interface",
57
+ "chan",
58
+ ]);
59
+
33
60
  /** @typedef {import('./index.js').TypeChecker} TypeChecker */
34
61
 
62
+ // min/max: the type of the first typed operand; with only untyped constants,
63
+ // an untyped float if any operand is one (Go's "largest kind" rule).
64
+ function minMaxType(at) {
65
+ const typed = at.find((t) => t && t.kind !== "untyped");
66
+ if (typed) return typed;
67
+ return at.find((t) => t?.base === "float64") ?? at[0] ?? ANY;
68
+ }
69
+
35
70
  // Dispatch table for builtin function type checking.
36
71
  // Each value is (self, name, expr, argTypes, scope) => returnType.
37
72
  const BUILTIN_CHECK = {
@@ -52,37 +87,55 @@ const BUILTIN_CHECK = {
52
87
  }
53
88
  const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
54
89
  if (sb?.kind === "slice") {
90
+ const is64 = is64BitInt(sb.elem);
55
91
  for (let i = 1; i < e.args.length; i++) {
56
- if (!e.args[i]._spread && at[i])
92
+ if (e.args[i]._spread)
93
+ s._rejectSeqAsSlice(at[i], e.args[i], "a spread argument");
94
+ if (!e.args[i]._spread && at[i]) {
95
+ if (is64) e.args[i]._is64Bit = true;
57
96
  s._markIfaceBox(
58
97
  s.resolveType(sb.elem),
59
98
  s.resolveType(at[i]),
60
99
  e.args[i],
61
100
  );
101
+ }
62
102
  }
63
103
  }
64
104
  return st;
65
105
  },
66
106
  copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
67
- delete: () => VOID,
107
+ delete: (s, _n, e, at) => {
108
+ const mt = s._deepUnderlying(at[0]);
109
+ if (mt?.kind === "map" && e.args[1]) s._markIfaceKey(mt, e.args[1], at[1]);
110
+ return VOID;
111
+ },
68
112
  make: (s, _n, e, _at, sc) => {
69
113
  if (e.args.length < 1) return ANY;
70
114
  const ta = e.args[0];
71
115
  const tn = ta.kind === "TypeExpr" ? ta.type : ta;
72
- return s.resolveTypeNode(tn, sc) ?? ANY;
116
+ const made = s.resolveTypeNode(tn, sc) ?? ANY;
117
+ // `make(S, n)` with S a type parameter makes its core type.
118
+ const core = made.kind === "typeParam" ? s._coreType(made, sc) : null;
119
+ if (core) ta._coreType = core;
120
+ return made;
73
121
  },
74
122
  new: (s, _n, e, _at, sc) => {
75
123
  if (e.args.length < 1) return ANY;
76
- const inner = s.resolveTypeNode(e.args[0], sc);
124
+ const ta = e.args[0];
125
+ const inner = s.resolveTypeNode(ta.kind === "TypeExpr" ? ta.type : ta, sc);
77
126
  return { kind: "pointer", base: inner };
78
127
  },
79
- print: () => VOID,
80
- println: () => VOID,
81
- panic: () => VOID,
128
+ print: (s, n, e, at) => s._checkPrintArgs(n, e, at),
129
+ println: (s, n, e, at) => s._checkPrintArgs(n, e, at),
130
+ panic: (s, _n, e, at) => {
131
+ // The operand converts to `any`, so a defined type keeps its identity.
132
+ if (e.args[0] && at?.[0]) s._markIfaceBox(ANY, at[0], e.args[0]);
133
+ return VOID;
134
+ },
82
135
  recover: () => ANY,
83
136
  error: () => ERROR,
84
- min: (_s, _n, _e, at) => at[0] ?? ANY,
85
- max: (_s, _n, _e, at) => at[0] ?? ANY,
137
+ min: (_s, _n, _e, at) => minMaxType(at),
138
+ max: (_s, _n, _e, at) => minMaxType(at),
86
139
  clear: () => VOID,
87
140
  complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
88
141
  real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
@@ -109,6 +162,7 @@ export const expressionCheckMethods = {
109
162
  checkExpr(expr, scope) {
110
163
  const t = this._checkExpr(expr, scope);
111
164
  expr._type = t;
165
+ this._noteConst(expr, scope);
112
166
  return t;
113
167
  },
114
168
 
@@ -133,12 +187,56 @@ export const expressionCheckMethods = {
133
187
  return UNTYPED_COMPLEX;
134
188
  case "Ident": {
135
189
  const t = scope.lookup(expr.name);
136
- if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
137
- if (this.types.has(expr.name)) expr._isTypeRef = true;
190
+ // `(*int)(nil)`: a predeclared type named where a type may stand.
191
+ if (!t && expr._typeRefOk && Object.hasOwn(BASIC_TYPES, expr.name)) {
192
+ expr._isTypeRef = true;
193
+ return BASIC_TYPES[expr.name];
194
+ }
195
+ if (!t) {
196
+ // The import of an excluded package already reported; its
197
+ // uses are not a second root cause.
198
+ const imported = this._fileImports
199
+ ?.get(this._currentFile)
200
+ ?.get(expr.name);
201
+ if (imported && EXCLUDED_STDLIB_PACKAGES.has(imported))
202
+ return TAINTED_ANY;
203
+ if (this._unavailableImports?.has(expr.name)) return TAINTED_ANY;
204
+ // `gom` needs no import in a js package; wasm packages get
205
+ // the same answer as an `import "gom"`.
206
+ if (
207
+ expr.name === "gom" &&
208
+ (this.target === "wasm" || this.target === "both")
209
+ ) {
210
+ this.recordBlocker("gom usage", "gom");
211
+ return this.err(
212
+ "package 'gom' is not available in wasm packages",
213
+ expr,
214
+ );
215
+ }
216
+ return this.err(`Undefined: '${expr.name}'`, expr);
217
+ }
218
+ if (this._namesType(expr.name, t, scope) || t.kind === "typeParam")
219
+ expr._isTypeRef = true;
220
+ // A Go declaration that shadows a browser global: JS output renames
221
+ // it so generated code keeps reaching the real global. Package
222
+ // namespaces (`import console "./util"`) are never renamed: JS
223
+ // selects their members directly (bundledPackages).
224
+ if (BROWSER_GLOBALS.has(expr.name) && t.kind !== "namespace") {
225
+ const owner = scope.lookupScope(expr.name);
226
+ if (
227
+ owner !== this.universe &&
228
+ !(owner === this.globals && this._externalNames.has(expr.name))
229
+ )
230
+ expr._shadowsGlobal = true;
231
+ }
232
+ // Go-source stdlib loading: record package-scope references so the
233
+ // loader can mangle them (see compiler.js loadStdlibPackage).
234
+ if (this._pkgRefs && scope.lookupScope(expr.name) === this.globals)
235
+ this._pkgRefs.add(`${this._currentFile}:${expr._line}:${expr._col}`);
138
236
  if (
139
237
  (this.target === "wasm" || this.target === "both") &&
140
238
  BROWSER_GLOBALS.has(expr.name) &&
141
- scope.lookup(expr.name) === this.globals.lookup(expr.name)
239
+ scope.lookupScope(expr.name) === this.universe
142
240
  ) {
143
241
  this.recordBlocker("browser global", expr.name);
144
242
  this.err(
@@ -147,14 +245,6 @@ export const expressionCheckMethods = {
147
245
  `(package '${this.pkgName}' is //gofront:target ${this.target} — browser globals are only available in js packages)`,
148
246
  );
149
247
  }
150
- if (
151
- (this.target === "wasm" || this.target === "both") &&
152
- expr.name === "gom" &&
153
- scope.lookup(expr.name) === this.globals.lookup(expr.name)
154
- ) {
155
- this.recordBlocker("gom usage", "gom");
156
- this.err("package 'gom' is not available in wasm packages", expr);
157
- }
158
248
  return t;
159
249
  }
160
250
  case "BinaryExpr": {
@@ -163,7 +253,18 @@ export const expressionCheckMethods = {
163
253
  if (lt?._tainted || rt?._tainted) return TAINTED_ANY;
164
254
  if (isAny(lt) || isAny(rt))
165
255
  return this.binaryResultType(expr.op, ANY, ANY, expr);
166
- return this.binaryResultType(expr.op, lt, rt, expr);
256
+ this._checkConstOperands(expr.op, lt, rt, expr);
257
+ this._markTypeParamMapNil(expr, lt, rt, scope);
258
+ const res = this.binaryResultType(expr.op, lt, rt, expr);
259
+ if (expr.op !== "<<" && expr.op !== ">>") {
260
+ this._retypeUntypedShift(lt, rt, expr.right);
261
+ this._retypeUntypedShift(rt, lt, expr.left);
262
+ }
263
+ if (is64BitInt(lt) && rt?.kind === "untyped" && expr.right)
264
+ expr.right._is64Bit = true;
265
+ if (is64BitInt(rt) && lt?.kind === "untyped" && expr.left)
266
+ expr.left._is64Bit = true;
267
+ return res;
167
268
  }
168
269
  case "AwaitExpr": {
169
270
  if (this.target === "wasm" || this.target === "both") {
@@ -177,6 +278,11 @@ export const expressionCheckMethods = {
177
278
  }
178
279
  case "InstantiationExpr":
179
280
  return this._checkInstantiationExpr(expr, scope);
281
+ case "TypeName":
282
+ // A type in expression position: monomorphisation rewrites
283
+ // `Box[int]` in `(*Box[int]).Set` to the TypeName `Box$int`.
284
+ expr._isTypeRef = true;
285
+ return this._typeRefOperand(expr, this.resolveTypeNode(expr, scope));
180
286
  default: {
181
287
  const m = CHECK_EXPR_DELEGATE[expr.kind];
182
288
  if (m) return this[m](expr, scope);
@@ -186,6 +292,13 @@ export const expressionCheckMethods = {
186
292
  return ANY;
187
293
  },
188
294
 
295
+ // A type ref (`*T`, `Box[int]`) is a value only as the base of a method
296
+ // expression (`(*T).M`), a conversion callee or the operand of `*`.
297
+ _typeRefOperand(expr, t) {
298
+ if (expr._typeRefOk) return t;
299
+ return this.err(`${typeStr(t)} (type) is not an expression`, expr);
300
+ },
301
+
189
302
  _checkDeref(ot, expr) {
190
303
  if (ot === ANY) return ANY;
191
304
  if (ot.kind === "pointer") return ot.base;
@@ -201,12 +314,209 @@ export const expressionCheckMethods = {
201
314
  },
202
315
 
203
316
  _checkNumericUnaryOp(ot, expr) {
317
+ if (ot?.kind === "typeParam") {
318
+ if (!this._typeParamAllows(ot, isComplexOrNumeric))
319
+ this.err(`${expr.op} requires numeric`, expr);
320
+ return ot;
321
+ }
204
322
  if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
205
323
  this.err(`${expr.op} requires numeric`, expr);
206
324
  return ot;
207
325
  },
208
326
 
327
+ // `^x` on a type parameter needs every type in its constraint to be an
328
+ // integer. Other operands keep the existing (unchecked) behaviour.
329
+ _checkComplementUnaryOp(ot, expr) {
330
+ if (ot?.kind === "typeParam" && !this._typeParamAllows(ot, isInteger))
331
+ this.err("^ requires integer", expr);
332
+ return ot;
333
+ },
334
+
335
+ // Whether every type in the type set of type parameter `tp`'s constraint
336
+ // satisfies `pred`. A constraint without a type set (any, comparable,
337
+ // method-only interfaces) allows nothing.
338
+ _typeParamAllows(tp, pred) {
339
+ const terms = this._constraintTerms(tp.constraint, new Set());
340
+ return terms !== null && terms.length > 0 && terms.every(pred);
341
+ },
342
+
343
+ // `scope` resolves the type parameters a term mentions (`~[]E`); it
344
+ // defaults to the package scope.
345
+ _constraintTerms(c, seen, scope = this.globals) {
346
+ if (!c || seen.has(c)) return null;
347
+ seen.add(c);
348
+ // Receiver type parameters keep their constraint as an AST node.
349
+ if (
350
+ c.kind === "TypeName" ||
351
+ c.kind === "InterfaceType" ||
352
+ c.kind === "GenericTypeName" ||
353
+ c.kind === "InstantiationExpr" ||
354
+ c.kind === "IndexExpr"
355
+ )
356
+ return this._constraintTerms(this.resolveTypeNode(c, scope), seen, scope);
357
+ if (c.kind === "named")
358
+ return this._constraintTerms(c.underlying, seen, scope);
359
+ if (c.kind === "interface") return this._interfaceTerms(c, seen, scope);
360
+ if (c.kind === "typeParam")
361
+ return this._constraintTerms(c.constraint, seen, scope);
362
+ if (c.kind !== "UnionConstraint") return isAny(c) ? null : [c];
363
+ const out = [];
364
+ for (const term of c.terms) {
365
+ const t = term.resolved ?? this.resolveTypeNode(term.type, scope);
366
+ const inner =
367
+ t?.kind === "named" && t.underlying?.kind === "interface"
368
+ ? this._constraintTerms(t, seen, scope)
369
+ : [t];
370
+ if (inner === null) return null;
371
+ out.push(...inner);
372
+ }
373
+ return out;
374
+ },
375
+
376
+ // An interface's type set is the intersection of its own union and the
377
+ // type sets of the interfaces it embeds; null when it has none.
378
+ _interfaceTerms(iface, seen, scope) {
379
+ const sets = [iface.unionConstraint, ...(iface._typeSetEmbeds ?? [])]
380
+ .filter(Boolean)
381
+ .map((s) => this._constraintTerms(s, new Set(seen), scope));
382
+ if (sets.length === 0 || sets.includes(null)) return null;
383
+ const [first, ...rest] = sets;
384
+ return first.filter((t) =>
385
+ rest.every((set) => set.some((u) => typeStr(u) === typeStr(t))),
386
+ );
387
+ },
388
+
389
+ // Only structs and arrays are their own managed objects in wasm; every other
390
+ // value (numbers, strings, slices, maps, interfaces, pointers, funcs, SIMD
391
+ // vectors) lives inline in its parent struct, array or global, so wasm has
392
+ // no stable address for it. Type parameters are checked per instantiation.
393
+ _isScalarStorageType(t) {
394
+ const u = this.resolveType(t?.kind === "named" ? t.underlying : t) ?? t;
395
+ const k = u?.kind;
396
+ if (!k || u._tainted) return false;
397
+ return k !== "struct" && k !== "array" && k !== "typeParam";
398
+ },
399
+
400
+ // True when `operand` is a struct field, array/slice element or package-level
401
+ // variable holding a scalar, in a package compiled to wasm.
402
+ _isScalarInteriorOperand(operand, type, scope) {
403
+ if (!this._isWasmPackage()) return false;
404
+ let node = operand;
405
+ while (node?.kind === "ParenExpr") node = node.expr;
406
+ if (!node) return false;
407
+ if (node.kind === "Ident") {
408
+ if (scope.lookupScope(node.name) !== this.globals) return false;
409
+ if (scope.isConst?.(node.name)) return false;
410
+ const sym = this.globals.symbols.get(node.name);
411
+ if (sym?.kind === "func" || sym?.kind === "namespace") return false;
412
+ } else if (node.kind !== "SelectorExpr" && node.kind !== "IndexExpr") {
413
+ return false;
414
+ }
415
+ if (node.kind === "SelectorExpr" && node.expr?._isTypeRef) return false;
416
+ if (node.kind === "SelectorExpr" && node.expr?._type?.kind === "namespace")
417
+ return false;
418
+ return this._isScalarStorageType(type);
419
+ },
420
+
421
+ // Postfix selectors and index expressions carry no position; report at
422
+ // their innermost positioned operand.
423
+ _positioned(node) {
424
+ let at = node;
425
+ while (at && !at._line && !at.line) at = at.operand ?? at.expr;
426
+ return at ?? node;
427
+ },
428
+
429
+ _errScalarInteriorAddress(node, type) {
430
+ return this.err(
431
+ `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`,
432
+ this._positioned(node),
433
+ );
434
+ },
435
+
436
+ // Go: map elements are not addressable, nor are fields and array elements
437
+ // reached from one by value (`&m[k]`, `&m[k].f`, `&m[k][i]` on an array).
438
+ _isMapElementOperand(operand) {
439
+ let node = operand;
440
+ while (node?.kind === "ParenExpr") node = node.expr;
441
+ if (node?.kind !== "SelectorExpr" && node?.kind !== "IndexExpr")
442
+ return false;
443
+ const bt = node.expr?._type;
444
+ const b = this.resolveType(bt?.kind === "named" ? bt.underlying : bt) ?? bt;
445
+ if (node.kind === "IndexExpr" && b?.kind === "map") return true;
446
+ if (b?.kind === "pointer" || b?.kind === "slice") return false;
447
+ if (node.kind === "SelectorExpr" && b?.kind !== "struct") return false;
448
+ if (
449
+ node.kind === "SelectorExpr" &&
450
+ this._promotedThroughPointer(bt, node.field)
451
+ )
452
+ return false;
453
+ return this._isMapElementOperand(node.expr);
454
+ },
455
+
456
+ // Whether field or method `name` of struct type `t` is promoted through an
457
+ // embedded pointer, so selecting it dereferences and is addressable.
458
+ _promotedThroughPointer(t, name) {
459
+ return this._selectorPath(t, name, 0) === "pointer";
460
+ },
461
+
462
+ // "pointer" when `name` is reached through an embedded pointer, "value"
463
+ // when it is found without one, null when `t` has no such member.
464
+ _selectorPath(t, name, depth) {
465
+ if (!t || depth > 8) return null;
466
+ if (t.kind === "named" && t.methods?.has(name)) return "value";
467
+ const st = this.resolveType(t.kind === "named" ? t.underlying : t) ?? t;
468
+ if (st?.kind !== "struct") return null;
469
+ for (const f of st._structFields ?? [])
470
+ if (!f.embedded && f.names?.includes(name)) return "value";
471
+ for (const e of st._embeds ?? []) {
472
+ const found = this._selectorPath(
473
+ e.kind === "pointer" ? e.base : e,
474
+ name,
475
+ depth + 1,
476
+ );
477
+ if (found) return e.kind === "pointer" ? "pointer" : found;
478
+ }
479
+ return null;
480
+ },
481
+
482
+ _errMapElementAddress(node) {
483
+ return this.err(
484
+ "cannot take the address of a map element; copy it to a local variable, or store pointers in the map",
485
+ this._positioned(node),
486
+ );
487
+ },
488
+
489
+ _isWasmPackage() {
490
+ return this.target === "wasm" || this.target === "both";
491
+ },
492
+
493
+ _isFuncType(t) {
494
+ const u = this.resolveType(t?.kind === "named" ? t.underlying : t) ?? t;
495
+ return u?.kind === "func";
496
+ },
497
+
498
+ // The WASM backend represents a func value by its closure reference and has
499
+ // no box for it, so a pointer to a func variable would lose writes.
500
+ _errFuncAddress(node, type) {
501
+ return this.err(
502
+ `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`,
503
+ this._positioned(node),
504
+ );
505
+ },
506
+
209
507
  _checkAddressOfExpr(expr, scope, ot) {
508
+ if (this._isMapElementOperand(expr.operand)) {
509
+ this._errMapElementAddress(expr);
510
+ return { kind: "pointer", base: ot };
511
+ }
512
+ if (this._isScalarInteriorOperand(expr.operand, ot, scope)) {
513
+ this._errScalarInteriorAddress(expr, ot);
514
+ return { kind: "pointer", base: ot };
515
+ }
516
+ if (this._isWasmPackage() && this._isFuncType(ot)) {
517
+ this._errFuncAddress(expr, ot);
518
+ return { kind: "pointer", base: ot };
519
+ }
210
520
  if (expr.operand.kind === "Ident") {
211
521
  expr.operand._addressTaken = true;
212
522
  const sym = scope.lookup(expr.operand.name);
@@ -215,13 +525,33 @@ export const expressionCheckMethods = {
215
525
  return { kind: "pointer", base: ot };
216
526
  },
217
527
 
528
+ // Go constants have no negative zero: `-0.0` is plain 0, so it is rewritten
529
+ // to a unary plus that both backends compile to +0.
530
+ _foldNegativeZeroLiteral(expr) {
531
+ const o = expr.operand;
532
+ if (expr.op !== "-" || o?.kind !== "BasicLit") return;
533
+ if (o.litKind !== "INT" && o.litKind !== "FLOAT") return;
534
+ if (Number(String(o.value).replaceAll("_", "")) === 0) expr.op = "+";
535
+ },
536
+
218
537
  _checkUnaryExpr(expr, scope) {
538
+ this._foldNegativeZeroLiteral(expr);
539
+ if (expr.op === "*") expr.operand._typeRefOk = true;
219
540
  const ot = this.checkExpr(expr.operand, scope);
220
541
  if (expr.op === "!") return this._checkBoolUnaryOp(ot, expr);
221
542
  if (expr.op === "-" || expr.op === "+")
222
543
  return this._checkNumericUnaryOp(ot, expr);
544
+ if (expr.op === "^") return this._checkComplementUnaryOp(ot, expr);
223
545
  if (expr.op === "&") return this._checkAddressOfExpr(expr, scope, ot);
224
- if (expr.op === "*") return this._checkDeref(ot, expr);
546
+ if (expr.op === "*") {
547
+ // `(*T).M`: the operand names a type, so `*T` is the pointer type
548
+ // of a method expression, not a dereference.
549
+ if (expr.operand._isTypeRef) {
550
+ expr._isTypeRef = true;
551
+ return this._typeRefOperand(expr, { kind: "pointer", base: ot });
552
+ }
553
+ return this._checkDeref(ot, expr);
554
+ }
225
555
  return ot;
226
556
  },
227
557
 
@@ -238,51 +568,162 @@ export const expressionCheckMethods = {
238
568
  return base?.methods?.get(field) ?? baseType.methods?.get(field);
239
569
  },
240
570
 
571
+ // Instantiated generic methods are substituted copies without `_ptrRecv`,
572
+ // so read it from the generic declaration's method.
573
+ _methodExprPtrRecv(recvBase, methodType, field) {
574
+ if (!methodType) return false;
575
+ if (Object.hasOwn(methodType, "_ptrRecv")) return methodType._ptrRecv;
576
+ const generic = recvBase.typeArgs
577
+ ? this.types.get(recvBase.name)?._generic
578
+ : null;
579
+ return Boolean(generic?.methods?.get(field)?._ptrRecv);
580
+ },
581
+
582
+ // `x.M` or `x.M()` with a pointer receiver on a named scalar or slice local takes
583
+ // `&x`, so `x` must be boxed like an explicit `&x`.
584
+ _markPtrRecvAddress(expr, base, ft, scope) {
585
+ if (!ft._ptrRecv) return;
586
+ if (
587
+ !this._promotedThroughPointer(expr.expr._type, expr.field) &&
588
+ this._isMapElementOperand(expr.expr)
589
+ ) {
590
+ this._errMapElementAddress(expr.expr);
591
+ return;
592
+ }
593
+ const k = base?.kind;
594
+ if (k !== "basic" && k !== "slice" && k !== "map" && k !== "func") return;
595
+ if (this._isScalarInteriorOperand(expr.expr, expr.expr._type, scope)) {
596
+ this._errScalarInteriorAddress(expr, expr.expr._type);
597
+ return;
598
+ }
599
+ if (k === "func" && this._isWasmPackage()) {
600
+ this._errFuncAddress(expr, expr.expr._type);
601
+ return;
602
+ }
603
+ if (expr.expr.kind !== "Ident") return;
604
+ expr.expr._addressTaken = true;
605
+ const sym = scope.lookup(expr.expr.name);
606
+ if (sym) sym._addressTaken = true;
607
+ },
608
+
609
+ // Resolved per file from the import path, so a local package sharing an
610
+ // identifier with a stdlib one (or a stdlib import in another file) never matches.
611
+ _checkExcludedNamespaceMember(expr, baseType) {
612
+ const pkgPath = this._fileImports
613
+ ?.get(this._currentFile)
614
+ ?.get(baseType.name);
615
+ if (!pkgPath || pkgPath.startsWith(".")) return null;
616
+ const key = `${pkgPath}.${expr.field}`;
617
+ if (!EXCLUDED_STDLIB_MEMBERS.has(key)) return null;
618
+ return this.err(
619
+ `${baseType.name}.${expr.field} is not available: ${EXCLUDED_STDLIB_MEMBERS.get(key)}`,
620
+ expr,
621
+ );
622
+ },
623
+
624
+ _checkExcludedSelectorExpr(expr, baseType) {
625
+ if (baseType?.kind === "namespace") {
626
+ return this._checkExcludedNamespaceMember(expr, baseType);
627
+ }
628
+ const recvCheck =
629
+ baseType?.kind === "pointer"
630
+ ? (this.resolveType(baseType.base) ?? baseType.base)
631
+ : baseType;
632
+ const recvName = recvCheck?.name;
633
+ // Stdlib named types are qualified (`pkg.T` or mangled `T$$pkg`); a
634
+ // bare name is a user type and never matches the exclusion table.
635
+ if (!recvName || !/[.$]/.test(recvName)) return null;
636
+ const dName = displayTypeName(recvName);
637
+ const reason =
638
+ EXCLUDED_STDLIB_MEMBERS.get(`${dName}.${expr.field}`) ??
639
+ EXCLUDED_STDLIB_MEMBERS.get(`${recvName}.${expr.field}`);
640
+ if (!reason) return null;
641
+ return this.err(`${dName}.${expr.field} is not available: ${reason}`, expr);
642
+ },
643
+
644
+ _checkMethodExprSelector(expr, baseType) {
645
+ const recvBase = baseType.kind === "pointer" ? baseType.base : baseType;
646
+ const base = this.resolveType(
647
+ recvBase.kind === "named" ? recvBase.underlying : recvBase,
648
+ );
649
+ const methodType = this._resolveMethodExprType(recvBase, base, expr.field);
650
+ if (
651
+ this._methodExprPtrRecv(recvBase, methodType, expr.field) &&
652
+ baseType.kind !== "pointer"
653
+ ) {
654
+ const t = typeStr(baseType);
655
+ return this.err(
656
+ `invalid method expression ${t}.${expr.field} (needs pointer receiver (*${t}).${expr.field})`,
657
+ expr,
658
+ );
659
+ }
660
+ if (!methodType) return null;
661
+ expr._isMethodExpr = true;
662
+ expr._methodExprRecv = baseType;
663
+ return {
664
+ kind: "func",
665
+ params: [baseType, ...(methodType.params ?? [])],
666
+ returns: methodType.returns ?? [],
667
+ variadic: methodType.variadic,
668
+ };
669
+ },
670
+
671
+ _markMethodValueSelector(expr, baseType, ft, scope) {
672
+ const recvT =
673
+ baseType.kind === "pointer"
674
+ ? (this.resolveType(baseType.base) ?? baseType.base)
675
+ : baseType;
676
+ const base = this.resolveType(
677
+ recvT?.kind === "named" ? recvT.underlying : recvT,
678
+ );
679
+ if (base?.kind === "interface" && base.methods?.has(expr.field)) {
680
+ // `i.M` binds the interface value; the call dispatches when it runs.
681
+ expr._isMethodValue = true;
682
+ return;
683
+ }
684
+ if (!recvT || !this._isStructOrNamedMethod(recvT, base, expr.field)) return;
685
+ expr._isMethodValue = true;
686
+ if (baseType.kind === "pointer")
687
+ expr._derefRecv = !this._methodExprPtrRecv(recvT, ft, expr.field);
688
+ else this._markPtrRecvAddress(expr, base, ft, scope);
689
+ },
690
+
691
+ // Whether the identifier `name`, which resolved to `t`, names a type. A
692
+ // local variable or parameter that shares its name with a type of the
693
+ // build (the type table spans packages) is a value.
694
+ _namesType(name, t, scope) {
695
+ if (!this.types.has(name)) return false;
696
+ return (
697
+ scope.lookupScope(name) === this.globals || this.types.get(name) === t
698
+ );
699
+ },
700
+
241
701
  _checkSelectorExpr(expr, scope) {
702
+ expr.expr._typeRefOk = true;
242
703
  const baseType = this.checkExpr(expr.expr, scope);
243
704
  if (baseType?._tainted) return TAINTED_ANY;
705
+ const exclErr = this._checkExcludedSelectorExpr(expr, baseType);
706
+ if (exclErr) return exclErr;
244
707
  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
- }
708
+ const mExpr = this._checkMethodExprSelector(expr, baseType);
709
+ if (mExpr) return mExpr;
262
710
  }
263
711
  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
- }
712
+ if (ft?.kind === "func")
713
+ this._markMethodValueSelector(expr, baseType, ft, scope);
270
714
  if (
271
715
  baseType?.kind === "namespace" &&
272
716
  this.types.has(`${baseType.name}.${expr.field}`) &&
273
- !(
274
- baseType.name === "binary" &&
275
- (expr.field === "LittleEndian" || expr.field === "BigEndian")
276
- )
717
+ baseType.name !== "binary"
277
718
  ) {
278
719
  expr._isTypeRef = true;
279
720
  }
280
721
  return ft;
281
722
  },
282
723
 
283
- _checkArrayBoundsExpr(btu, bt, expr) {
724
+ _checkArrayBoundsExpr(btu, bt, expr, scope) {
284
725
  if (btu.kind !== "array" || btu.size == null) return;
285
- const idx = this._constIntValue(expr.index);
726
+ const idx = this._constIntValue(expr.index, scope);
286
727
  if (idx === null) return;
287
728
  if (idx < 0)
288
729
  this.err(`invalid array index ${idx} (index must not be negative)`, expr);
@@ -293,34 +734,189 @@ export const expressionCheckMethods = {
293
734
  );
294
735
  },
295
736
 
737
+ // `Box[int]` in an expression (the receiver of a method expression such
738
+ // as `Box[int].Get` or `(*Box[int]).Set`) names the instantiated type.
739
+ // The node becomes an InstantiationExpr so monomorphisation rewrites it.
740
+ _genericTypeRef(expr, scope) {
741
+ const base = expr.expr;
742
+ let t;
743
+ if (base?.kind === "SelectorExpr" && base.expr?.kind === "Ident") {
744
+ // `iter.Seq[int]`: a generic type behind a package qualifier.
745
+ if (scope.lookup(base.expr.name)?.kind !== "namespace") return null;
746
+ t = this.types.get(`${base.expr.name}.${base.field}`);
747
+ if (!t?._generic || t._generic.isAlias) return null;
748
+ } else {
749
+ if (base?.kind !== "Ident") return null;
750
+ t = this.types.get(base.name);
751
+ if (!t?._generic || t._generic.isAlias || scope.lookup(base.name) !== t)
752
+ return null;
753
+ }
754
+ if (expr.kind === "IndexExpr") {
755
+ if (expr.index?.kind !== "Ident") return null;
756
+ expr.kind = "InstantiationExpr";
757
+ expr.typeArgs = [{ kind: "TypeName", name: expr.index.name }];
758
+ delete expr.index;
759
+ }
760
+ const resolved = this.resolveTypeNode(expr, scope);
761
+ expr._isTypeRef = true;
762
+ return this._typeRefOperand(expr, resolved);
763
+ },
764
+
765
+ // Underlying type with chains of defined types followed through
766
+ // (`type W V` where `V` is itself a named array).
767
+ _deepUnderlying(t) {
768
+ let u = t;
769
+ for (let i = 0; u?.kind === "named" && i < 32; i++)
770
+ u = this.resolveType(u.underlying) ?? u.underlying;
771
+ return u;
772
+ },
773
+
774
+ // Core type of type parameter `tp`: the one underlying type shared by every
775
+ // type in its constraint (`~[]E`, `~map[K]V`), or null when there is none.
776
+ _coreType(tp, scope) {
777
+ if (tp?.kind !== "typeParam") return null;
778
+ const terms = this._constraintTerms(tp.constraint, new Set(), scope);
779
+ if (!terms || terms.length === 0) return null;
780
+ const unders = terms.map((t) => this._deepUnderlying(this.resolveType(t)));
781
+ const key = typeStr(unders[0]);
782
+ if (unders.some((u) => !u || typeStr(u) !== key)) return null;
783
+ return unders[0];
784
+ },
785
+
786
+ // `m == nil` on a type parameter whose core type is a map: the backends
787
+ // compare it as a map, so record the core type on the operand.
788
+ _markTypeParamMapNil(expr, lt, rt, scope) {
789
+ if (expr.op !== "==" && expr.op !== "!=") return;
790
+ const [operand, other] =
791
+ rt === NIL
792
+ ? [expr.left, lt]
793
+ : lt === NIL
794
+ ? [expr.right, rt]
795
+ : [null, null];
796
+ if (!operand || other?.kind !== "typeParam") return;
797
+ const core = this._coreType(other, scope);
798
+ if (core?.kind === "map") operand._coreType = core;
799
+ },
800
+
801
+ // Underlying type of `t` for indexing, slicing, ranging and the like; a
802
+ // type parameter stands for its core type.
803
+ _coreUnderlying(t, scope) {
804
+ if (t?.kind === "typeParam") return this._coreType(t, scope) ?? t;
805
+ return this._deepUnderlying(t);
806
+ },
807
+
808
+ // One step of underlying type, or the core type for a type parameter.
809
+ _underlyingOrCore(t, scope) {
810
+ if (t?.kind === "typeParam") return this._coreType(t, scope) ?? t;
811
+ return t?.kind === "named" ? t.underlying : t;
812
+ },
813
+
814
+ _markCompositeKey(key, kb, node, srcType) {
815
+ if (!node || !srcType || isAny(key) || kb?.kind === "interface")
816
+ return false;
817
+ // Arrays, anonymous structs and pointers are not JS property names: the
818
+ // backend encodes them by value (`sk`) or by identity (`pk`).
819
+ if (kb?.kind === "pointer") node._mapKeyEnc = "pk";
820
+ else if (kb?.kind === "array" || (key?.kind === "struct" && kb === key))
821
+ node._mapKeyEnc = "sk";
822
+ return true;
823
+ },
824
+
825
+ // A key of an interface-keyed map (`map[any]T`): the backend encodes it
826
+ // with its dynamic type, so `L("1")`, `N(1)`, `"1"` and `1` stay distinct.
827
+ // A named basic value is boxed first, exactly as in any interface
828
+ // conversion; other values are keyed as they are.
829
+ _markIfaceKey(mapType, node, srcType) {
830
+ const key = this.resolveType(mapType.key);
831
+ const kb = key?.kind === "named" ? this.resolveType(key.underlying) : key;
832
+ const src = srcType && this.resolveType(srcType);
833
+ if (node && src?.kind === "typeParam") {
834
+ // Generic code: whether the key is boxed depends on the type argument,
835
+ // so the backend decides at run time (`__ikt`).
836
+ if (key?.kind === "typeParam" || isAny(key) || kb?.kind === "interface") {
837
+ node._ifaceKeyDyn = key?.kind === "typeParam" ? "same" : "box";
838
+ node._ifaceKeyParam = src.name;
839
+ }
840
+ return;
841
+ }
842
+ if (this._markCompositeKey(key, kb, node, srcType)) return;
843
+ if (!node || !srcType || (!isAny(key) && kb?.kind !== "interface")) return;
844
+ node._ifaceKey = true;
845
+ node._ifaceKeyType = src;
846
+ this._markIfaceBox(key, this.resolveType(srcType), node);
847
+ if (node._ifaceBox === "named" || node._ifaceBox === "named_ptr") return;
848
+ node._ifaceBox = undefined;
849
+ node._ifaceSrc = undefined;
850
+ node._ifacePtrFields = undefined;
851
+ },
852
+
296
853
  _checkIndexExpr(expr, scope) {
297
- const bt = this.checkExpr(expr.expr, scope);
298
- this.checkExpr(expr.index, scope);
854
+ const typeRef = this._genericTypeRef(expr, scope);
855
+ if (typeRef) return typeRef;
856
+ const bt = this._derefArrayPtr(
857
+ expr,
858
+ "expr",
859
+ this.checkExpr(expr.expr, scope),
860
+ );
861
+ if (bt?.kind === "generic") {
862
+ const inst = this._genericFuncValueAsInstantiation(expr);
863
+ if (inst) return this._checkInstantiationExpr(inst, scope);
864
+ }
865
+ const indexType = this.checkExpr(expr.index, scope);
299
866
  if (bt?._tainted) return TAINTED_ANY;
300
867
  if (isAny(bt)) return ANY;
301
- const btu =
302
- bt.kind === "named"
303
- ? (this.resolveType(bt.underlying) ?? bt.underlying)
304
- : bt;
868
+ const btu = this._coreUnderlying(bt, scope);
305
869
  if (btu.kind === "slice" || btu.kind === "array") {
306
- this._checkArrayBoundsExpr(btu, bt, expr);
870
+ this._checkArrayBoundsExpr(btu, bt, expr, scope);
307
871
  return btu.elem;
308
872
  }
309
873
  if (btu.kind === "map") {
310
874
  expr._mapValueType = btu.value;
875
+ this.assertAssignable(btu.key, indexType, expr.index);
876
+ this._markIfaceKey(btu, expr.index, indexType);
311
877
  return btu.value;
312
878
  }
313
879
  if (btu.kind === "shared") {
314
880
  expr._sharedElem = btu.elem;
315
- const idx = this._constIntValue(expr.index);
881
+ const idx = this._constIntValue(expr.index, scope);
316
882
  if (idx !== null && idx < 0)
317
883
  this.err(`invalid index ${idx} (index must not be negative)`, expr);
318
884
  return btu.elem;
319
885
  }
320
- if (isString(bt)) return INT;
886
+ if (isString(bt)) return UINT8;
321
887
  return this.err(`Cannot index type ${typeStr(bt)}`, expr);
322
888
  },
323
889
 
890
+ // `Id[int]` used as a value reaches the typechecker as an IndexExpr (the
891
+ // parser only commits to type arguments before a call or literal). The
892
+ // node becomes an InstantiationExpr so both backends specialise it.
893
+ _genericFuncValueAsInstantiation(expr) {
894
+ const toTypeNode = (n) => {
895
+ if (n?.kind === "TypeExpr") return n.type;
896
+ // `Id[*int]`: the parser sees a dereference; on a generic function
897
+ // it is a pointer type argument.
898
+ if (n?.kind === "UnaryExpr" && n.op === "*") {
899
+ const base = toTypeNode(n.operand);
900
+ return base && { kind: "PointerType", base, _line: n._line };
901
+ }
902
+ const parts = [];
903
+ let cur = n;
904
+ while (cur?.kind === "SelectorExpr") {
905
+ parts.unshift(cur.field);
906
+ cur = cur.expr;
907
+ }
908
+ if (cur?.kind !== "Ident") return null;
909
+ parts.unshift(cur.name);
910
+ return { kind: "TypeName", name: parts.join("."), _line: n._line };
911
+ };
912
+ const typeArg = toTypeNode(expr.index);
913
+ if (!typeArg) return null;
914
+ expr.kind = "InstantiationExpr";
915
+ expr.typeArgs = [typeArg];
916
+ delete expr.index;
917
+ return expr;
918
+ },
919
+
324
920
  // copy(dst, src): element types must be identical when either side is a
325
921
  // shared buffer (the wasm emitter uses one element type for both sides).
326
922
  _checkBuiltinCopy(e, at) {
@@ -346,15 +942,33 @@ export const expressionCheckMethods = {
346
942
  return INT;
347
943
  },
348
944
 
945
+ // Go's shorthand for pointers to arrays: `p[i]`, `p[lo:hi]`, `len(p)` and
946
+ // `range p` mean `(*p)[i]` and so on. The operand is rewritten to an
947
+ // explicit dereference so every backend sees the array.
948
+ _derefArrayPtr(holder, key, t) {
949
+ if (t?.kind !== "pointer") return t;
950
+ const base = t.base?.kind === "named" ? this.resolveType(t.base) : t.base;
951
+ const under = base?.kind === "named" ? base.underlying : base;
952
+ if (under?.kind !== "array") return t;
953
+ holder[key] = { kind: "UnaryExpr", op: "*", operand: holder[key] };
954
+ holder[key]._type = t.base;
955
+ return t.base;
956
+ },
957
+
349
958
  _checkSliceExpr(expr, scope) {
350
- const bt = this.checkExpr(expr.expr, scope);
959
+ const bt = this._derefArrayPtr(
960
+ expr,
961
+ "expr",
962
+ this.checkExpr(expr.expr, scope),
963
+ );
351
964
  if (expr.low) this.checkExpr(expr.low, scope);
352
965
  if (expr.high) this.checkExpr(expr.high, scope);
353
966
  if (expr.max) this.checkExpr(expr.max, scope);
354
967
  if (bt?._tainted) return TAINTED_ANY;
355
968
  if (isAny(bt)) return ANY;
356
- if (bt.kind === "slice" || bt.kind === "array")
357
- return { kind: "slice", elem: bt.elem };
969
+ const btu = this._coreUnderlying(bt, scope);
970
+ if (btu.kind === "slice" || btu.kind === "array")
971
+ return { kind: "slice", elem: btu.elem };
358
972
  if (isString(bt)) return STRING;
359
973
  if (sharedInfo(bt))
360
974
  return this.err(
@@ -387,13 +1001,16 @@ export const expressionCheckMethods = {
387
1001
  }
388
1002
  const inner = new Scope(scope);
389
1003
  for (const p of expr.params)
390
- inner.define(p.name, p.type ? this.resolveTypeNode(p.type, scope) : ANY);
1004
+ inner.define(p.name, p.type ? this._paramType(p, scope) : ANY);
391
1005
  const ret = expr.returnType
392
1006
  ? this.resolveTypeNode(expr.returnType, scope)
393
1007
  : VOID;
1008
+ const bodyRet = this._injectNamedReturns(expr.returnType, inner)
1009
+ ? { ...ret, _hasNamedReturns: true }
1010
+ : ret;
394
1011
  const savedDefer = this._deferCount;
395
1012
  this._deferCount = 0;
396
- this.checkBlock(expr.body, inner, ret);
1013
+ this.checkBlock(expr.body, inner, bodyRet);
397
1014
  this._reportUnused(inner, expr);
398
1015
  if (this._deferCount > 0) expr.body._hasDefer = true;
399
1016
  this._deferCount = savedDefer;
@@ -404,6 +1021,7 @@ export const expressionCheckMethods = {
404
1021
  kind: "func",
405
1022
  params: paramTypes,
406
1023
  returns: [ret],
1024
+ variadic: expr.params.at(-1)?.variadic === true,
407
1025
  async: expr.async,
408
1026
  };
409
1027
  },
@@ -429,6 +1047,19 @@ export const expressionCheckMethods = {
429
1047
  );
430
1048
  },
431
1049
 
1050
+ // Struct types with identical field names and types convert to each other.
1051
+ _sameStructFields(a, b) {
1052
+ if (a?.kind !== "struct" || b?.kind !== "struct") return false;
1053
+ if (a.fields.size !== b.fields.size) return false;
1054
+ const bf = [...b.fields];
1055
+ let i = 0;
1056
+ for (const [name, type] of a.fields) {
1057
+ const [bn, bt] = bf[i++];
1058
+ if (name !== bn || typeStr(type) !== typeStr(bt)) return false;
1059
+ }
1060
+ return true;
1061
+ },
1062
+
432
1063
  _isInvalidArrayConv(tgt, src, srcType) {
433
1064
  return tgt?.kind === "array" && src?.kind !== "array" && !isAny(srcType);
434
1065
  },
@@ -436,6 +1067,9 @@ export const expressionCheckMethods = {
436
1067
  _checkTypeConversion(expr, scope) {
437
1068
  const srcType = this.checkExpr(expr.expr, scope);
438
1069
  const targetType = this.resolveTypeNode(expr.targetType, scope);
1070
+ // `any(x)` / `error(x)`: box x as an implicit conversion would.
1071
+ this._markIfaceBox(targetType, srcType, expr.expr);
1072
+ this._rejectNilConversion(srcType, targetType, expr);
439
1073
  const srcResolved =
440
1074
  srcType?.kind === "named" ? srcType.underlying : srcType;
441
1075
  const tgtResolved =
@@ -456,6 +1090,8 @@ export const expressionCheckMethods = {
456
1090
  expr,
457
1091
  );
458
1092
  }
1093
+ if (tgtResolved?.kind === "slice")
1094
+ this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
459
1095
  if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
460
1096
  this._checkElemTypeCompatible(
461
1097
  tgtResolved.elem,
@@ -475,12 +1111,42 @@ export const expressionCheckMethods = {
475
1111
  return targetType;
476
1112
  },
477
1113
 
1114
+ // `int(nil)`: only a type with a nil value converts from `nil`.
1115
+ _rejectNilConversion(srcType, targetType, expr) {
1116
+ if (!isNil(srcType) || !targetType || isAny(targetType)) return;
1117
+ if (targetType.kind === "typeParam") return;
1118
+ const kind = this._deepUnderlying(targetType)?.kind;
1119
+ if (!kind || NILABLE_KINDS.has(kind)) return;
1120
+ this.err(`cannot convert nil to ${typeStr(targetType)}`, expr);
1121
+ },
1122
+
1123
+ // Pointers convert when their base types share an underlying type.
1124
+ _pointerConversionOk(srcPtr, tgtPtr, srcType, targetType, expr) {
1125
+ const sBase = this._deepUnderlying(srcPtr.base);
1126
+ const tBase = this._deepUnderlying(tgtPtr.base);
1127
+ if (typeStr(sBase) === typeStr(tBase)) return true;
1128
+ if (!this._sameStructFields(sBase, tBase)) return false;
1129
+ // WebAssembly struct types are nominal: the two pointers could not
1130
+ // share one object.
1131
+ if (this.target === "wasm" || this.target === "both") {
1132
+ this.recordBlocker("pointer conversion between struct types");
1133
+ this.err(
1134
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} in a wasm package: pointers to distinct struct types cannot share storage; keep this code in a js package`,
1135
+ expr,
1136
+ );
1137
+ }
1138
+ return true;
1139
+ },
1140
+
478
1141
  _checkConversionValidity(srcType, targetType, expr) {
479
1142
  if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
480
1143
  const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
481
1144
  const tgtResolved =
482
1145
  targetType.kind === "named" ? targetType.underlying : targetType;
483
1146
 
1147
+ // `nil` converts to any type that has a nil value.
1148
+ if (isNil(srcType) && NILABLE_KINDS.has(tgtResolved?.kind)) return;
1149
+
484
1150
  if (isNumeric(targetType) && isComplex(srcType)) {
485
1151
  this.err(
486
1152
  `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
@@ -495,6 +1161,8 @@ export const expressionCheckMethods = {
495
1161
  );
496
1162
  return;
497
1163
  }
1164
+ if (tgtResolved?.kind === "slice")
1165
+ this._rejectSeqAsSlice(srcType, expr.args?.[0], "a slice conversion");
498
1166
  if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
499
1167
  this._checkElemTypeCompatible(
500
1168
  tgtResolved.elem,
@@ -513,9 +1181,15 @@ export const expressionCheckMethods = {
513
1181
  return;
514
1182
  }
515
1183
  if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
1184
+ if (this._sameStructFields(srcResolved, tgtResolved)) return;
516
1185
  if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
517
1186
  if (isString(srcResolved) && isString(tgtResolved)) return;
518
1187
  if (isBool(srcResolved) && isBool(tgtResolved)) return;
1188
+ if (
1189
+ (isComplex(srcResolved) || isNumeric(srcResolved)) &&
1190
+ isComplex(tgtResolved)
1191
+ )
1192
+ return;
519
1193
  if (
520
1194
  isString(srcResolved) &&
521
1195
  tgtResolved?.kind === "slice" &&
@@ -535,17 +1209,18 @@ export const expressionCheckMethods = {
535
1209
  srcResolved.elem?.name === "int32")))
536
1210
  )
537
1211
  return;
538
- if (srcResolved?.kind === "pointer" && tgtResolved?.kind === "pointer") {
539
- const sBase =
540
- srcResolved.base?.kind === "named"
541
- ? srcResolved.base.underlying
542
- : srcResolved.base;
543
- const tBase =
544
- tgtResolved.base?.kind === "named"
545
- ? tgtResolved.base.underlying
546
- : tgtResolved.base;
547
- if (typeStr(sBase) === typeStr(tBase)) return;
548
- }
1212
+ if (
1213
+ srcResolved?.kind === "pointer" &&
1214
+ tgtResolved?.kind === "pointer" &&
1215
+ this._pointerConversionOk(
1216
+ srcResolved,
1217
+ tgtResolved,
1218
+ srcType,
1219
+ targetType,
1220
+ expr,
1221
+ )
1222
+ )
1223
+ return;
549
1224
  this.err(
550
1225
  `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
551
1226
  expr,
@@ -555,6 +1230,7 @@ export const expressionCheckMethods = {
555
1230
  _checkTypeAssertExpr(expr, scope) {
556
1231
  const srcType = this.checkExpr(expr.expr, scope);
557
1232
  const targetType = this.resolveTypeNode(expr.type, scope);
1233
+ this._markDynamicType(expr.type, targetType);
558
1234
 
559
1235
  // In Go, the source of a type assertion must be an interface type.
560
1236
  // We allow `any` (which is GoFront's basic any) and interface types.
@@ -589,19 +1265,33 @@ export const expressionCheckMethods = {
589
1265
  },
590
1266
 
591
1267
  _checkRangeExpr(expr, scope) {
592
- const collType = this.checkExpr(expr.expr, scope);
593
- const resolved =
594
- collType?.kind === "named" ? collType.underlying : collType;
1268
+ const collType = this._derefArrayPtr(
1269
+ expr,
1270
+ "expr",
1271
+ this.checkExpr(expr.expr, scope),
1272
+ );
1273
+ const resolved = this._underlyingOrCore(collType, scope);
1274
+ // Backends range over the core type of a type parameter.
1275
+ if (collType?.kind === "typeParam" && resolved !== collType)
1276
+ expr.expr._coreType = resolved;
595
1277
  return this._rangeCollTypeTuple(resolved, collType);
596
1278
  },
597
1279
 
598
1280
  _checkInstantiationExpr(expr, scope) {
1281
+ const typeRef = this._genericTypeRef(expr, scope);
1282
+ if (typeRef) return typeRef;
599
1283
  // Generic instantiation: Foo[int] or Foo[int, string]
600
1284
  const baseType = this.checkExpr(expr.expr, scope);
601
1285
  if (baseType?.kind === "generic") {
602
1286
  const typeArgs = expr.typeArgs.map((ta) =>
603
1287
  this.resolveTypeNode(ta, scope),
604
1288
  );
1289
+ if (typeArgs.length > baseType.typeParams.length) {
1290
+ return this.err(
1291
+ `got ${typeArgs.length} type arguments but ${baseType.name ?? "function"} has ${baseType.typeParams.length} type parameters`,
1292
+ expr,
1293
+ );
1294
+ }
605
1295
  // Check constraints
606
1296
  for (
607
1297
  let i = 0;
@@ -610,7 +1300,10 @@ export const expressionCheckMethods = {
610
1300
  ) {
611
1301
  const tp = baseType.typeParams[i];
612
1302
  const constraint = tp.constraint
613
- ? this.resolveTypeNode(tp.constraint, scope)
1303
+ ? this.resolveTypeNode(
1304
+ tp.constraint,
1305
+ this._typeArgScope(baseType.typeParams, typeArgs, scope),
1306
+ )
614
1307
  : null;
615
1308
  if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
616
1309
  }
@@ -625,9 +1318,28 @@ export const expressionCheckMethods = {
625
1318
  // rewrite the node in place into an IndexExpr and check that instead.
626
1319
  const indexExpr = this._instantiationAsIndex(expr, baseType);
627
1320
  if (indexExpr) return this._checkIndexExpr(indexExpr, scope);
1321
+ if (baseType?.kind === "func" && !this._isPseudoGenericStdFunc(baseType))
1322
+ return this.err(
1323
+ `cannot instantiate ${this._calleeName(expr.expr)}: not a generic function`,
1324
+ expr,
1325
+ );
628
1326
  return baseType;
629
1327
  },
630
1328
 
1329
+ // Emitter-backed stdlib helpers (`slices.Sort`, `maps.Keys`) take `any`
1330
+ // parameters in place of type parameters and accept (and ignore) explicit
1331
+ // type arguments; a stdlib function with concrete parameters does not.
1332
+ _isPseudoGenericStdFunc(fnType) {
1333
+ return (fnType.params ?? []).some((p) => isAny(p));
1334
+ },
1335
+
1336
+ _calleeName(node) {
1337
+ if (node?.kind === "Ident") return node.name;
1338
+ if (node?.kind === "SelectorExpr")
1339
+ return `${this._calleeName(node.expr)}.${node.field}`;
1340
+ return "expression";
1341
+ },
1342
+
631
1343
  _instantiationAsIndex(expr, baseType) {
632
1344
  if (!baseType || expr.typeArgs.length !== 1) return null;
633
1345
  const ta = expr.typeArgs[0];
@@ -652,32 +1364,97 @@ export const expressionCheckMethods = {
652
1364
  return expr;
653
1365
  },
654
1366
 
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);
1367
+ // `int(K)`, `uint8(K)`, `MyInt(K)`: a conversion to an integer type.
1368
+ _isIntConversion(expr) {
1369
+ if (expr.args?.length !== 1) return false;
1370
+ const target = expr._conversionTargetType;
1371
+ if (expr._isTypeConversion && target)
1372
+ return isInteger(target.kind === "named" ? target.underlying : target);
1373
+ return (
1374
+ expr.func?.kind === "Ident" && SIZED_INT_NAMES.includes(expr.func.name)
1375
+ );
1376
+ },
1377
+
1378
+ // Keyed array literal indices (Go spec): each key must be a non-negative
1379
+ // integer constant, and no index may appear twice. The resolved index is
1380
+ // stored on the KeyValueExpr as `_index` for compositeLitIndices.
1381
+ _checkArrayLitKeys(expr, scope) {
1382
+ const seen = new Set();
1383
+ let next = 0;
1384
+ let unknown = false;
1385
+ for (const elem of expr.elems) {
1386
+ if (elem.kind === "KeyValueExpr") {
1387
+ this.checkExpr(elem.key, scope);
1388
+ const idx = this._constIntValue(elem.key, scope);
1389
+ const text = constKeyText(elem.key);
1390
+ if (idx === null && this._isConstExpr(elem.key, scope)) {
1391
+ // A constant key the folder cannot evaluate (`len(arr)`) keeps
1392
+ // the literal/positional index of compositeLitIndices; later
1393
+ // indices are not duplicate-checked.
1394
+ unknown = true;
1395
+ next++;
1396
+ continue;
1397
+ }
1398
+ if (idx === null)
1399
+ this.err(`index ${text} must be integer constant`, elem.key);
1400
+ else if (idx < 0)
1401
+ this.err(
1402
+ `index ${text} must be non-negative integer constant`,
1403
+ elem.key,
1404
+ );
1405
+ else next = idx;
1406
+ elem._index = next;
1407
+ }
1408
+ if (!unknown && seen.has(next))
1409
+ this.err(`duplicate index ${next} in array or slice literal`, elem);
1410
+ seen.add(next);
1411
+ next++;
676
1412
  }
677
- if (expr.kind === "ParenExpr") {
678
- return this._constIntValue(expr.expr, scope);
1413
+ },
1414
+
1415
+ // Whether `e` is a constant expression (whose value the folder may not
1416
+ // know): literals, constants, operators, conversions and builtin calls.
1417
+ _isConstExpr(e, scope) {
1418
+ switch (e?.kind) {
1419
+ case "BasicLit":
1420
+ return true;
1421
+ case "ParenExpr":
1422
+ return this._isConstExpr(e.expr, scope);
1423
+ case "UnaryExpr":
1424
+ return (
1425
+ ["-", "+", "^", "!"].includes(e.op) &&
1426
+ this._isConstExpr(e.operand, scope)
1427
+ );
1428
+ case "BinaryExpr":
1429
+ return (
1430
+ this._isConstExpr(e.left, scope) && this._isConstExpr(e.right, scope)
1431
+ );
1432
+ case "Ident":
1433
+ return !!(scope ?? this.globals)
1434
+ .lookupScope(e.name)
1435
+ ?._consts.has(e.name);
1436
+ case "SelectorExpr": {
1437
+ // Package members: constant unless the package says otherwise.
1438
+ const ns =
1439
+ e.expr?.kind === "Ident"
1440
+ ? (scope ?? this.globals).lookup(e.expr.name)
1441
+ : null;
1442
+ if (ns?.kind !== "namespace") return false;
1443
+ return ns._consts ? ns._consts.has(e.field) : true;
1444
+ }
1445
+ case "TypeConversion":
1446
+ return this._isConstExpr(e.expr, scope);
1447
+ case "CallExpr":
1448
+ if (e._isTypeConversion || this._isIntConversion(e))
1449
+ return this._isConstExpr(e.args?.[0], scope);
1450
+ return (
1451
+ e.func?.kind === "Ident" &&
1452
+ CONST_BUILTINS.has(e.func.name) &&
1453
+ (scope ?? this.globals).lookup(e.func.name)?.kind === "builtin"
1454
+ );
1455
+ default:
1456
+ return false;
679
1457
  }
680
- return null;
681
1458
  },
682
1459
 
683
1460
  _resolveCallArgTypes(expr, scope) {
@@ -705,11 +1482,125 @@ export const expressionCheckMethods = {
705
1482
  if (!resolved) return null;
706
1483
  fnType = resolved;
707
1484
  }
708
- return fnType;
1485
+ return this._completePartialInstantiation(expr, argTypes, scope) ?? fnType;
1486
+ },
1487
+
1488
+ // `F[int](x)` with fewer explicit type arguments than type parameters:
1489
+ // infer the rest from the call arguments and record the full list.
1490
+ _completePartialInstantiation(expr, argTypes, scope) {
1491
+ const inst = expr.func;
1492
+ const generic = inst?.kind === "InstantiationExpr" && inst._genericFnType;
1493
+ if (!generic || inst._typeArgs.length >= generic.typeParams.length)
1494
+ return null;
1495
+ const typeArgs = this.inferTypeArgs(
1496
+ generic,
1497
+ this._inferArgTypes(expr, argTypes),
1498
+ inst._typeArgs,
1499
+ scope,
1500
+ );
1501
+ if (!typeArgs) {
1502
+ this.err(
1503
+ `Cannot infer type arguments for generic function '${generic.name}'`,
1504
+ expr,
1505
+ );
1506
+ return null;
1507
+ }
1508
+ inst._typeArgs = typeArgs;
1509
+ if (inst.expr) inst.expr._typeArgs = typeArgs;
1510
+ return this.instantiateGenericFunc(generic, typeArgs);
1511
+ },
1512
+
1513
+ // `T(x)` with T a type parameter: only a numeric x converts (the constraint
1514
+ // decides the target at instantiation).
1515
+ _checkTypeParamConversion(tp, argTypes, expr, scope) {
1516
+ expr._isTypeConversion = true;
1517
+ expr._conversionTargetType = tp;
1518
+ if (expr.args.length !== 1) {
1519
+ return this.err(
1520
+ `cannot convert ${expr.args.length} arguments to type ${typeStr(tp)}`,
1521
+ expr,
1522
+ );
1523
+ }
1524
+ // A constraint with a core type (`~[]E`) converts as that type does.
1525
+ const core = this._coreType(tp, scope);
1526
+ if (core) {
1527
+ const src = argTypes[0];
1528
+ const nilOk = ["slice", "map", "pointer", "func", "chan", "interface"];
1529
+ if (src === NIL || (src?.kind === "basic" && src.name === "nil")) {
1530
+ if (!nilOk.includes(core.kind))
1531
+ this.err(`cannot convert nil to ${typeStr(tp)}`, expr);
1532
+ } else if (src?.kind !== "typeParam") {
1533
+ this._checkConversionValidity(src, core, expr);
1534
+ }
1535
+ return tp;
1536
+ }
1537
+ if (
1538
+ isNumeric(argTypes[0]) &&
1539
+ !this._constraintHasTypeSet(tp.constraint, scope)
1540
+ )
1541
+ return this.err(
1542
+ `cannot convert ${typeStr(argTypes[0])} to type ${typeStr(tp)}: ${typeStr(tp)} is not constrained to a set of convertible types`,
1543
+ expr,
1544
+ );
1545
+ if (
1546
+ !isNumeric(argTypes[0]) &&
1547
+ !isAny(argTypes[0]) &&
1548
+ argTypes[0]?.kind !== "typeParam"
1549
+ )
1550
+ this.err(
1551
+ `cannot convert ${typeStr(argTypes[0])} to ${typeStr(tp)}`,
1552
+ expr,
1553
+ );
1554
+ return tp;
1555
+ },
1556
+
1557
+ // A conversion to a type parameter needs a constraint that lists types
1558
+ // (`~int | ~float64`, or an interface embedding such a union); `any`,
1559
+ // `comparable` and method-only constraints allow every type argument.
1560
+ _constraintHasTypeSet(constraint, scope = this.globals) {
1561
+ if (!constraint) return true;
1562
+ if (constraint.kind === "UnionConstraint") return true;
1563
+ // Receiver type parameters keep their constraint as an AST node.
1564
+ const c = this.resolveType(
1565
+ constraint.kind === "TypeName" || constraint.kind === "InterfaceType"
1566
+ ? this.resolveTypeNode(constraint, scope)
1567
+ : constraint,
1568
+ );
1569
+ const base = c?.kind === "named" ? this.resolveType(c.underlying) : c;
1570
+ if (base?.kind !== "interface") return false;
1571
+ if (base.unionConstraint) return true;
1572
+ return (base._typeSetEmbeds ?? []).some((e) =>
1573
+ this._constraintHasTypeSet(e, scope),
1574
+ );
1575
+ },
1576
+
1577
+ // A direct call of a `//gofront:native` function is marked `_native` so a
1578
+ // backend may substitute host code. A function used as a value (`f :=
1579
+ // str.Index; f(a, b)`) is not a direct reference and calls the Go body.
1580
+ _markNativeCall(expr, fnType, scope) {
1581
+ const callee =
1582
+ expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
1583
+ const native = fnType?.native ?? fnType?.underlying?.native ?? null;
1584
+ const direct =
1585
+ callee.kind === "Ident"
1586
+ ? scope.lookupScope(callee.name) === this.globals &&
1587
+ Boolean(this._funcDeclNames?.has(callee.name))
1588
+ : callee.kind === "SelectorExpr" && Boolean(callee._stdName);
1589
+ expr._native = native && direct ? native : null;
709
1590
  },
710
1591
 
711
1592
  checkCall(expr, scope) {
1593
+ expr.func._typeRefOk = true;
712
1594
  let fnType = this.checkExpr(expr.func, scope);
1595
+ // `slices.Sort[[]int](xs)` on an emitter-backed (non-generic) stdlib
1596
+ // function: the type arguments are inferred, so call it as `slices.Sort(xs)`.
1597
+ if (
1598
+ expr.func.kind === "InstantiationExpr" &&
1599
+ !expr.func._genericFnType &&
1600
+ expr.func.expr?.kind === "SelectorExpr" &&
1601
+ fnType?.kind === "func"
1602
+ )
1603
+ expr.func = expr.func.expr;
713
1604
  this._checkSharedAllocSite(expr, scope);
714
1605
  this._checkBoundaryCallInLoop(expr, scope);
715
1606
  if (fnType.kind === "builtin") {
@@ -721,14 +1612,23 @@ export const expressionCheckMethods = {
721
1612
  return this.checkBuiltin(fnType.name, expr, argTypes, scope);
722
1613
  }
723
1614
 
1615
+ this._markNativeCall(expr, fnType, scope);
724
1616
  const argTypes = this._resolveCallArgTypes(expr, scope);
725
1617
 
726
1618
  if (fnType?._tainted) return TAINTED_ANY;
1619
+ // `any(x)` / `error(x)` / `Iface(x)`: box x as an implicit conversion would.
1620
+ if (expr.func._isTypeRef && expr.args.length === 1 && argTypes[0])
1621
+ this._markIfaceBox(fnType, argTypes[0], expr.args[0]);
727
1622
  if (isAny(fnType)) return ANY;
728
1623
 
729
1624
  // Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
730
1625
  // The callee is a type name, not a callable variable.
731
- if (fnType.kind === "named" && expr.func._isTypeRef) {
1626
+ if (
1627
+ (fnType.kind === "named" ||
1628
+ fnType.kind === "pointer" ||
1629
+ fnType.kind === "basic") &&
1630
+ expr.func._isTypeRef
1631
+ ) {
732
1632
  expr._isTypeConversion = true;
733
1633
  expr._conversionTargetType = fnType;
734
1634
  if (expr.args.length !== 1) {
@@ -742,6 +1642,10 @@ export const expressionCheckMethods = {
742
1642
  return fnType;
743
1643
  }
744
1644
 
1645
+ if (fnType.kind === "typeParam" && expr.func._isTypeRef) {
1646
+ return this._checkTypeParamConversion(fnType, argTypes, expr, scope);
1647
+ }
1648
+
745
1649
  fnType = this._resolveCallFnType(fnType, argTypes, expr, scope);
746
1650
  if (!fnType) return ANY;
747
1651
  if (fnType.kind !== "func") {
@@ -750,13 +1654,6 @@ export const expressionCheckMethods = {
750
1654
 
751
1655
  this._checkCallArgs(fnType, argTypes, expr, scope);
752
1656
 
753
- // Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
754
- // how their result derives from the first argument.
755
- if (fnType._derivedReturn) {
756
- const derived = this._derivedReturnType(fnType._derivedReturn, argTypes);
757
- if (derived) return derived;
758
- }
759
-
760
1657
  const ret = fnType.returns;
761
1658
  if (!ret || ret.length === 0) return VOID;
762
1659
  if (ret.length === 1) return ret[0];
@@ -806,27 +1703,34 @@ export const expressionCheckMethods = {
806
1703
  );
807
1704
  },
808
1705
 
809
- _derivedReturnType(mode, argTypes) {
810
- const arg = argTypes[0];
811
- const under = arg?.kind === "named" ? arg.underlying : arg;
812
- switch (mode) {
813
- case "arg0":
814
- return under ? arg : null;
815
- case "elemOfArg0":
816
- 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
- default:
824
- return null;
825
- }
1706
+ // Argument types for inference: a spread argument `xs...` stands for its
1707
+ // elements, which is what the variadic parameter type describes.
1708
+ _inferArgTypes(expr, argTypes) {
1709
+ return argTypes.map((t, i) => {
1710
+ if (!expr.args?.[i]?._spread) return t;
1711
+ const u = this.resolveType(t?.kind === "named" ? t.underlying : t);
1712
+ return u?.kind === "slice" ? u.elem : t;
1713
+ });
1714
+ },
1715
+
1716
+ // Scope where each type parameter name stands for its type argument, so a
1717
+ // constraint that mentions another parameter (`S ~[]E`) resolves.
1718
+ _typeArgScope(typeParams, typeArgs, scope) {
1719
+ const out = new Scope(scope);
1720
+ typeParams.forEach((tp, i) => {
1721
+ if (typeArgs[i])
1722
+ out.define(tp.name, { kind: "aliasArg", type: typeArgs[i] });
1723
+ });
1724
+ return out;
826
1725
  },
827
1726
 
828
1727
  _resolveGenericFnType(fnType, argTypes, expr, scope) {
829
- const typeArgs = this.inferTypeArgs(fnType, argTypes);
1728
+ const typeArgs = this.inferTypeArgs(
1729
+ fnType,
1730
+ this._inferArgTypes(expr, argTypes),
1731
+ [],
1732
+ scope,
1733
+ );
830
1734
  if (!typeArgs) {
831
1735
  this.err(
832
1736
  `Cannot infer type arguments for generic function '${fnType.name}'`,
@@ -838,7 +1742,10 @@ export const expressionCheckMethods = {
838
1742
  for (let i = 0; i < fnType.typeParams.length && i < typeArgs.length; i++) {
839
1743
  const tp = fnType.typeParams[i];
840
1744
  const constraint = tp.constraint
841
- ? this.resolveTypeNode(tp.constraint, scope)
1745
+ ? this.resolveTypeNode(
1746
+ tp.constraint,
1747
+ this._typeArgScope(fnType.typeParams, typeArgs, scope),
1748
+ )
842
1749
  : null;
843
1750
  if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
844
1751
  }
@@ -865,8 +1772,31 @@ export const expressionCheckMethods = {
865
1772
  );
866
1773
  },
867
1774
 
1775
+ // WASM packs a variadic call's trailing arguments into a slice, which it
1776
+ // cannot do for `f(g())` yet. Emitter-backed stdlib calls (`fmt.X`) are
1777
+ // not packed; GoFront and Go-source package functions are.
1778
+ _checkMultiForwardVariadic(fnType, expr) {
1779
+ if (!expr._multiForward || !fnType.variadic) return;
1780
+ if (this.target !== "wasm" && this.target !== "both") return;
1781
+ const ns =
1782
+ expr.func?.kind === "SelectorExpr" ? expr.func.expr?._type : null;
1783
+ const emitterBacked =
1784
+ ns?.kind === "namespace" &&
1785
+ !ns._gofront &&
1786
+ !ns._mangle?.has(expr.func.field);
1787
+ if (emitterBacked) return;
1788
+ // The generated wrapper of a Print-family call takes the results as
1789
+ // separate parameters (src/lower/fmt-formatters.js).
1790
+ if (ns?.name === "fmt" && FMT_PRINT_FUNCS.has(expr.func.field)) return;
1791
+ this.err(
1792
+ "passing multiple return values to a variadic function is not supported in wasm packages; assign them to variables first",
1793
+ expr,
1794
+ );
1795
+ },
1796
+
868
1797
  _checkCallArgs(fnType, argTypes, expr, scope = null) {
869
1798
  this._checkArgCount(fnType, argTypes, expr);
1799
+ this._checkMultiForwardVariadic(fnType, expr);
870
1800
  const maxLen = fnType.variadic
871
1801
  ? argTypes.length
872
1802
  : Math.min(fnType.params.length, argTypes.length);
@@ -875,37 +1805,63 @@ export const expressionCheckMethods = {
875
1805
  fnType.variadic && i >= fnType.params.length - 1
876
1806
  ? fnType.params.length - 1
877
1807
  : i;
878
- if (expr.args[i]?._spread) continue;
1808
+ if (expr.args[i]?._spread) {
1809
+ this._rejectSeqAsSlice(argTypes[i], expr.args[i], "a spread argument");
1810
+ continue;
1811
+ }
879
1812
  const targetParam = fnType.params[paramIdx];
880
1813
  const node = expr._multiForward ? null : expr.args[i];
881
- this.assertAssignable(targetParam, argTypes[i], node);
1814
+ this.assertAssignable(targetParam, argTypes[i], node, "argument");
882
1815
  }
883
1816
 
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
- }
1817
+ this._checkSimdLaneArgs(expr, scope);
1818
+ },
1819
+
1820
+ // Lane, WithLane, Swizzle and Shuffle take constant lane indices.
1821
+ _checkSimdLaneArgs(expr, scope) {
1822
+ if (expr.func?.kind !== "SelectorExpr") return;
1823
+ const recv = expr.func.expr?._type;
1824
+ const field = expr.func.field;
1825
+ let first = 0;
1826
+ let count = 0;
1827
+ let max = 3;
1828
+ if (recv?.kind === "namespace" && recv.members?.Mask32x4) {
1829
+ if (field !== "Shuffle") return;
1830
+ first = 2;
1831
+ count = 4;
1832
+ max = 7;
1833
+ } else if (this._isSimdType(recv)) {
1834
+ if (field === "Lane" || field === "WithLane") count = 1;
1835
+ else if (field === "Swizzle") count = 4;
1836
+ else return;
1837
+ } else {
1838
+ return;
1839
+ }
1840
+ for (let i = first; i < first + count; i++) {
1841
+ const arg = expr.args[i];
1842
+ if (!arg) continue;
1843
+ const idx = this._constIntValue(arg, scope);
1844
+ if (idx !== null) arg._constInt = idx;
1845
+ if (idx === null || idx < 0 || idx > max) {
1846
+ this.err(
1847
+ `lane index must be a constant integer in range [0, ${max}]`,
1848
+ arg,
1849
+ );
905
1850
  }
906
1851
  }
907
1852
  },
908
1853
 
1854
+ _isSimdType(t) {
1855
+ return (
1856
+ t?.name === "simd.F32x4" ||
1857
+ t?.name === "F32x4" ||
1858
+ t?.name === "simd.Mask32x4" ||
1859
+ t?.name === "Mask32x4" ||
1860
+ t?.underlying?.kind === "simd" ||
1861
+ t?.kind === "simd"
1862
+ );
1863
+ },
1864
+
909
1865
  checkBuiltin(name, expr, argTypes, scope) {
910
1866
  const gen = BUILTIN_CHECK[name];
911
1867
  return gen ? gen(this, name, expr, argTypes, scope) : ANY;
@@ -923,14 +1879,32 @@ export const expressionCheckMethods = {
923
1879
  },
924
1880
 
925
1881
  _checkBuiltinLen(name, expr, argTypes) {
926
- if (name === "len") {
927
- const size = this._constLenFromType(argTypes[0]);
928
- if (size != null) expr._constLen = size;
929
- } else if (sharedInfo(argTypes[0])) {
930
- this.err(
931
- `cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
932
- expr,
933
- );
1882
+ this._rejectSeqAsSlice(argTypes[0], expr.args?.[0], `${name}()`);
1883
+ const operand = expr.args?.[0];
1884
+ if (expr.args?.length === 1)
1885
+ argTypes[0] = this._derefArrayPtr(expr.args, 0, argTypes[0]);
1886
+ // `len` and `cap` of an array (or pointer to one) are its length.
1887
+ const size = this._constLenFromType(argTypes[0]);
1888
+ if (size != null) {
1889
+ expr._constLen = size;
1890
+ // The operand is not dereferenced: a nil `*[N]T` has length N.
1891
+ if (expr.args.length === 1) expr.args[0] = operand;
1892
+ }
1893
+ if (name === "cap") {
1894
+ if (
1895
+ (argTypes[0]?.underlying ?? argTypes[0])?.kind === "map" ||
1896
+ isString(argTypes[0])
1897
+ ) {
1898
+ this.err(
1899
+ `invalid argument: ${exprText(expr.args?.[0])} (${operandKind(expr.args?.[0], typeStr(argTypes[0]), false)}) for cap`,
1900
+ expr,
1901
+ );
1902
+ } else if (sharedInfo(argTypes[0])) {
1903
+ this.err(
1904
+ `cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
1905
+ expr,
1906
+ );
1907
+ }
934
1908
  }
935
1909
  return INT;
936
1910
  },
@@ -952,6 +1926,17 @@ export const expressionCheckMethods = {
952
1926
  }
953
1927
  if (crt.kind === "untyped" && cit.kind === "untyped")
954
1928
  return UNTYPED_COMPLEX;
1929
+ if (crt.name === "float32" && cit.name === "float32") return COMPLEX64;
1930
+ if (crt.name === "float64" && cit.name === "float64") return COMPLEX128;
1931
+ if (crt.name === "float32" || cit.name === "float32") {
1932
+ if (crt.name === "float64" || cit.name === "float64") {
1933
+ this.err(
1934
+ `invalid complex call: mismatched float types (${typeStr(crt)} and ${typeStr(cit)})`,
1935
+ expr,
1936
+ );
1937
+ }
1938
+ return COMPLEX64;
1939
+ }
955
1940
  return COMPLEX128;
956
1941
  },
957
1942
 
@@ -966,6 +1951,7 @@ export const expressionCheckMethods = {
966
1951
  return ANY;
967
1952
  }
968
1953
  if (zt.kind === "untyped") return UNTYPED_FLOAT;
1954
+ if (isComplex64(zt)) return FLOAT32;
969
1955
  return FLOAT64;
970
1956
  },
971
1957
 
@@ -997,6 +1983,11 @@ export const expressionCheckMethods = {
997
1983
  : this.resolveTypeNode(expr.typeExpr, scope);
998
1984
  const base = t.kind === "named" ? t.underlying : t;
999
1985
  const typeNode = expr.typeExpr;
1986
+ if (expr.typeExpr === null && hintType && base?.kind === "basic") {
1987
+ this.err(`invalid composite literal type ${typeStr(t)}`, expr);
1988
+ return t;
1989
+ }
1990
+ if (this._isSliceOrArray(base)) this._checkArrayLitKeys(expr, scope);
1000
1991
  this._inferArraySize(expr, typeNode, base);
1001
1992
  if (base?.kind === "struct")
1002
1993
  return this._checkStructCompositeLit(expr, scope, t, base);
@@ -1010,6 +2001,11 @@ export const expressionCheckMethods = {
1010
2001
  },
1011
2002
 
1012
2003
  _checkSimdCompositeLit(expr, scope, t, base) {
2004
+ if (base.simd === "Mask32x4") {
2005
+ if (expr.elems.length > 0)
2006
+ this.err("simd.Mask32x4 literal cannot have values", expr);
2007
+ return t;
2008
+ }
1013
2009
  if (expr.elems.length > 4) {
1014
2010
  this.err("too many values in simd.F32x4 literal", expr);
1015
2011
  }
@@ -1021,7 +2017,11 @@ export const expressionCheckMethods = {
1021
2017
  },
1022
2018
 
1023
2019
  _checkStructPositionalElems(expr, scope, t, base) {
1024
- const fieldNames = [...(base.fields?.keys() ?? [])];
2020
+ const fieldNames = base._structFields
2021
+ ? base._structFields.flatMap((f) =>
2022
+ f.embedded ? [embeddedFieldName(f)] : f.names,
2023
+ )
2024
+ : [...(base.fields?.keys() ?? [])];
1025
2025
  for (let i = 0; i < expr.elems.length; i++) {
1026
2026
  const elem = expr.elems[i];
1027
2027
  const fieldName = fieldNames[i];
@@ -1041,12 +2041,9 @@ export const expressionCheckMethods = {
1041
2041
  for (const elem of expr.elems) {
1042
2042
  if (elem.kind === "KeyValueExpr") {
1043
2043
  const keyName = elem.key.name;
1044
- const embed = base._embeds?.find((e) => {
1045
- const inner = e.kind === "pointer" ? e.base : e;
1046
- const name =
1047
- inner.kind === "named" ? inner.name.split(".").pop() : null;
1048
- return name === keyName;
1049
- });
2044
+ const embed = base._structFields?.find(
2045
+ (f) => f.embedded && embeddedFieldName(f) === keyName,
2046
+ )?.type;
1050
2047
  if (embed) elem._isEmbedInit = true;
1051
2048
  const fieldType = base.fields?.get(keyName) ?? embed;
1052
2049
  if (fieldType) {
@@ -1078,8 +2075,19 @@ export const expressionCheckMethods = {
1078
2075
  const et = this.checkCompositeLit(elem, scope, base.elem);
1079
2076
  elem._type = et;
1080
2077
  } else if (elem.kind === "KeyValueExpr") {
1081
- const vt = this.checkExpr(elem.value, scope);
1082
- this.assertAssignable(base.elem, vt, elem.value);
2078
+ if (
2079
+ elem.value.kind === "CompositeLit" &&
2080
+ elem.value.typeExpr === null
2081
+ ) {
2082
+ elem.value._type = this.checkCompositeLit(
2083
+ elem.value,
2084
+ scope,
2085
+ base.elem,
2086
+ );
2087
+ } else {
2088
+ const vt = this.checkExpr(elem.value, scope);
2089
+ this.assertAssignable(base.elem, vt, elem.value);
2090
+ }
1083
2091
  } else {
1084
2092
  const et = this.checkExpr(elem, scope);
1085
2093
  this.assertAssignable(base.elem, et, elem);
@@ -1103,7 +2111,13 @@ export const expressionCheckMethods = {
1103
2111
  _checkMapCompositeLit(expr, scope, t, base) {
1104
2112
  for (const elem of expr.elems) {
1105
2113
  if (elem.kind === "KeyValueExpr") {
1106
- const kt = this.checkExpr(elem.key, scope);
2114
+ let kt;
2115
+ if (elem.key.kind === "CompositeLit" && elem.key.typeExpr === null) {
2116
+ kt = this.checkCompositeLit(elem.key, scope, base.key);
2117
+ elem.key._type = kt;
2118
+ } else {
2119
+ kt = this.checkExpr(elem.key, scope);
2120
+ }
1107
2121
  // value may be an implicit composite lit
1108
2122
  let vt;
1109
2123
  if (
@@ -1116,6 +2130,7 @@ export const expressionCheckMethods = {
1116
2130
  vt = this.checkExpr(elem.value, scope);
1117
2131
  }
1118
2132
  this.assertAssignable(base.key, kt, elem.key);
2133
+ this._markIfaceKey(base, elem.key, kt);
1119
2134
  this.assertAssignable(base.value, vt, elem.value);
1120
2135
  }
1121
2136
  }
@@ -1123,15 +2138,37 @@ export const expressionCheckMethods = {
1123
2138
  },
1124
2139
  };
1125
2140
 
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
- }
2141
+ // Builtins whose calls may be constant expressions (`len("abc")`).
2142
+ const CONST_BUILTINS = new Set([
2143
+ "len",
2144
+ "cap",
2145
+ "real",
2146
+ "imag",
2147
+ "complex",
2148
+ "min",
2149
+ "max",
2150
+ ]);
2151
+
2152
+ // Source-like text of a literal key for diagnostics (`k`, `-1`, `K + 1`).
2153
+ function constKeyText(e) {
2154
+ switch (e?.kind) {
2155
+ case "Ident":
2156
+ return e.name;
2157
+ case "BasicLit":
2158
+ return String(e.value);
2159
+ case "UnaryExpr":
2160
+ return `${e.op}${constKeyText(e.operand)}`;
2161
+ case "BinaryExpr":
2162
+ return `${constKeyText(e.left)} ${e.op} ${constKeyText(e.right)}`;
2163
+ case "ParenExpr":
2164
+ return `(${constKeyText(e.expr)})`;
2165
+ case "SelectorExpr":
2166
+ return `${constKeyText(e.expr)}.${e.field}`;
2167
+ default:
2168
+ return "expression";
1135
2169
  }
1136
- return Math.max(sequential - 1, maxKeyed);
2170
+ }
2171
+
2172
+ function computeMaxIndex(elems) {
2173
+ return compositeLitMaxIndex(elems);
1137
2174
  }