gofront 1.8.0 → 1.9.1

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 (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -1,9 +1,14 @@
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
+ import { exprText, operandKind } from "./operand-text.js";
4
+ import {
5
+ EXCLUDED_STDLIB_MEMBERS,
6
+ EXCLUDED_STDLIB_PACKAGES,
7
+ } from "./stdlib/excluded.js";
4
8
  import { sharedInfo } from "./stdlib/shared.js";
5
9
  import {
6
10
  ANY,
11
+ BASIC_TYPES,
7
12
  BOOL,
8
13
  BROWSER_GLOBALS,
9
14
  COMPLEX64,
@@ -11,9 +16,12 @@ import {
11
16
  compositeLitMaxIndex,
12
17
  displayTypeName,
13
18
  ERROR,
19
+ embeddedFieldName,
14
20
  FLOAT32,
15
21
  FLOAT64,
22
+ FMT_PRINT_FUNCS,
16
23
  INT,
24
+ is64BitInt,
17
25
  isAny,
18
26
  isArray,
19
27
  isBool,
@@ -21,6 +29,7 @@ import {
21
29
  isComplex64,
22
30
  isComplexOrNumeric,
23
31
  isInteger,
32
+ isNil,
24
33
  isNumeric,
25
34
  isString,
26
35
  NIL,
@@ -30,14 +39,25 @@ import {
30
39
  STRING,
31
40
  TAINTED_ANY,
32
41
  typeStr,
42
+ UINT8,
33
43
  UNTYPED_BOOL,
34
44
  UNTYPED_COMPLEX,
35
45
  UNTYPED_FLOAT,
36
46
  UNTYPED_INT,
47
+ UNTYPED_RUNE,
37
48
  UNTYPED_STRING,
38
49
  VOID,
39
50
  } from "./types.js";
40
51
 
52
+ const NILABLE_KINDS = new Set([
53
+ "slice",
54
+ "map",
55
+ "pointer",
56
+ "func",
57
+ "interface",
58
+ "chan",
59
+ ]);
60
+
41
61
  /** @typedef {import('./index.js').TypeChecker} TypeChecker */
42
62
 
43
63
  // min/max: the type of the first typed operand; with only untyped constants,
@@ -52,7 +72,7 @@ function minMaxType(at) {
52
72
  // Each value is (self, name, expr, argTypes, scope) => returnType.
53
73
  const BUILTIN_CHECK = {
54
74
  len: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
55
- cap: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
75
+ cap: (s, n, e, at, sc) => s._checkBuiltinLen(n, e, at, sc),
56
76
  append: (s, _n, e, at) => {
57
77
  const st = at[0] ?? ANY;
58
78
  if (isArray(st)) {
@@ -68,15 +88,23 @@ const BUILTIN_CHECK = {
68
88
  }
69
89
  const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
70
90
  if (sb?.kind === "slice") {
91
+ const is64 = is64BitInt(sb.elem);
71
92
  for (let i = 1; i < e.args.length; i++) {
72
93
  if (e.args[i]._spread)
73
94
  s._rejectSeqAsSlice(at[i], e.args[i], "a spread argument");
74
- if (!e.args[i]._spread && at[i])
95
+ if (!e.args[i]._spread && at[i]) {
96
+ if (is64) e.args[i]._is64Bit = true;
97
+ s._tagComplexConst(
98
+ s.resolveType(sb.elem),
99
+ s.resolveType(at[i]),
100
+ e.args[i],
101
+ );
75
102
  s._markIfaceBox(
76
103
  s.resolveType(sb.elem),
77
104
  s.resolveType(at[i]),
78
105
  e.args[i],
79
106
  );
107
+ }
80
108
  }
81
109
  }
82
110
  return st;
@@ -84,14 +112,23 @@ const BUILTIN_CHECK = {
84
112
  copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
85
113
  delete: (s, _n, e, at) => {
86
114
  const mt = s._deepUnderlying(at[0]);
87
- if (mt?.kind === "map" && e.args[1]) s._markIfaceKey(mt, e.args[1], at[1]);
115
+ if (mt?.kind === "map" && e.args[1]) {
116
+ s._tagFloat32Const(s.resolveType(mt.key), at[1], e.args[1]);
117
+ s._markIfaceKey(mt, e.args[1], at[1]);
118
+ }
88
119
  return VOID;
89
120
  },
90
121
  make: (s, _n, e, _at, sc) => {
91
122
  if (e.args.length < 1) return ANY;
92
123
  const ta = e.args[0];
93
124
  const tn = ta.kind === "TypeExpr" ? ta.type : ta;
94
- return s.resolveTypeNode(tn, sc) ?? ANY;
125
+ const made = s.resolveTypeNode(tn, sc) ?? ANY;
126
+ // `make(S, n)` with S a type parameter makes its core type.
127
+ const core = made.kind === "typeParam" ? s._coreType(made, sc) : null;
128
+ if (core) ta._coreType = core;
129
+ for (const size of e.args.slice(1, 3))
130
+ s._checkIntegerConstOperand(size, "size", sc);
131
+ return made;
95
132
  },
96
133
  new: (s, _n, e, _at, sc) => {
97
134
  if (e.args.length < 1) return ANY;
@@ -101,17 +138,44 @@ const BUILTIN_CHECK = {
101
138
  },
102
139
  print: (s, n, e, at) => s._checkPrintArgs(n, e, at),
103
140
  println: (s, n, e, at) => s._checkPrintArgs(n, e, at),
104
- panic: () => VOID,
141
+ panic: (s, _n, e, at) => {
142
+ // The operand converts to `any`, so a defined type keeps its identity.
143
+ if (e.args[0] && at?.[0]) s._markIfaceBox(ANY, at[0], e.args[0]);
144
+ return VOID;
145
+ },
105
146
  recover: () => ANY,
106
147
  error: () => ERROR,
107
- min: (_s, _n, _e, at) => minMaxType(at),
108
- max: (_s, _n, _e, at) => minMaxType(at),
148
+ min: (s, _n, e, at) => s._checkMinMax(e, at),
149
+ max: (s, _n, e, at) => s._checkMinMax(e, at),
109
150
  clear: () => VOID,
110
151
  complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
111
152
  real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
112
153
  imag: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
113
154
  };
114
155
 
156
+ // Type kinds that are never valid as an index, slice bound or `make` size.
157
+ const NON_INTEGER_KINDS = new Set([
158
+ "struct",
159
+ "pointer",
160
+ "slice",
161
+ "map",
162
+ "func",
163
+ "array",
164
+ "interface",
165
+ "simd",
166
+ ]);
167
+
168
+ // Basic types that are never valid as an index, slice bound or `make` size.
169
+ const NON_INTEGER_BASICS = new Set([
170
+ "float32",
171
+ "float64",
172
+ "complex64",
173
+ "complex128",
174
+ "bool",
175
+ "string",
176
+ "any",
177
+ ]);
178
+
115
179
  // Method-name dispatch for _checkExpr — string values add no static call edges.
116
180
  const CHECK_EXPR_DELEGATE = {
117
181
  UnaryExpr: "_checkUnaryExpr",
@@ -141,7 +205,7 @@ export const expressionCheckMethods = {
141
205
  case "BasicLit": {
142
206
  switch (expr.litKind) {
143
207
  case "INT":
144
- return UNTYPED_INT;
208
+ return expr.rune ? UNTYPED_RUNE : UNTYPED_INT;
145
209
  case "FLOAT":
146
210
  return UNTYPED_FLOAT;
147
211
  case "STRING":
@@ -157,8 +221,35 @@ export const expressionCheckMethods = {
157
221
  return UNTYPED_COMPLEX;
158
222
  case "Ident": {
159
223
  const t = scope.lookup(expr.name);
160
- if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
161
- if (this.types.has(expr.name) || t.kind === "typeParam")
224
+ // `(*int)(nil)`: a predeclared type named where a type may stand.
225
+ if (!t && expr._typeRefOk && Object.hasOwn(BASIC_TYPES, expr.name)) {
226
+ expr._isTypeRef = true;
227
+ return BASIC_TYPES[expr.name];
228
+ }
229
+ if (!t) {
230
+ // The import of an excluded package already reported; its
231
+ // uses are not a second root cause.
232
+ const imported = this._fileImports
233
+ ?.get(this._currentFile)
234
+ ?.get(expr.name);
235
+ if (imported && EXCLUDED_STDLIB_PACKAGES.has(imported))
236
+ return TAINTED_ANY;
237
+ if (this._unavailableImports?.has(expr.name)) return TAINTED_ANY;
238
+ // `gom` needs no import in a js package; wasm packages get
239
+ // the same answer as an `import "gom"`.
240
+ if (
241
+ expr.name === "gom" &&
242
+ (this.target === "wasm" || this.target === "both")
243
+ ) {
244
+ this.recordBlocker("gom usage", "gom");
245
+ return this.err(
246
+ "package 'gom' is not available in wasm packages",
247
+ expr,
248
+ );
249
+ }
250
+ return this.err(`Undefined: '${expr.name}'`, expr);
251
+ }
252
+ if (this._namesType(expr.name, t, scope) || t.kind === "typeParam")
162
253
  expr._isTypeRef = true;
163
254
  // A Go declaration that shadows a browser global: JS output renames
164
255
  // it so generated code keeps reaching the real global. Package
@@ -188,14 +279,6 @@ export const expressionCheckMethods = {
188
279
  `(package '${this.pkgName}' is //gofront:target ${this.target} — browser globals are only available in js packages)`,
189
280
  );
190
281
  }
191
- if (
192
- (this.target === "wasm" || this.target === "both") &&
193
- expr.name === "gom" &&
194
- scope.lookup(expr.name) === this.globals.lookup(expr.name)
195
- ) {
196
- this.recordBlocker("gom usage", "gom");
197
- this.err("package 'gom' is not available in wasm packages", expr);
198
- }
199
282
  return t;
200
283
  }
201
284
  case "BinaryExpr": {
@@ -205,7 +288,21 @@ export const expressionCheckMethods = {
205
288
  if (isAny(lt) || isAny(rt))
206
289
  return this.binaryResultType(expr.op, ANY, ANY, expr);
207
290
  this._checkConstOperands(expr.op, lt, rt, expr);
208
- return this.binaryResultType(expr.op, lt, rt, expr);
291
+ this._markTypeParamMapNil(expr, lt, rt, scope);
292
+ const res = this.binaryResultType(expr.op, lt, rt, expr);
293
+ if (expr.op !== "<<" && expr.op !== ">>") {
294
+ this._retypeUntypedShift(lt, rt, expr.right);
295
+ this._retypeUntypedShift(rt, lt, expr.left);
296
+ }
297
+ this._tagFloat32Const(lt, rt, expr.right);
298
+ this._tagFloat32Const(rt, lt, expr.left);
299
+ this._tagComplex64Const(lt, rt, expr.right);
300
+ this._tagComplex64Const(rt, lt, expr.left);
301
+ if (is64BitInt(lt) && rt?.kind === "untyped" && expr.right)
302
+ expr.right._is64Bit = true;
303
+ if (is64BitInt(rt) && lt?.kind === "untyped" && expr.left)
304
+ expr.left._is64Bit = true;
305
+ return res;
209
306
  }
210
307
  case "AwaitExpr": {
211
308
  if (this.target === "wasm" || this.target === "both") {
@@ -251,7 +348,8 @@ export const expressionCheckMethods = {
251
348
 
252
349
  _checkBoolUnaryOp(ot, expr) {
253
350
  if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
254
- return BOOL;
351
+ // `!` keeps an untyped or defined bool type.
352
+ return isBool(ot) ? ot : BOOL;
255
353
  },
256
354
 
257
355
  _checkNumericUnaryOp(ot, expr) {
@@ -281,23 +379,32 @@ export const expressionCheckMethods = {
281
379
  return terms !== null && terms.length > 0 && terms.every(pred);
282
380
  },
283
381
 
284
- _constraintTerms(c, seen) {
382
+ // `scope` resolves the type parameters a term mentions (`~[]E`); it
383
+ // defaults to the package scope.
384
+ _constraintTerms(c, seen, scope = this.globals) {
285
385
  if (!c || seen.has(c)) return null;
286
386
  seen.add(c);
287
387
  // 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);
388
+ if (
389
+ c.kind === "TypeName" ||
390
+ c.kind === "InterfaceType" ||
391
+ c.kind === "GenericTypeName" ||
392
+ c.kind === "InstantiationExpr" ||
393
+ c.kind === "IndexExpr"
394
+ )
395
+ return this._constraintTerms(this.resolveTypeNode(c, scope), seen, scope);
396
+ if (c.kind === "named")
397
+ return this._constraintTerms(c.underlying, seen, scope);
398
+ if (c.kind === "interface") return this._interfaceTerms(c, seen, scope);
292
399
  if (c.kind === "typeParam")
293
- return this._constraintTerms(c.constraint, seen);
400
+ return this._constraintTerms(c.constraint, seen, scope);
294
401
  if (c.kind !== "UnionConstraint") return isAny(c) ? null : [c];
295
402
  const out = [];
296
403
  for (const term of c.terms) {
297
- const t = this.resolveTypeNode(term.type, this.globals);
404
+ const t = term.resolved ?? this.resolveTypeNode(term.type, scope);
298
405
  const inner =
299
406
  t?.kind === "named" && t.underlying?.kind === "interface"
300
- ? this._constraintTerms(t, seen)
407
+ ? this._constraintTerms(t, seen, scope)
301
408
  : [t];
302
409
  if (inner === null) return null;
303
410
  out.push(...inner);
@@ -307,10 +414,10 @@ export const expressionCheckMethods = {
307
414
 
308
415
  // An interface's type set is the intersection of its own union and the
309
416
  // type sets of the interfaces it embeds; null when it has none.
310
- _interfaceTerms(iface, seen) {
417
+ _interfaceTerms(iface, seen, scope) {
311
418
  const sets = [iface.unionConstraint, ...(iface._typeSetEmbeds ?? [])]
312
419
  .filter(Boolean)
313
- .map((s) => this._constraintTerms(s, new Set(seen)));
420
+ .map((s) => this._constraintTerms(s, new Set(seen), scope));
314
421
  if (sets.length === 0 || sets.includes(null)) return null;
315
422
  const [first, ...rest] = sets;
316
423
  return first.filter((t) =>
@@ -608,6 +715,11 @@ export const expressionCheckMethods = {
608
715
  const base = this.resolveType(
609
716
  recvT?.kind === "named" ? recvT.underlying : recvT,
610
717
  );
718
+ if (base?.kind === "interface" && base.methods?.has(expr.field)) {
719
+ // `i.M` binds the interface value; the call dispatches when it runs.
720
+ expr._isMethodValue = true;
721
+ return;
722
+ }
611
723
  if (!recvT || !this._isStructOrNamedMethod(recvT, base, expr.field)) return;
612
724
  expr._isMethodValue = true;
613
725
  if (baseType.kind === "pointer")
@@ -615,6 +727,16 @@ export const expressionCheckMethods = {
615
727
  else this._markPtrRecvAddress(expr, base, ft, scope);
616
728
  },
617
729
 
730
+ // Whether the identifier `name`, which resolved to `t`, names a type. A
731
+ // local variable or parameter that shares its name with a type of the
732
+ // build (the type table spans packages) is a value.
733
+ _namesType(name, t, scope) {
734
+ if (!this.types.has(name)) return false;
735
+ return (
736
+ scope.lookupScope(name) === this.globals || this.types.get(name) === t
737
+ );
738
+ },
739
+
618
740
  _checkSelectorExpr(expr, scope) {
619
741
  expr.expr._typeRefOk = true;
620
742
  const baseType = this.checkExpr(expr.expr, scope);
@@ -688,10 +810,70 @@ export const expressionCheckMethods = {
688
810
  return u;
689
811
  },
690
812
 
813
+ // Core type of type parameter `tp`: the one underlying type shared by every
814
+ // type in its constraint (`~[]E`, `~map[K]V`), or null when there is none.
815
+ _coreType(tp, scope) {
816
+ if (tp?.kind !== "typeParam") return null;
817
+ const terms = this._constraintTerms(tp.constraint, new Set(), scope);
818
+ if (!terms || terms.length === 0) return null;
819
+ const unders = terms.map((t) => this._deepUnderlying(this.resolveType(t)));
820
+ const key = typeStr(unders[0]);
821
+ if (unders.some((u) => !u || typeStr(u) !== key)) return null;
822
+ return unders[0];
823
+ },
824
+
825
+ // `m == nil` on a type parameter whose core type is a map: the backends
826
+ // compare it as a map, so record the core type on the operand.
827
+ _markTypeParamMapNil(expr, lt, rt, scope) {
828
+ if (expr.op !== "==" && expr.op !== "!=") return;
829
+ const [operand, other] =
830
+ rt === NIL
831
+ ? [expr.left, lt]
832
+ : lt === NIL
833
+ ? [expr.right, rt]
834
+ : [null, null];
835
+ if (!operand || other?.kind !== "typeParam") return;
836
+ const core = this._coreType(other, scope);
837
+ if (core?.kind === "map") operand._coreType = core;
838
+ },
839
+
840
+ // Underlying type of `t` for indexing, slicing, ranging and the like; a
841
+ // type parameter stands for its core type.
842
+ _coreUnderlying(t, scope) {
843
+ if (t?.kind === "typeParam") return this._coreType(t, scope) ?? t;
844
+ return this._deepUnderlying(t);
845
+ },
846
+
847
+ // One step of underlying type, or the core type for a type parameter.
848
+ _underlyingOrCore(t, scope) {
849
+ if (t?.kind === "typeParam") return this._coreType(t, scope) ?? t;
850
+ return t?.kind === "named" ? t.underlying : t;
851
+ },
852
+
853
+ _markCompositeKey(key, kb, node, srcType) {
854
+ if (!node || !srcType || isAny(key) || kb?.kind === "interface")
855
+ return false;
856
+ // Arrays, anonymous structs and pointers are not JS property names: the
857
+ // backend encodes them by value (`sk`) or by identity (`pk`).
858
+ if (kb?.kind === "pointer") node._mapKeyEnc = "pk";
859
+ else if (kb?.kind === "array" || (key?.kind === "struct" && kb === key))
860
+ node._mapKeyEnc = "sk";
861
+ return true;
862
+ },
863
+
691
864
  // A key of an interface-keyed map (`map[any]T`): the backend encodes it
692
865
  // with its dynamic type, so `L("1")`, `N(1)`, `"1"` and `1` stay distinct.
693
866
  // A named basic value is boxed first, exactly as in any interface
694
867
  // conversion; other values are keyed as they are.
868
+ // Untyped constant operands take the result type, so a float32 result
869
+ // rounds them like the typed operands.
870
+ _checkMinMax(e, at) {
871
+ const rt = minMaxType(at);
872
+ for (let i = 0; i < e.args.length; i++)
873
+ this._tagFloat32Const(rt, at[i], e.args[i]);
874
+ return rt;
875
+ },
876
+
695
877
  _markIfaceKey(mapType, node, srcType) {
696
878
  const key = this.resolveType(mapType.key);
697
879
  const kb = key?.kind === "named" ? this.resolveType(key.underlying) : key;
@@ -705,8 +887,10 @@ export const expressionCheckMethods = {
705
887
  }
706
888
  return;
707
889
  }
890
+ if (this._markCompositeKey(key, kb, node, srcType)) return;
708
891
  if (!node || !srcType || (!isAny(key) && kb?.kind !== "interface")) return;
709
892
  node._ifaceKey = true;
893
+ node._ifaceKeyType = src;
710
894
  this._markIfaceBox(key, this.resolveType(srcType), node);
711
895
  if (node._ifaceBox === "named" || node._ifaceBox === "named_ptr") return;
712
896
  node._ifaceBox = undefined;
@@ -729,13 +913,16 @@ export const expressionCheckMethods = {
729
913
  const indexType = this.checkExpr(expr.index, scope);
730
914
  if (bt?._tainted) return TAINTED_ANY;
731
915
  if (isAny(bt)) return ANY;
732
- const btu = this._deepUnderlying(bt);
916
+ const btu = this._coreUnderlying(bt, scope);
917
+ if (btu.kind !== "map")
918
+ this._checkIntegerConstOperand(expr.index, "index", scope);
733
919
  if (btu.kind === "slice" || btu.kind === "array") {
734
920
  this._checkArrayBoundsExpr(btu, bt, expr, scope);
735
921
  return btu.elem;
736
922
  }
737
923
  if (btu.kind === "map") {
738
924
  expr._mapValueType = btu.value;
925
+ this.assertAssignable(btu.key, indexType, expr.index);
739
926
  this._markIfaceKey(btu, expr.index, indexType);
740
927
  return btu.value;
741
928
  }
@@ -746,7 +933,7 @@ export const expressionCheckMethods = {
746
933
  this.err(`invalid index ${idx} (index must not be negative)`, expr);
747
934
  return btu.elem;
748
935
  }
749
- if (isString(bt)) return INT;
936
+ if (isString(bt)) return UINT8;
750
937
  return this.err(`Cannot index type ${typeStr(bt)}`, expr);
751
938
  },
752
939
 
@@ -756,6 +943,12 @@ export const expressionCheckMethods = {
756
943
  _genericFuncValueAsInstantiation(expr) {
757
944
  const toTypeNode = (n) => {
758
945
  if (n?.kind === "TypeExpr") return n.type;
946
+ // `Id[*int]`: the parser sees a dereference; on a generic function
947
+ // it is a pointer type argument.
948
+ if (n?.kind === "UnaryExpr" && n.op === "*") {
949
+ const base = toTypeNode(n.operand);
950
+ return base && { kind: "PointerType", base, _line: n._line };
951
+ }
759
952
  const parts = [];
760
953
  let cur = n;
761
954
  while (cur?.kind === "SelectorExpr") {
@@ -812,18 +1005,64 @@ export const expressionCheckMethods = {
812
1005
  return t.base;
813
1006
  },
814
1007
 
1008
+ // An index, slice bound or `make` size that is a constant must be an
1009
+ // integer (`s[1 == 1]`, `make([]int, "a")`).
1010
+ // A typed operand must have an integer type too (`s[f]` with `f float64`).
1011
+ _checkIntegerConstOperand(operand, what, scope) {
1012
+ const kind = this._nonIntegerConst(operand, scope);
1013
+ if (kind) {
1014
+ this.err(
1015
+ `invalid argument: ${what} ${exprText(operand)} (${kind}) must be integer`,
1016
+ operand,
1017
+ );
1018
+ return;
1019
+ }
1020
+ const t = operand?._type;
1021
+ if (!this._isNonIntegerType(t, scope)) return;
1022
+ this.err(
1023
+ `invalid argument: ${what} ${exprText(operand)} (${operandKind(operand, typeStr(t), false)}) must be integer`,
1024
+ operand,
1025
+ );
1026
+ },
1027
+
1028
+ // Whether `t` is a type (or a defined type over one) that is not an
1029
+ // integer: a float, complex, bool, string, `any`, struct, pointer, slice,
1030
+ // map, func, array, interface, simd vector or a type parameter whose type
1031
+ // set is not all integers. Untyped constants are judged by
1032
+ // `_nonIntegerConst`, so only untyped non-integers that reach here
1033
+ // (`s[a == b]`) count.
1034
+ _isNonIntegerType(t, scope) {
1035
+ if (!t || t._tainted) return false;
1036
+ // A type parameter is an integer only when every type in its type set
1037
+ // is (`~int | ~uint8`); `any`, `comparable` and method-only
1038
+ // constraints have no integer type set.
1039
+ if (t.kind === "typeParam") {
1040
+ const terms = this._constraintTerms(t.constraint, new Set(), scope);
1041
+ return !(terms?.length > 0 && terms.every(isInteger));
1042
+ }
1043
+ if (t.kind === "untyped")
1044
+ return (
1045
+ t.base === "bool" || t.base === "string" || t.base === "complex128"
1046
+ );
1047
+ const u = this._deepUnderlying(t);
1048
+ if (u?.kind === "basic") return NON_INTEGER_BASICS.has(u.name);
1049
+ return NON_INTEGER_KINDS.has(u?.kind);
1050
+ },
1051
+
815
1052
  _checkSliceExpr(expr, scope) {
816
1053
  const bt = this._derefArrayPtr(
817
1054
  expr,
818
1055
  "expr",
819
1056
  this.checkExpr(expr.expr, scope),
820
1057
  );
821
- if (expr.low) this.checkExpr(expr.low, scope);
822
- if (expr.high) this.checkExpr(expr.high, scope);
823
- if (expr.max) this.checkExpr(expr.max, scope);
1058
+ for (const bound of [expr.low, expr.high, expr.max]) {
1059
+ if (!bound) continue;
1060
+ this.checkExpr(bound, scope);
1061
+ this._checkIntegerConstOperand(bound, "index", scope);
1062
+ }
824
1063
  if (bt?._tainted) return TAINTED_ANY;
825
1064
  if (isAny(bt)) return ANY;
826
- const btu = this._deepUnderlying(bt);
1065
+ const btu = this._coreUnderlying(bt, scope);
827
1066
  if (btu.kind === "slice" || btu.kind === "array")
828
1067
  return { kind: "slice", elem: btu.elem };
829
1068
  if (isString(bt)) return STRING;
@@ -862,9 +1101,12 @@ export const expressionCheckMethods = {
862
1101
  const ret = expr.returnType
863
1102
  ? this.resolveTypeNode(expr.returnType, scope)
864
1103
  : VOID;
1104
+ const bodyRet = this._injectNamedReturns(expr.returnType, inner)
1105
+ ? { ...ret, _hasNamedReturns: true }
1106
+ : ret;
865
1107
  const savedDefer = this._deferCount;
866
1108
  this._deferCount = 0;
867
- this.checkBlock(expr.body, inner, ret);
1109
+ this.checkBlock(expr.body, inner, bodyRet);
868
1110
  this._reportUnused(inner, expr);
869
1111
  if (this._deferCount > 0) expr.body._hasDefer = true;
870
1112
  this._deferCount = savedDefer;
@@ -923,6 +1165,17 @@ export const expressionCheckMethods = {
923
1165
  const targetType = this.resolveTypeNode(expr.targetType, scope);
924
1166
  // `any(x)` / `error(x)`: box x as an implicit conversion would.
925
1167
  this._markIfaceBox(targetType, srcType, expr.expr);
1168
+ this._rejectNilConversion(srcType, targetType, expr);
1169
+ if (this._checkIfaceConversion(srcType, targetType, expr, expr.expr))
1170
+ return targetType;
1171
+ if (
1172
+ srcType &&
1173
+ targetType &&
1174
+ !isAny(srcType) &&
1175
+ !isAny(targetType) &&
1176
+ this._checkIfaceTargetConversion(srcType, targetType, expr.expr)
1177
+ )
1178
+ return targetType;
926
1179
  const srcResolved =
927
1180
  srcType?.kind === "named" ? srcType.underlying : srcType;
928
1181
  const tgtResolved =
@@ -943,6 +1196,8 @@ export const expressionCheckMethods = {
943
1196
  expr,
944
1197
  );
945
1198
  }
1199
+ // `complex64(1 + 0.01i)`: the constant's components round to float32.
1200
+ this._tagComplex64Const(targetType, srcType, expr.expr);
946
1201
  if (tgtResolved?.kind === "slice")
947
1202
  this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
948
1203
  if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
@@ -964,12 +1219,59 @@ export const expressionCheckMethods = {
964
1219
  return targetType;
965
1220
  },
966
1221
 
1222
+ // `int(nil)`: only a type with a nil value converts from `nil`.
1223
+ _rejectNilConversion(srcType, targetType, expr) {
1224
+ if (!isNil(srcType) || !targetType || isAny(targetType)) return;
1225
+ if (targetType.kind === "typeParam") return;
1226
+ const kind = this._deepUnderlying(targetType)?.kind;
1227
+ if (!kind || NILABLE_KINDS.has(kind)) return;
1228
+ this.err(`cannot convert nil to ${typeStr(targetType)}`, expr);
1229
+ },
1230
+
1231
+ // Pointers convert when their base types share an underlying type.
1232
+ _pointerConversionOk(srcPtr, tgtPtr, srcType, targetType, expr) {
1233
+ const sBase = this._deepUnderlying(srcPtr.base);
1234
+ const tBase = this._deepUnderlying(tgtPtr.base);
1235
+ if (typeStr(sBase) === typeStr(tBase)) return true;
1236
+ if (!this._sameStructFields(sBase, tBase)) return false;
1237
+ // WebAssembly struct types are nominal: the two pointers could not
1238
+ // share one object.
1239
+ if (this.target === "wasm" || this.target === "both") {
1240
+ this.recordBlocker("pointer conversion between struct types");
1241
+ this.err(
1242
+ `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`,
1243
+ expr,
1244
+ );
1245
+ }
1246
+ return true;
1247
+ },
1248
+
1249
+ // `I(x)` with I an interface type converts exactly when x is assignable to
1250
+ // I, so the method-set check (and its wording) is the assignment one.
1251
+ _checkIfaceTargetConversion(srcType, targetType, operand) {
1252
+ // `error("msg")` builds an error value (GoFront extension).
1253
+ if (targetType === ERROR && isString(srcType)) return false;
1254
+ const base = this.resolveType(
1255
+ targetType.kind === "named" ? targetType.underlying : targetType,
1256
+ );
1257
+ if (base?.kind !== "interface" || !operand) return false;
1258
+ this.assertAssignable(targetType, srcType, operand, "conversion");
1259
+ return true;
1260
+ },
1261
+
967
1262
  _checkConversionValidity(srcType, targetType, expr) {
1263
+ if (this._checkIfaceConversion(srcType, targetType, expr, expr.args?.[0]))
1264
+ return;
968
1265
  if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
1266
+ if (this._checkIfaceTargetConversion(srcType, targetType, expr.args?.[0]))
1267
+ return;
969
1268
  const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
970
1269
  const tgtResolved =
971
1270
  targetType.kind === "named" ? targetType.underlying : targetType;
972
1271
 
1272
+ // `nil` converts to any type that has a nil value.
1273
+ if (isNil(srcType) && NILABLE_KINDS.has(tgtResolved?.kind)) return;
1274
+
973
1275
  if (isNumeric(targetType) && isComplex(srcType)) {
974
1276
  this.err(
975
1277
  `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
@@ -1032,17 +1334,18 @@ export const expressionCheckMethods = {
1032
1334
  srcResolved.elem?.name === "int32")))
1033
1335
  )
1034
1336
  return;
1035
- if (srcResolved?.kind === "pointer" && tgtResolved?.kind === "pointer") {
1036
- const sBase =
1037
- srcResolved.base?.kind === "named"
1038
- ? srcResolved.base.underlying
1039
- : srcResolved.base;
1040
- const tBase =
1041
- tgtResolved.base?.kind === "named"
1042
- ? tgtResolved.base.underlying
1043
- : tgtResolved.base;
1044
- if (typeStr(sBase) === typeStr(tBase)) return;
1045
- }
1337
+ if (
1338
+ srcResolved?.kind === "pointer" &&
1339
+ tgtResolved?.kind === "pointer" &&
1340
+ this._pointerConversionOk(
1341
+ srcResolved,
1342
+ tgtResolved,
1343
+ srcType,
1344
+ targetType,
1345
+ expr,
1346
+ )
1347
+ )
1348
+ return;
1046
1349
  this.err(
1047
1350
  `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
1048
1351
  expr,
@@ -1052,6 +1355,7 @@ export const expressionCheckMethods = {
1052
1355
  _checkTypeAssertExpr(expr, scope) {
1053
1356
  const srcType = this.checkExpr(expr.expr, scope);
1054
1357
  const targetType = this.resolveTypeNode(expr.type, scope);
1358
+ this._markDynamicType(expr.type, targetType);
1055
1359
 
1056
1360
  // In Go, the source of a type assertion must be an interface type.
1057
1361
  // We allow `any` (which is GoFront's basic any) and interface types.
@@ -1091,8 +1395,10 @@ export const expressionCheckMethods = {
1091
1395
  "expr",
1092
1396
  this.checkExpr(expr.expr, scope),
1093
1397
  );
1094
- const resolved =
1095
- collType?.kind === "named" ? collType.underlying : collType;
1398
+ const resolved = this._underlyingOrCore(collType, scope);
1399
+ // Backends range over the core type of a type parameter.
1400
+ if (collType?.kind === "typeParam" && resolved !== collType)
1401
+ expr.expr._coreType = resolved;
1096
1402
  return this._rangeCollTypeTuple(resolved, collType);
1097
1403
  },
1098
1404
 
@@ -1119,7 +1425,10 @@ export const expressionCheckMethods = {
1119
1425
  ) {
1120
1426
  const tp = baseType.typeParams[i];
1121
1427
  const constraint = tp.constraint
1122
- ? this.resolveTypeNode(tp.constraint, scope)
1428
+ ? this.resolveTypeNode(
1429
+ tp.constraint,
1430
+ this._typeArgScope(baseType.typeParams, typeArgs, scope),
1431
+ )
1123
1432
  : null;
1124
1433
  if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
1125
1434
  }
@@ -1298,12 +1607,12 @@ export const expressionCheckMethods = {
1298
1607
  if (!resolved) return null;
1299
1608
  fnType = resolved;
1300
1609
  }
1301
- return this._completePartialInstantiation(expr, argTypes) ?? fnType;
1610
+ return this._completePartialInstantiation(expr, argTypes, scope) ?? fnType;
1302
1611
  },
1303
1612
 
1304
1613
  // `F[int](x)` with fewer explicit type arguments than type parameters:
1305
1614
  // infer the rest from the call arguments and record the full list.
1306
- _completePartialInstantiation(expr, argTypes) {
1615
+ _completePartialInstantiation(expr, argTypes, scope) {
1307
1616
  const inst = expr.func;
1308
1617
  const generic = inst?.kind === "InstantiationExpr" && inst._genericFnType;
1309
1618
  if (!generic || inst._typeArgs.length >= generic.typeParams.length)
@@ -1312,6 +1621,7 @@ export const expressionCheckMethods = {
1312
1621
  generic,
1313
1622
  this._inferArgTypes(expr, argTypes),
1314
1623
  inst._typeArgs,
1624
+ scope,
1315
1625
  );
1316
1626
  if (!typeArgs) {
1317
1627
  this.err(
@@ -1327,7 +1637,7 @@ export const expressionCheckMethods = {
1327
1637
 
1328
1638
  // `T(x)` with T a type parameter: only a numeric x converts (the constraint
1329
1639
  // decides the target at instantiation).
1330
- _checkTypeParamConversion(tp, argTypes, expr) {
1640
+ _checkTypeParamConversion(tp, argTypes, expr, scope) {
1331
1641
  expr._isTypeConversion = true;
1332
1642
  expr._conversionTargetType = tp;
1333
1643
  if (expr.args.length !== 1) {
@@ -1336,7 +1646,23 @@ export const expressionCheckMethods = {
1336
1646
  expr,
1337
1647
  );
1338
1648
  }
1339
- if (isNumeric(argTypes[0]) && !this._constraintHasTypeSet(tp.constraint))
1649
+ // A constraint with a core type (`~[]E`) converts as that type does.
1650
+ const core = this._coreType(tp, scope);
1651
+ if (core) {
1652
+ const src = argTypes[0];
1653
+ const nilOk = ["slice", "map", "pointer", "func", "chan", "interface"];
1654
+ if (src === NIL || (src?.kind === "basic" && src.name === "nil")) {
1655
+ if (!nilOk.includes(core.kind))
1656
+ this.err(`cannot convert nil to ${typeStr(tp)}`, expr);
1657
+ } else if (src?.kind !== "typeParam") {
1658
+ this._checkConversionValidity(src, core, expr);
1659
+ }
1660
+ return tp;
1661
+ }
1662
+ if (
1663
+ isNumeric(argTypes[0]) &&
1664
+ !this._constraintHasTypeSet(tp.constraint, scope)
1665
+ )
1340
1666
  return this.err(
1341
1667
  `cannot convert ${typeStr(argTypes[0])} to type ${typeStr(tp)}: ${typeStr(tp)} is not constrained to a set of convertible types`,
1342
1668
  expr,
@@ -1356,18 +1682,38 @@ export const expressionCheckMethods = {
1356
1682
  // A conversion to a type parameter needs a constraint that lists types
1357
1683
  // (`~int | ~float64`, or an interface embedding such a union); `any`,
1358
1684
  // `comparable` and method-only constraints allow every type argument.
1359
- _constraintHasTypeSet(constraint) {
1685
+ _constraintHasTypeSet(constraint, scope = this.globals) {
1360
1686
  if (!constraint) return true;
1361
1687
  if (constraint.kind === "UnionConstraint") return true;
1362
- const c = this.resolveType(constraint);
1688
+ // Receiver type parameters keep their constraint as an AST node.
1689
+ const c = this.resolveType(
1690
+ constraint.kind === "TypeName" || constraint.kind === "InterfaceType"
1691
+ ? this.resolveTypeNode(constraint, scope)
1692
+ : constraint,
1693
+ );
1363
1694
  const base = c?.kind === "named" ? this.resolveType(c.underlying) : c;
1364
1695
  if (base?.kind !== "interface") return false;
1365
1696
  if (base.unionConstraint) return true;
1366
1697
  return (base._typeSetEmbeds ?? []).some((e) =>
1367
- this._constraintHasTypeSet(e),
1698
+ this._constraintHasTypeSet(e, scope),
1368
1699
  );
1369
1700
  },
1370
1701
 
1702
+ // A direct call of a `//gofront:native` function is marked `_native` so a
1703
+ // backend may substitute host code. A function used as a value (`f :=
1704
+ // str.Index; f(a, b)`) is not a direct reference and calls the Go body.
1705
+ _markNativeCall(expr, fnType, scope) {
1706
+ const callee =
1707
+ expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
1708
+ const native = fnType?.native ?? fnType?.underlying?.native ?? null;
1709
+ const direct =
1710
+ callee.kind === "Ident"
1711
+ ? scope.lookupScope(callee.name) === this.globals &&
1712
+ Boolean(this._funcDeclNames?.has(callee.name))
1713
+ : callee.kind === "SelectorExpr" && Boolean(callee._stdName);
1714
+ expr._native = native && direct ? native : null;
1715
+ },
1716
+
1371
1717
  checkCall(expr, scope) {
1372
1718
  expr.func._typeRefOk = true;
1373
1719
  let fnType = this.checkExpr(expr.func, scope);
@@ -1391,6 +1737,7 @@ export const expressionCheckMethods = {
1391
1737
  return this.checkBuiltin(fnType.name, expr, argTypes, scope);
1392
1738
  }
1393
1739
 
1740
+ this._markNativeCall(expr, fnType, scope);
1394
1741
  const argTypes = this._resolveCallArgTypes(expr, scope);
1395
1742
 
1396
1743
  if (fnType?._tainted) return TAINTED_ANY;
@@ -1401,7 +1748,12 @@ export const expressionCheckMethods = {
1401
1748
 
1402
1749
  // Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
1403
1750
  // The callee is a type name, not a callable variable.
1404
- if (fnType.kind === "named" && expr.func._isTypeRef) {
1751
+ if (
1752
+ (fnType.kind === "named" ||
1753
+ fnType.kind === "pointer" ||
1754
+ fnType.kind === "basic") &&
1755
+ expr.func._isTypeRef
1756
+ ) {
1405
1757
  expr._isTypeConversion = true;
1406
1758
  expr._conversionTargetType = fnType;
1407
1759
  if (expr.args.length !== 1) {
@@ -1416,7 +1768,7 @@ export const expressionCheckMethods = {
1416
1768
  }
1417
1769
 
1418
1770
  if (fnType.kind === "typeParam" && expr.func._isTypeRef) {
1419
- return this._checkTypeParamConversion(fnType, argTypes, expr);
1771
+ return this._checkTypeParamConversion(fnType, argTypes, expr, scope);
1420
1772
  }
1421
1773
 
1422
1774
  fnType = this._resolveCallFnType(fnType, argTypes, expr, scope);
@@ -1427,13 +1779,6 @@ export const expressionCheckMethods = {
1427
1779
 
1428
1780
  this._checkCallArgs(fnType, argTypes, expr, scope);
1429
1781
 
1430
- // Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
1431
- // how their result derives from the first argument.
1432
- if (fnType._derivedReturn) {
1433
- const derived = this._derivedReturnType(fnType._derivedReturn, argTypes);
1434
- if (derived) return derived;
1435
- }
1436
-
1437
1782
  const ret = fnType.returns;
1438
1783
  if (!ret || ret.length === 0) return VOID;
1439
1784
  if (ret.length === 1) return ret[0];
@@ -1483,19 +1828,6 @@ export const expressionCheckMethods = {
1483
1828
  );
1484
1829
  },
1485
1830
 
1486
- _derivedReturnType(mode, argTypes) {
1487
- const arg = argTypes[0];
1488
- const under = arg?.kind === "named" ? arg.underlying : arg;
1489
- switch (mode) {
1490
- case "arg0":
1491
- return under ? arg : null;
1492
- case "elemOfArg0":
1493
- return under?.elem ?? null;
1494
- default:
1495
- return null;
1496
- }
1497
- },
1498
-
1499
1831
  // Argument types for inference: a spread argument `xs...` stands for its
1500
1832
  // elements, which is what the variadic parameter type describes.
1501
1833
  _inferArgTypes(expr, argTypes) {
@@ -1506,10 +1838,23 @@ export const expressionCheckMethods = {
1506
1838
  });
1507
1839
  },
1508
1840
 
1841
+ // Scope where each type parameter name stands for its type argument, so a
1842
+ // constraint that mentions another parameter (`S ~[]E`) resolves.
1843
+ _typeArgScope(typeParams, typeArgs, scope) {
1844
+ const out = new Scope(scope);
1845
+ typeParams.forEach((tp, i) => {
1846
+ if (typeArgs[i])
1847
+ out.define(tp.name, { kind: "aliasArg", type: typeArgs[i] });
1848
+ });
1849
+ return out;
1850
+ },
1851
+
1509
1852
  _resolveGenericFnType(fnType, argTypes, expr, scope) {
1510
1853
  const typeArgs = this.inferTypeArgs(
1511
1854
  fnType,
1512
1855
  this._inferArgTypes(expr, argTypes),
1856
+ [],
1857
+ scope,
1513
1858
  );
1514
1859
  if (!typeArgs) {
1515
1860
  this.err(
@@ -1522,7 +1867,10 @@ export const expressionCheckMethods = {
1522
1867
  for (let i = 0; i < fnType.typeParams.length && i < typeArgs.length; i++) {
1523
1868
  const tp = fnType.typeParams[i];
1524
1869
  const constraint = tp.constraint
1525
- ? this.resolveTypeNode(tp.constraint, scope)
1870
+ ? this.resolveTypeNode(
1871
+ tp.constraint,
1872
+ this._typeArgScope(fnType.typeParams, typeArgs, scope),
1873
+ )
1526
1874
  : null;
1527
1875
  if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
1528
1876
  }
@@ -1562,6 +1910,9 @@ export const expressionCheckMethods = {
1562
1910
  !ns._gofront &&
1563
1911
  !ns._mangle?.has(expr.func.field);
1564
1912
  if (emitterBacked) return;
1913
+ // The generated wrapper of a Print-family call takes the results as
1914
+ // separate parameters (src/lower/fmt-formatters.js).
1915
+ if (ns?.name === "fmt" && FMT_PRINT_FUNCS.has(expr.func.field)) return;
1565
1916
  this.err(
1566
1917
  "passing multiple return values to a variadic function is not supported in wasm packages; assign them to variables first",
1567
1918
  expr,
@@ -1587,6 +1938,14 @@ export const expressionCheckMethods = {
1587
1938
  const node = expr._multiForward ? null : expr.args[i];
1588
1939
  this.assertAssignable(targetParam, argTypes[i], node, "argument");
1589
1940
  }
1941
+ // `g(f())`: per-element box marks for lowering to split the forward.
1942
+ if (
1943
+ expr._multiForward &&
1944
+ !fnType.variadic &&
1945
+ !this._skipIfaceMark &&
1946
+ fnType.params.length === argTypes.length
1947
+ )
1948
+ this._markForwardedElems(fnType.params, argTypes, expr.args[0]);
1590
1949
 
1591
1950
  this._checkSimdLaneArgs(expr, scope);
1592
1951
  },
@@ -1652,18 +2011,51 @@ export const expressionCheckMethods = {
1652
2011
  return null;
1653
2012
  },
1654
2013
 
1655
- _checkBuiltinLen(name, expr, argTypes) {
2014
+ _capOperandKind(operand, type) {
2015
+ const untypedConst = type?.kind === "untyped" && type.base !== "nil";
2016
+ return untypedConst
2017
+ ? operandKind(operand, type.base, true)
2018
+ : operandKind(operand, typeStr(type), false);
2019
+ },
2020
+
2021
+ // `cap` takes a slice or an array (a pointer to an array is dereferenced
2022
+ // before this check). A type parameter needs a slice or array core type;
2023
+ // one with no single core type is left to instantiation. Unknown and
2024
+ // `any` operands (JS interop) are not judged.
2025
+ _capOperandOk(t, scope) {
2026
+ if (!t || t._tainted || isAny(t)) return true;
2027
+ if (t.kind === "typeParam") {
2028
+ const core = this._coreType(t, scope);
2029
+ return !core || core.kind === "slice" || core.kind === "array";
2030
+ }
2031
+ const kind = this._deepUnderlying(t)?.kind;
2032
+ return kind === "slice" || kind === "array";
2033
+ },
2034
+
2035
+ _checkBuiltinLen(name, expr, argTypes, scope) {
1656
2036
  this._rejectSeqAsSlice(argTypes[0], expr.args?.[0], `${name}()`);
2037
+ const operand = expr.args?.[0];
1657
2038
  if (expr.args?.length === 1)
1658
2039
  argTypes[0] = this._derefArrayPtr(expr.args, 0, argTypes[0]);
1659
- if (name === "len") {
1660
- const size = this._constLenFromType(argTypes[0]);
1661
- if (size != null) expr._constLen = size;
1662
- } else if (sharedInfo(argTypes[0])) {
1663
- this.err(
1664
- `cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
1665
- expr,
1666
- );
2040
+ // `len` and `cap` of an array (or pointer to one) are its length.
2041
+ const size = this._constLenFromType(argTypes[0]);
2042
+ if (size != null) {
2043
+ expr._constLen = size;
2044
+ // The operand is not dereferenced: a nil `*[N]T` has length N.
2045
+ if (expr.args.length === 1) expr.args[0] = operand;
2046
+ }
2047
+ if (name === "cap") {
2048
+ if (sharedInfo(argTypes[0])) {
2049
+ this.err(
2050
+ `cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
2051
+ expr,
2052
+ );
2053
+ } else if (!this._capOperandOk(argTypes[0], scope)) {
2054
+ this.err(
2055
+ `invalid argument: ${exprText(expr.args?.[0])} (${this._capOperandKind(expr.args?.[0], argTypes[0])}) for cap`,
2056
+ expr,
2057
+ );
2058
+ }
1667
2059
  }
1668
2060
  return INT;
1669
2061
  },
@@ -1694,6 +2086,9 @@ export const expressionCheckMethods = {
1694
2086
  expr,
1695
2087
  );
1696
2088
  }
2089
+ // An untyped operand takes type float32 (`complex(f, 0.01)`).
2090
+ this._tagFloat32Const(FLOAT32, crt, expr.args[0]);
2091
+ this._tagFloat32Const(FLOAT32, cit, expr.args[1]);
1697
2092
  return COMPLEX64;
1698
2093
  }
1699
2094
  return COMPLEX128;
@@ -1778,17 +2173,7 @@ export const expressionCheckMethods = {
1778
2173
  _checkStructPositionalElems(expr, scope, t, base) {
1779
2174
  const fieldNames = base._structFields
1780
2175
  ? 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,
2176
+ f.embedded ? [embeddedFieldName(f)] : f.names,
1792
2177
  )
1793
2178
  : [...(base.fields?.keys() ?? [])];
1794
2179
  for (let i = 0; i < expr.elems.length; i++) {
@@ -1810,12 +2195,9 @@ export const expressionCheckMethods = {
1810
2195
  for (const elem of expr.elems) {
1811
2196
  if (elem.kind === "KeyValueExpr") {
1812
2197
  const keyName = elem.key.name;
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
- });
2198
+ const embed = base._structFields?.find(
2199
+ (f) => f.embedded && embeddedFieldName(f) === keyName,
2200
+ )?.type;
1819
2201
  if (embed) elem._isEmbedInit = true;
1820
2202
  const fieldType = base.fields?.get(keyName) ?? embed;
1821
2203
  if (fieldType) {