gofront 1.9.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 (42) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/backend/js/expressions.js +329 -58
  4. package/src/backend/js/index.js +90 -10
  5. package/src/backend/js/runtime.js +51 -9
  6. package/src/backend/js/statements.js +193 -84
  7. package/src/backend/js/type-info.js +123 -8
  8. package/src/backend/shared/fmt-types.js +30 -1
  9. package/src/backend/wasm/emit-builtins.js +250 -9
  10. package/src/backend/wasm/emit-exprs.js +63 -22
  11. package/src/backend/wasm/emit-shared.js +4 -1
  12. package/src/backend/wasm/emit-stmts.js +239 -205
  13. package/src/backend/wasm/emit.js +73 -26
  14. package/src/backend/wasm/globals.js +1 -0
  15. package/src/backend/wasm/index.js +8 -0
  16. package/src/backend/wasm/module-emitter.js +211 -57
  17. package/src/backend/wasm/monomorph.js +36 -2
  18. package/src/backend/wasm/natives.js +4 -0
  19. package/src/backend/wasm/types.js +32 -3
  20. package/src/backend/wasm/variadic.js +6 -1
  21. package/src/compiler.js +6 -1
  22. package/src/lexer/lexer.js +1 -0
  23. package/src/link/stdlib.js +1 -0
  24. package/src/link/type-names.js +72 -16
  25. package/src/lower/embedding.js +43 -15
  26. package/src/lower/fmt-formatters.js +38 -14
  27. package/src/lower/functions.js +223 -7
  28. package/src/lower/gen-types.js +21 -2
  29. package/src/lower/index.js +7 -1
  30. package/src/lower/json-codecs.js +114 -6
  31. package/src/parser/declarations.js +4 -2
  32. package/src/parser/expressions.js +25 -41
  33. package/src/parser/statements.js +107 -0
  34. package/src/parser/types.js +11 -0
  35. package/src/typechecker/assignability.js +94 -12
  36. package/src/typechecker/constants.js +124 -2
  37. package/src/typechecker/expressions.js +171 -17
  38. package/src/typechecker/index.js +158 -29
  39. package/src/typechecker/operand-text.js +1 -0
  40. package/src/typechecker/resolve.js +308 -39
  41. package/src/typechecker/statements.js +171 -21
  42. package/src/typechecker/types.js +84 -2
@@ -15,7 +15,28 @@ import {
15
15
  INT_RANGES,
16
16
  intConst,
17
17
  } from "./const-fold.js";
18
- import { defaultType, isAny, typeStr, VOID } from "./types.js";
18
+ import {
19
+ compositeLitMaxIndex,
20
+ defaultType,
21
+ isAny,
22
+ isComplex,
23
+ isComplex64,
24
+ isUntyped,
25
+ typeStr,
26
+ VOID,
27
+ } from "./types.js";
28
+
29
+ // True when evaluating `node` runs a call or a channel receive (conversions
30
+ // excluded): such an operand makes `len` of an array a non-constant.
31
+ function hasCallOrReceive(node) {
32
+ if (!node || typeof node !== "object") return false;
33
+ if (Array.isArray(node)) return node.some(hasCallOrReceive);
34
+ if (node.kind === "CallExpr" && !node._isTypeConversion) return true;
35
+ if (node.kind === "UnaryExpr" && node.op === "<-") return true;
36
+ return Object.keys(node).some(
37
+ (key) => key.charCodeAt(0) !== 95 && hasCallOrReceive(node[key]),
38
+ );
39
+ }
19
40
 
20
41
  // Basic type names usable as conversion functions (`int32(x)`).
21
42
  const BASIC_CONVERSIONS = new Set([
@@ -38,6 +59,17 @@ const CONST_KINDS = new Set([
38
59
  "SelectorExpr",
39
60
  ]);
40
61
 
62
+ // Describes a constant that is not integer-valued (`float64 constant`), or
63
+ // null for an integer, an untyped float with an integer value, or a rune.
64
+ function nonIntegerConstKind(v) {
65
+ if (v.k === "INT") return null;
66
+ if (v.k === "FLOAT") {
67
+ if (v.t) return `${v.t} constant`;
68
+ return constIntValue(v) === null ? "untyped float constant" : null;
69
+ }
70
+ return `${v.k.toLowerCase()} constant`;
71
+ }
72
+
41
73
  const NEGATIVE_SHIFT_ERROR = "invalid shift count: negative shift count";
42
74
 
43
75
  /** @type {ThisType<import('./index.js').TypeChecker>} */
@@ -49,6 +81,23 @@ export const constantCheckMethods = {
49
81
  return raw && !raw.overflow && !raw.error ? raw : null;
50
82
  },
51
83
 
84
+ // What makes `expr` a constant that cannot be an array length, index or
85
+ // size (`untyped bool constant`, `untyped nil`), or null when it is an
86
+ // integer constant or not a constant at all.
87
+ _nonIntegerConst(expr, scope = null) {
88
+ let e = expr;
89
+ while (e?.kind === "ParenExpr") e = e.expr;
90
+ if (e?.kind === "ImagLit") return "untyped complex constant";
91
+ if (e?.kind === "BasicLit" && e.litKind === "NIL") return "untyped nil";
92
+ let v = null;
93
+ try {
94
+ v = this._constValue(expr, scope);
95
+ } catch {
96
+ // not a constant the evaluator understands
97
+ }
98
+ return v ? nonIntegerConstKind(v) : null;
99
+ },
100
+
52
101
  // Integer value of an integer (or untyped integral float) constant as a
53
102
  // Number, or null.
54
103
  _constIntValue(expr, scope = null) {
@@ -58,6 +107,23 @@ export const constantCheckMethods = {
58
107
  return i === null ? null : Number(i);
59
108
  },
60
109
 
110
+ // A named untyped complex constant has no folded value (`_const`): its
111
+ // initializer goes on the identifier as `_complexInit`, so the backends
112
+ // can fold it where it takes type complex64.
113
+ // A named `complex64` constant is tagged `_c64Const` as well, so a use
114
+ // takes float32 components as in Go.
115
+ _noteComplexConstInit(expr, scope) {
116
+ if (expr.kind !== "Ident") return;
117
+ const typed64 = !isUntyped(expr._type) && isComplex64(expr._type);
118
+ if (!typed64 && !(isUntyped(expr._type) && isComplex(expr._type))) return;
119
+ const owner = scope.lookupScope(expr.name);
120
+ if (!owner?._consts.has(expr.name)) return;
121
+ const init = owner.lookupConstExpr(expr.name);
122
+ if (!init) return;
123
+ expr._complexInit = init;
124
+ if (typed64) expr._c64Const = true;
125
+ },
126
+
61
127
  // Raw evaluation result, memoized on checked nodes: a value, a value with
62
128
  // `overflow`, `{ error }`, or null.
63
129
  _constRaw(expr, scope) {
@@ -83,6 +149,7 @@ export const constantCheckMethods = {
83
149
  // overflow / division by zero once per node.
84
150
  _noteConst(expr, scope) {
85
151
  if (!CONST_KINDS.has(expr.kind)) return;
152
+ this._noteComplexConstInit(expr, scope ?? this.globals);
86
153
  const raw = this._constRaw(expr, scope);
87
154
  expr._const = raw && !raw.overflow && !raw.error ? raw : null;
88
155
  if (!raw || expr._constReported) return;
@@ -128,7 +195,13 @@ export const constantCheckMethods = {
128
195
 
129
196
  _evalConstIdent(expr, scope) {
130
197
  const owner = scope.lookupScope(expr.name);
131
- if (!owner?._consts.has(expr.name)) return null;
198
+ // A constant declared after the type that uses it (`type B [k]int`) has
199
+ // no symbol yet, but its initializer is registered.
200
+ if (!owner) {
201
+ const early = scope.lookupConstExpr(expr.name);
202
+ return early ? this._constValue(early, scope) : null;
203
+ }
204
+ if (!owner._consts.has(expr.name)) return null;
132
205
  const init = owner.lookupConstExpr(expr.name);
133
206
  const v = init
134
207
  ? this._constValue(init, owner)
@@ -159,6 +232,8 @@ export const constantCheckMethods = {
159
232
  const fn = expr.func;
160
233
  if (fn?.kind !== "Ident" || scope.lookup(fn.name)?.kind !== "builtin")
161
234
  return null;
235
+ const arrayLen = this._constArrayLen(fn.name, expr.args, scope);
236
+ if (arrayLen !== null) return intConst(BigInt(arrayLen));
162
237
  const vals = (expr.args ?? []).map((a) => this._constValue(a, scope));
163
238
  if (vals.length === 0 || vals.some((v) => !v)) return null;
164
239
  if (fn.name === "min" || fn.name === "max")
@@ -168,6 +243,53 @@ export const constantCheckMethods = {
168
243
  return isLen ? intConst(BigInt(vals[0].v.length)) : null;
169
244
  },
170
245
 
246
+ // A package variable named in an array length may be declared after the
247
+ // type (or variable) that uses it, so it is not defined yet: its declared
248
+ // type, or the type of its composite literal, is resolved from the spec.
249
+ _earlyPackageVarType(arg, scope) {
250
+ let e = arg;
251
+ while (e?.kind === "ParenExpr") e = e.expr;
252
+ if (e?.kind !== "Ident") return null;
253
+ // An untyped package variable is `any` until its initializer is typed.
254
+ const known = scope.lookup(e.name);
255
+ if (known && !isAny(known)) return null;
256
+ const entry = this._pkgVarSpecs?.get(e.name);
257
+ if (!entry || entry.busy) return null;
258
+ const { spec, index } = entry;
259
+ const lit = spec.value?.[index];
260
+ const typeNode =
261
+ spec.type ?? (lit?.kind === "CompositeLit" ? lit.typeExpr : null);
262
+ if (!typeNode) return null;
263
+ entry.busy = true;
264
+ try {
265
+ const t = this.resolveTypeNode(typeNode, this.globals);
266
+ // `[...]T{...}`: the length is the literal's highest index + 1.
267
+ return t?.kind === "array" && t.size === null && lit?.elems
268
+ ? { ...t, size: compositeLitMaxIndex(lit.elems) + 1 }
269
+ : t;
270
+ } finally {
271
+ entry.busy = false;
272
+ }
273
+ },
274
+
275
+ // `len(a)` and `cap(a)` of an array (or pointer to an array) are constants
276
+ // unless `a` contains a function call or channel receive; `a` itself is not
277
+ // evaluated. Returns the length, or null.
278
+ _constArrayLen(name, args, scope) {
279
+ if ((name !== "len" && name !== "cap") || args?.length !== 1) return null;
280
+ const arg = args[0];
281
+ if (hasCallOrReceive(arg)) return null;
282
+ let t = this._earlyPackageVarType(arg, scope);
283
+ if (!t) {
284
+ if (arg._type === undefined) this.checkExpr(arg, scope);
285
+ t = arg._type;
286
+ }
287
+ t = this.resolveType(t);
288
+ if (t?.kind === "pointer") t = this.resolveType(t.base);
289
+ if (t?.kind === "named") t = this.resolveType(t.underlying);
290
+ return t?.kind === "array" && Number.isInteger(t.size) ? t.size : null;
291
+ },
292
+
171
293
  // Target type of a conversion call (`MyInt(x)`, `int32(x)`), or null.
172
294
  _constConversionTarget(expr, scope) {
173
295
  if (expr.args?.length !== 1) return null;
@@ -44,6 +44,7 @@ import {
44
44
  UNTYPED_COMPLEX,
45
45
  UNTYPED_FLOAT,
46
46
  UNTYPED_INT,
47
+ UNTYPED_RUNE,
47
48
  UNTYPED_STRING,
48
49
  VOID,
49
50
  } from "./types.js";
@@ -71,7 +72,7 @@ function minMaxType(at) {
71
72
  // Each value is (self, name, expr, argTypes, scope) => returnType.
72
73
  const BUILTIN_CHECK = {
73
74
  len: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
74
- cap: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
75
+ cap: (s, n, e, at, sc) => s._checkBuiltinLen(n, e, at, sc),
75
76
  append: (s, _n, e, at) => {
76
77
  const st = at[0] ?? ANY;
77
78
  if (isArray(st)) {
@@ -93,6 +94,11 @@ const BUILTIN_CHECK = {
93
94
  s._rejectSeqAsSlice(at[i], e.args[i], "a spread argument");
94
95
  if (!e.args[i]._spread && at[i]) {
95
96
  if (is64) e.args[i]._is64Bit = true;
97
+ s._tagComplexConst(
98
+ s.resolveType(sb.elem),
99
+ s.resolveType(at[i]),
100
+ e.args[i],
101
+ );
96
102
  s._markIfaceBox(
97
103
  s.resolveType(sb.elem),
98
104
  s.resolveType(at[i]),
@@ -106,7 +112,10 @@ const BUILTIN_CHECK = {
106
112
  copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
107
113
  delete: (s, _n, e, at) => {
108
114
  const mt = s._deepUnderlying(at[0]);
109
- if (mt?.kind === "map" && e.args[1]) s._markIfaceKey(mt, e.args[1], at[1]);
115
+ if (mt?.kind === "map" && e.args[1]) {
116
+ s._tagFloat32Const(s.resolveType(mt.key), at[1], e.args[1]);
117
+ s._markIfaceKey(mt, e.args[1], at[1]);
118
+ }
110
119
  return VOID;
111
120
  },
112
121
  make: (s, _n, e, _at, sc) => {
@@ -117,6 +126,8 @@ const BUILTIN_CHECK = {
117
126
  // `make(S, n)` with S a type parameter makes its core type.
118
127
  const core = made.kind === "typeParam" ? s._coreType(made, sc) : null;
119
128
  if (core) ta._coreType = core;
129
+ for (const size of e.args.slice(1, 3))
130
+ s._checkIntegerConstOperand(size, "size", sc);
120
131
  return made;
121
132
  },
122
133
  new: (s, _n, e, _at, sc) => {
@@ -134,14 +145,37 @@ const BUILTIN_CHECK = {
134
145
  },
135
146
  recover: () => ANY,
136
147
  error: () => ERROR,
137
- min: (_s, _n, _e, at) => minMaxType(at),
138
- max: (_s, _n, _e, at) => minMaxType(at),
148
+ min: (s, _n, e, at) => s._checkMinMax(e, at),
149
+ max: (s, _n, e, at) => s._checkMinMax(e, at),
139
150
  clear: () => VOID,
140
151
  complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
141
152
  real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
142
153
  imag: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
143
154
  };
144
155
 
156
+ // Type kinds that are never valid as an index, slice bound or `make` size.
157
+ const NON_INTEGER_KINDS = new Set([
158
+ "struct",
159
+ "pointer",
160
+ "slice",
161
+ "map",
162
+ "func",
163
+ "array",
164
+ "interface",
165
+ "simd",
166
+ ]);
167
+
168
+ // Basic types that are never valid as an index, slice bound or `make` size.
169
+ const NON_INTEGER_BASICS = new Set([
170
+ "float32",
171
+ "float64",
172
+ "complex64",
173
+ "complex128",
174
+ "bool",
175
+ "string",
176
+ "any",
177
+ ]);
178
+
145
179
  // Method-name dispatch for _checkExpr — string values add no static call edges.
146
180
  const CHECK_EXPR_DELEGATE = {
147
181
  UnaryExpr: "_checkUnaryExpr",
@@ -171,7 +205,7 @@ export const expressionCheckMethods = {
171
205
  case "BasicLit": {
172
206
  switch (expr.litKind) {
173
207
  case "INT":
174
- return UNTYPED_INT;
208
+ return expr.rune ? UNTYPED_RUNE : UNTYPED_INT;
175
209
  case "FLOAT":
176
210
  return UNTYPED_FLOAT;
177
211
  case "STRING":
@@ -260,6 +294,10 @@ export const expressionCheckMethods = {
260
294
  this._retypeUntypedShift(lt, rt, expr.right);
261
295
  this._retypeUntypedShift(rt, lt, expr.left);
262
296
  }
297
+ this._tagFloat32Const(lt, rt, expr.right);
298
+ this._tagFloat32Const(rt, lt, expr.left);
299
+ this._tagComplex64Const(lt, rt, expr.right);
300
+ this._tagComplex64Const(rt, lt, expr.left);
263
301
  if (is64BitInt(lt) && rt?.kind === "untyped" && expr.right)
264
302
  expr.right._is64Bit = true;
265
303
  if (is64BitInt(rt) && lt?.kind === "untyped" && expr.left)
@@ -310,7 +348,8 @@ export const expressionCheckMethods = {
310
348
 
311
349
  _checkBoolUnaryOp(ot, expr) {
312
350
  if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
313
- return BOOL;
351
+ // `!` keeps an untyped or defined bool type.
352
+ return isBool(ot) ? ot : BOOL;
314
353
  },
315
354
 
316
355
  _checkNumericUnaryOp(ot, expr) {
@@ -826,6 +865,15 @@ export const expressionCheckMethods = {
826
865
  // with its dynamic type, so `L("1")`, `N(1)`, `"1"` and `1` stay distinct.
827
866
  // A named basic value is boxed first, exactly as in any interface
828
867
  // conversion; other values are keyed as they are.
868
+ // Untyped constant operands take the result type, so a float32 result
869
+ // rounds them like the typed operands.
870
+ _checkMinMax(e, at) {
871
+ const rt = minMaxType(at);
872
+ for (let i = 0; i < e.args.length; i++)
873
+ this._tagFloat32Const(rt, at[i], e.args[i]);
874
+ return rt;
875
+ },
876
+
829
877
  _markIfaceKey(mapType, node, srcType) {
830
878
  const key = this.resolveType(mapType.key);
831
879
  const kb = key?.kind === "named" ? this.resolveType(key.underlying) : key;
@@ -866,6 +914,8 @@ export const expressionCheckMethods = {
866
914
  if (bt?._tainted) return TAINTED_ANY;
867
915
  if (isAny(bt)) return ANY;
868
916
  const btu = this._coreUnderlying(bt, scope);
917
+ if (btu.kind !== "map")
918
+ this._checkIntegerConstOperand(expr.index, "index", scope);
869
919
  if (btu.kind === "slice" || btu.kind === "array") {
870
920
  this._checkArrayBoundsExpr(btu, bt, expr, scope);
871
921
  return btu.elem;
@@ -955,15 +1005,61 @@ export const expressionCheckMethods = {
955
1005
  return t.base;
956
1006
  },
957
1007
 
1008
+ // An index, slice bound or `make` size that is a constant must be an
1009
+ // integer (`s[1 == 1]`, `make([]int, "a")`).
1010
+ // A typed operand must have an integer type too (`s[f]` with `f float64`).
1011
+ _checkIntegerConstOperand(operand, what, scope) {
1012
+ const kind = this._nonIntegerConst(operand, scope);
1013
+ if (kind) {
1014
+ this.err(
1015
+ `invalid argument: ${what} ${exprText(operand)} (${kind}) must be integer`,
1016
+ operand,
1017
+ );
1018
+ return;
1019
+ }
1020
+ const t = operand?._type;
1021
+ if (!this._isNonIntegerType(t, scope)) return;
1022
+ this.err(
1023
+ `invalid argument: ${what} ${exprText(operand)} (${operandKind(operand, typeStr(t), false)}) must be integer`,
1024
+ operand,
1025
+ );
1026
+ },
1027
+
1028
+ // Whether `t` is a type (or a defined type over one) that is not an
1029
+ // integer: a float, complex, bool, string, `any`, struct, pointer, slice,
1030
+ // map, func, array, interface, simd vector or a type parameter whose type
1031
+ // set is not all integers. Untyped constants are judged by
1032
+ // `_nonIntegerConst`, so only untyped non-integers that reach here
1033
+ // (`s[a == b]`) count.
1034
+ _isNonIntegerType(t, scope) {
1035
+ if (!t || t._tainted) return false;
1036
+ // A type parameter is an integer only when every type in its type set
1037
+ // is (`~int | ~uint8`); `any`, `comparable` and method-only
1038
+ // constraints have no integer type set.
1039
+ if (t.kind === "typeParam") {
1040
+ const terms = this._constraintTerms(t.constraint, new Set(), scope);
1041
+ return !(terms?.length > 0 && terms.every(isInteger));
1042
+ }
1043
+ if (t.kind === "untyped")
1044
+ return (
1045
+ t.base === "bool" || t.base === "string" || t.base === "complex128"
1046
+ );
1047
+ const u = this._deepUnderlying(t);
1048
+ if (u?.kind === "basic") return NON_INTEGER_BASICS.has(u.name);
1049
+ return NON_INTEGER_KINDS.has(u?.kind);
1050
+ },
1051
+
958
1052
  _checkSliceExpr(expr, scope) {
959
1053
  const bt = this._derefArrayPtr(
960
1054
  expr,
961
1055
  "expr",
962
1056
  this.checkExpr(expr.expr, scope),
963
1057
  );
964
- if (expr.low) this.checkExpr(expr.low, scope);
965
- if (expr.high) this.checkExpr(expr.high, scope);
966
- if (expr.max) this.checkExpr(expr.max, scope);
1058
+ for (const bound of [expr.low, expr.high, expr.max]) {
1059
+ if (!bound) continue;
1060
+ this.checkExpr(bound, scope);
1061
+ this._checkIntegerConstOperand(bound, "index", scope);
1062
+ }
967
1063
  if (bt?._tainted) return TAINTED_ANY;
968
1064
  if (isAny(bt)) return ANY;
969
1065
  const btu = this._coreUnderlying(bt, scope);
@@ -1070,6 +1166,16 @@ export const expressionCheckMethods = {
1070
1166
  // `any(x)` / `error(x)`: box x as an implicit conversion would.
1071
1167
  this._markIfaceBox(targetType, srcType, expr.expr);
1072
1168
  this._rejectNilConversion(srcType, targetType, expr);
1169
+ if (this._checkIfaceConversion(srcType, targetType, expr, expr.expr))
1170
+ return targetType;
1171
+ if (
1172
+ srcType &&
1173
+ targetType &&
1174
+ !isAny(srcType) &&
1175
+ !isAny(targetType) &&
1176
+ this._checkIfaceTargetConversion(srcType, targetType, expr.expr)
1177
+ )
1178
+ return targetType;
1073
1179
  const srcResolved =
1074
1180
  srcType?.kind === "named" ? srcType.underlying : srcType;
1075
1181
  const tgtResolved =
@@ -1090,6 +1196,8 @@ export const expressionCheckMethods = {
1090
1196
  expr,
1091
1197
  );
1092
1198
  }
1199
+ // `complex64(1 + 0.01i)`: the constant's components round to float32.
1200
+ this._tagComplex64Const(targetType, srcType, expr.expr);
1093
1201
  if (tgtResolved?.kind === "slice")
1094
1202
  this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
1095
1203
  if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
@@ -1138,8 +1246,25 @@ export const expressionCheckMethods = {
1138
1246
  return true;
1139
1247
  },
1140
1248
 
1249
+ // `I(x)` with I an interface type converts exactly when x is assignable to
1250
+ // I, so the method-set check (and its wording) is the assignment one.
1251
+ _checkIfaceTargetConversion(srcType, targetType, operand) {
1252
+ // `error("msg")` builds an error value (GoFront extension).
1253
+ if (targetType === ERROR && isString(srcType)) return false;
1254
+ const base = this.resolveType(
1255
+ targetType.kind === "named" ? targetType.underlying : targetType,
1256
+ );
1257
+ if (base?.kind !== "interface" || !operand) return false;
1258
+ this.assertAssignable(targetType, srcType, operand, "conversion");
1259
+ return true;
1260
+ },
1261
+
1141
1262
  _checkConversionValidity(srcType, targetType, expr) {
1263
+ if (this._checkIfaceConversion(srcType, targetType, expr, expr.args?.[0]))
1264
+ return;
1142
1265
  if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
1266
+ if (this._checkIfaceTargetConversion(srcType, targetType, expr.args?.[0]))
1267
+ return;
1143
1268
  const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
1144
1269
  const tgtResolved =
1145
1270
  targetType.kind === "named" ? targetType.underlying : targetType;
@@ -1813,6 +1938,14 @@ export const expressionCheckMethods = {
1813
1938
  const node = expr._multiForward ? null : expr.args[i];
1814
1939
  this.assertAssignable(targetParam, argTypes[i], node, "argument");
1815
1940
  }
1941
+ // `g(f())`: per-element box marks for lowering to split the forward.
1942
+ if (
1943
+ expr._multiForward &&
1944
+ !fnType.variadic &&
1945
+ !this._skipIfaceMark &&
1946
+ fnType.params.length === argTypes.length
1947
+ )
1948
+ this._markForwardedElems(fnType.params, argTypes, expr.args[0]);
1816
1949
 
1817
1950
  this._checkSimdLaneArgs(expr, scope);
1818
1951
  },
@@ -1878,7 +2011,28 @@ export const expressionCheckMethods = {
1878
2011
  return null;
1879
2012
  },
1880
2013
 
1881
- _checkBuiltinLen(name, expr, argTypes) {
2014
+ _capOperandKind(operand, type) {
2015
+ const untypedConst = type?.kind === "untyped" && type.base !== "nil";
2016
+ return untypedConst
2017
+ ? operandKind(operand, type.base, true)
2018
+ : operandKind(operand, typeStr(type), false);
2019
+ },
2020
+
2021
+ // `cap` takes a slice or an array (a pointer to an array is dereferenced
2022
+ // before this check). A type parameter needs a slice or array core type;
2023
+ // one with no single core type is left to instantiation. Unknown and
2024
+ // `any` operands (JS interop) are not judged.
2025
+ _capOperandOk(t, scope) {
2026
+ if (!t || t._tainted || isAny(t)) return true;
2027
+ if (t.kind === "typeParam") {
2028
+ const core = this._coreType(t, scope);
2029
+ return !core || core.kind === "slice" || core.kind === "array";
2030
+ }
2031
+ const kind = this._deepUnderlying(t)?.kind;
2032
+ return kind === "slice" || kind === "array";
2033
+ },
2034
+
2035
+ _checkBuiltinLen(name, expr, argTypes, scope) {
1882
2036
  this._rejectSeqAsSlice(argTypes[0], expr.args?.[0], `${name}()`);
1883
2037
  const operand = expr.args?.[0];
1884
2038
  if (expr.args?.length === 1)
@@ -1891,17 +2045,14 @@ export const expressionCheckMethods = {
1891
2045
  if (expr.args.length === 1) expr.args[0] = operand;
1892
2046
  }
1893
2047
  if (name === "cap") {
1894
- if (
1895
- (argTypes[0]?.underlying ?? argTypes[0])?.kind === "map" ||
1896
- isString(argTypes[0])
1897
- ) {
2048
+ if (sharedInfo(argTypes[0])) {
1898
2049
  this.err(
1899
- `invalid argument: ${exprText(expr.args?.[0])} (${operandKind(expr.args?.[0], typeStr(argTypes[0]), false)}) for cap`,
2050
+ `cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
1900
2051
  expr,
1901
2052
  );
1902
- } else if (sharedInfo(argTypes[0])) {
2053
+ } else if (!this._capOperandOk(argTypes[0], scope)) {
1903
2054
  this.err(
1904
- `cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
2055
+ `invalid argument: ${exprText(expr.args?.[0])} (${this._capOperandKind(expr.args?.[0], argTypes[0])}) for cap`,
1905
2056
  expr,
1906
2057
  );
1907
2058
  }
@@ -1935,6 +2086,9 @@ export const expressionCheckMethods = {
1935
2086
  expr,
1936
2087
  );
1937
2088
  }
2089
+ // An untyped operand takes type float32 (`complex(f, 0.01)`).
2090
+ this._tagFloat32Const(FLOAT32, crt, expr.args[0]);
2091
+ this._tagFloat32Const(FLOAT32, cit, expr.args[1]);
1938
2092
  return COMPLEX64;
1939
2093
  }
1940
2094
  return COMPLEX128;