gofront 0.0.8 → 1.0.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 (51) hide show
  1. package/README.md +88 -12
  2. package/package.json +8 -4
  3. package/src/codegen/expressions.js +520 -1717
  4. package/src/{codegen.js → codegen/index.js} +155 -184
  5. package/src/codegen/runtime.js +151 -0
  6. package/src/codegen/statements.js +305 -296
  7. package/src/codegen/stdlib/builder.js +36 -0
  8. package/src/codegen/stdlib/bytes.js +62 -0
  9. package/src/codegen/stdlib/errors.js +17 -0
  10. package/src/codegen/stdlib/fmt.js +101 -0
  11. package/src/codegen/stdlib/gom.js +152 -0
  12. package/src/codegen/stdlib/html.js +17 -0
  13. package/src/codegen/stdlib/index.js +95 -0
  14. package/src/codegen/stdlib/io.js +33 -0
  15. package/src/codegen/stdlib/maps.js +42 -0
  16. package/src/codegen/stdlib/math.js +53 -0
  17. package/src/codegen/stdlib/os.js +10 -0
  18. package/src/codegen/stdlib/path.js +31 -0
  19. package/src/codegen/stdlib/rand.js +24 -0
  20. package/src/codegen/stdlib/regexp.js +51 -0
  21. package/src/codegen/stdlib/slices.js +48 -0
  22. package/src/codegen/stdlib/sort.js +28 -0
  23. package/src/codegen/stdlib/strconv.js +30 -0
  24. package/src/codegen/stdlib/strings.js +83 -0
  25. package/src/codegen/stdlib/time.js +54 -0
  26. package/src/codegen/stdlib/unicode.js +22 -0
  27. package/src/codegen/stdlib/utf8.js +23 -0
  28. package/src/codegen/templ.js +281 -0
  29. package/src/compiler.js +102 -62
  30. package/src/dts-parser.js +355 -371
  31. package/src/index.js +22 -54
  32. package/src/lexer.js +200 -286
  33. package/src/minifier.js +305 -318
  34. package/src/parser/expressions.js +54 -36
  35. package/src/{parser.js → parser/index.js} +12 -5
  36. package/src/parser/statements.js +29 -35
  37. package/src/parser/types.js +31 -29
  38. package/src/templ-lexer.js +737 -0
  39. package/src/templ-parser.js +235 -0
  40. package/src/tokens.js +109 -0
  41. package/src/typechecker/assignability.js +258 -0
  42. package/src/typechecker/expressions.js +559 -549
  43. package/src/typechecker/index.js +555 -0
  44. package/src/typechecker/resolve.js +421 -0
  45. package/src/typechecker/statements.js +267 -280
  46. package/src/typechecker/stdlib/core.js +592 -0
  47. package/src/typechecker/stdlib/extended.js +332 -0
  48. package/src/typechecker/stdlib.js +9 -1060
  49. package/src/typechecker/termination.js +83 -0
  50. package/src/typechecker/types.js +6 -3
  51. package/src/typechecker.js +0 -1271
@@ -26,6 +26,60 @@ import {
26
26
  VOID,
27
27
  } from "./types.js";
28
28
 
29
+ // Dispatch table for builtin function type checking.
30
+ // Each value is (self, name, expr, argTypes, scope) => returnType.
31
+ const BUILTIN_CHECK = {
32
+ len: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
33
+ cap: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
34
+ append: (s, _n, e, at) => {
35
+ const st = at[0] ?? ANY;
36
+ if (isArray(st)) {
37
+ s.err(`cannot append to array (type ${typeStr(st)})`, e);
38
+ return st;
39
+ }
40
+ return st;
41
+ },
42
+ copy: () => INT,
43
+ delete: () => VOID,
44
+ make: (s, _n, e, _at, sc) => {
45
+ if (e.args.length < 1) return ANY;
46
+ const ta = e.args[0];
47
+ const tn = ta.kind === "TypeExpr" ? ta.type : ta;
48
+ return s.resolveTypeNode(tn, sc) ?? ANY;
49
+ },
50
+ new: (s, _n, e, _at, sc) => {
51
+ if (e.args.length < 1) return ANY;
52
+ const inner = s.resolveTypeNode(e.args[0], sc);
53
+ return { kind: "pointer", base: inner };
54
+ },
55
+ print: () => VOID,
56
+ println: () => VOID,
57
+ panic: () => VOID,
58
+ recover: () => ANY,
59
+ error: () => ERROR,
60
+ min: (_s, _n, _e, at) => at[0] ?? ANY,
61
+ max: (_s, _n, _e, at) => at[0] ?? ANY,
62
+ clear: () => VOID,
63
+ complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
64
+ real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
65
+ imag: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
66
+ };
67
+
68
+ // Method-name dispatch for _checkExpr — string values add no static call edges.
69
+ const CHECK_EXPR_DELEGATE = {
70
+ UnaryExpr: "_checkUnaryExpr",
71
+ CallExpr: "checkCall",
72
+ SelectorExpr: "_checkSelectorExpr",
73
+ IndexExpr: "_checkIndexExpr",
74
+ SliceExpr: "_checkSliceExpr",
75
+ CompositeLit: "checkCompositeLit",
76
+ FuncLit: "_checkFuncLit",
77
+ TypeConversion: "_checkTypeConversion",
78
+ TypeAssertExpr: "_checkTypeAssertExpr",
79
+ RangeExpr: "_checkRangeExpr",
80
+ InstantiationExpr: "_checkInstantiationExpr",
81
+ };
82
+
29
83
  export const expressionCheckMethods = {
30
84
  checkExpr(expr, scope) {
31
85
  const t = this._checkExpr(expr, scope);
@@ -50,49 +104,14 @@ export const expressionCheckMethods = {
50
104
  }
51
105
  break;
52
106
  }
53
-
54
107
  case "ImagLit":
55
108
  return UNTYPED_COMPLEX;
56
-
57
109
  case "Ident": {
58
110
  const t = scope.lookup(expr.name);
59
111
  if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
60
- // Mark type-name identifiers so SelectorExpr can detect method expressions
61
112
  if (this.types.has(expr.name)) expr._isTypeRef = true;
62
113
  return t;
63
114
  }
64
-
65
- case "UnaryExpr": {
66
- const ot = this.checkExpr(expr.operand, scope);
67
- if (expr.op === "!") {
68
- if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
69
- return BOOL;
70
- }
71
- if (expr.op === "-" || expr.op === "+") {
72
- if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
73
- this.err(`${expr.op} requires numeric`, expr);
74
- return ot;
75
- }
76
- if (expr.op === "&") {
77
- // Mark the operand as address-taken for codegen
78
- if (expr.operand.kind === "Ident") {
79
- expr.operand._addressTaken = true;
80
- const sym = scope.lookup(expr.operand.name);
81
- if (sym) sym._addressTaken = true;
82
- }
83
- return { kind: "pointer", base: ot };
84
- }
85
- if (expr.op === "*") {
86
- if (ot === ANY) return ANY;
87
- if (ot.kind === "pointer") return ot.base;
88
- if (ot.kind === "named" && ot.underlying?.kind === "pointer")
89
- return ot.underlying.base;
90
- this.err(`cannot dereference non-pointer type ${typeStr(ot)}`, expr);
91
- return ANY;
92
- }
93
- return ot;
94
- }
95
-
96
115
  case "BinaryExpr": {
97
116
  const lt = this.checkExpr(expr.left, scope);
98
117
  const rt = this.checkExpr(expr.right, scope);
@@ -100,285 +119,298 @@ export const expressionCheckMethods = {
100
119
  return this.binaryResultType(expr.op, ANY, ANY, expr);
101
120
  return this.binaryResultType(expr.op, lt, rt, expr);
102
121
  }
103
-
104
- case "CallExpr": {
105
- return this.checkCall(expr, scope);
122
+ case "AwaitExpr":
123
+ return this.checkExpr(expr.expr, scope);
124
+ case "InstantiationExpr":
125
+ return this._checkInstantiationExpr(expr, scope);
126
+ default: {
127
+ const m = CHECK_EXPR_DELEGATE[expr.kind];
128
+ if (m) return this[m](expr, scope);
129
+ return ANY;
106
130
  }
131
+ }
132
+ return ANY;
133
+ },
107
134
 
108
- case "SelectorExpr": {
109
- const baseType = this.checkExpr(expr.expr, scope);
110
- // Method expression: TypeName.MethodName → func(ReceiverType, ...params) ret
111
- if (expr.expr._isTypeRef) {
112
- let base = baseType.kind === "named" ? baseType.underlying : baseType;
113
- base = this.resolveType(base);
114
- let methodType = null;
115
- if (base?.kind === "struct" && base.methods?.has(expr.field)) {
116
- methodType = base.methods.get(expr.field);
117
- } else if (
118
- baseType.kind === "named" &&
119
- baseType.methods?.has(expr.field)
120
- ) {
121
- methodType = baseType.methods.get(expr.field);
122
- }
123
- if (methodType) {
124
- // Return a func type with the receiver prepended to params
125
- expr._isMethodExpr = true;
126
- expr._methodExprRecv = baseType;
127
- return {
128
- kind: "func",
129
- params: [baseType, ...(methodType.params ?? [])],
130
- returns: methodType.returns ?? [],
131
- variadic: methodType.variadic,
132
- };
133
- }
134
- }
135
- const ft = this.fieldType(baseType, expr.field, expr);
136
- // Mark method selectors so codegen can emit .bind() when used as a value
137
- if (ft?.kind === "func") {
138
- let base = baseType.kind === "named" ? baseType.underlying : baseType;
139
- base = this.resolveType(base);
140
- if (
141
- (base?.kind === "struct" && base.methods?.has(expr.field)) ||
142
- (baseType.kind === "named" && baseType.methods?.has(expr.field))
143
- ) {
144
- expr._isMethodValue = true;
145
- }
146
- }
147
- return ft;
148
- }
135
+ _checkDeref(ot, expr) {
136
+ if (ot === ANY) return ANY;
137
+ if (ot.kind === "pointer") return ot.base;
138
+ if (ot.kind === "named" && ot.underlying?.kind === "pointer")
139
+ return ot.underlying.base;
140
+ this.err(`cannot dereference non-pointer type ${typeStr(ot)}`, expr);
141
+ return ANY;
142
+ },
149
143
 
150
- case "IndexExpr": {
151
- const bt = this.checkExpr(expr.expr, scope);
152
- this.checkExpr(expr.index, scope);
153
- if (isAny(bt)) return ANY;
154
- // Named types (e.g. type Nums []int) are indexable via their underlying
155
- const btu =
156
- bt.kind === "named"
157
- ? (this.resolveType(bt.underlying) ?? bt.underlying)
158
- : bt;
159
- if (btu.kind === "slice" || btu.kind === "array") {
160
- // Compile-time bounds checking for arrays
161
- if (btu.kind === "array" && btu.size != null) {
162
- const idx = this._constIntValue(expr.index);
163
- if (idx !== null) {
164
- if (idx < 0) {
165
- this.err(
166
- `invalid array index ${idx} (index must not be negative)`,
167
- expr,
168
- );
169
- } else if (idx >= btu.size) {
170
- this.err(
171
- `invalid array index ${idx} (out of bounds for ${typeStr(bt)})`,
172
- expr,
173
- );
174
- }
175
- }
176
- }
177
- return btu.elem;
178
- }
179
- if (btu.kind === "map") {
180
- expr._mapValueType = btu.value; // for codegen zero-value fallback
181
- return btu.value;
182
- }
183
- if (isString(bt)) return INT; // byte
184
- return this.err(`Cannot index type ${typeStr(bt)}`, expr);
185
- }
144
+ _checkBoolUnaryOp(ot, expr) {
145
+ if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
146
+ return BOOL;
147
+ },
186
148
 
187
- case "SliceExpr": {
188
- const bt = this.checkExpr(expr.expr, scope);
189
- if (expr.low) this.checkExpr(expr.low, scope);
190
- if (expr.high) this.checkExpr(expr.high, scope);
191
- if (expr.max) this.checkExpr(expr.max, scope);
192
- if (isAny(bt)) return ANY;
193
- if (bt.kind === "slice" || bt.kind === "array")
194
- return { kind: "slice", elem: bt.elem };
195
- if (isString(bt)) return STRING;
196
- return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
197
- }
149
+ _checkNumericUnaryOp(ot, expr) {
150
+ if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
151
+ this.err(`${expr.op} requires numeric`, expr);
152
+ return ot;
153
+ },
198
154
 
199
- case "CompositeLit": {
200
- return this.checkCompositeLit(expr, scope);
201
- }
155
+ _checkAddressOfExpr(expr, scope, ot) {
156
+ if (expr.operand.kind === "Ident") {
157
+ expr.operand._addressTaken = true;
158
+ const sym = scope.lookup(expr.operand.name);
159
+ if (sym) sym._addressTaken = true;
160
+ }
161
+ return { kind: "pointer", base: ot };
162
+ },
202
163
 
203
- case "FuncLit": {
204
- const inner = new Scope(scope);
205
- for (const p of expr.params)
206
- inner.define(
207
- p.name,
208
- p.type ? this.resolveTypeNode(p.type, scope) : ANY,
209
- );
210
- const ret = expr.returnType
211
- ? this.resolveTypeNode(expr.returnType, scope)
212
- : VOID;
213
- const savedDefer = this._deferCount;
214
- this._deferCount = 0;
215
- this.checkBlock(expr.body, inner, ret);
216
- this._reportUnused(inner, expr);
217
- if (this._deferCount > 0) expr.body._hasDefer = true;
218
- this._deferCount = savedDefer;
219
- const paramTypes = expr.params.map((p) =>
220
- p.type ? this.resolveTypeNode(p.type, scope) : ANY,
221
- );
164
+ _checkUnaryExpr(expr, scope) {
165
+ const ot = this.checkExpr(expr.operand, scope);
166
+ if (expr.op === "!") return this._checkBoolUnaryOp(ot, expr);
167
+ if (expr.op === "-" || expr.op === "+")
168
+ return this._checkNumericUnaryOp(ot, expr);
169
+ if (expr.op === "&") return this._checkAddressOfExpr(expr, scope, ot);
170
+ if (expr.op === "*") return this._checkDeref(ot, expr);
171
+ return ot;
172
+ },
173
+
174
+ _isStructOrNamedMethod(baseType, base, field) {
175
+ return (
176
+ (base?.kind === "struct" && base.methods?.has(field)) ||
177
+ (baseType.kind === "named" && baseType.methods?.has(field))
178
+ );
179
+ },
180
+
181
+ _resolveMethodExprType(baseType, base, field) {
182
+ if (!this._isStructOrNamedMethod(baseType, base, field)) return null;
183
+ return base?.methods?.get(field) ?? baseType.methods?.get(field);
184
+ },
185
+
186
+ _checkSelectorExpr(expr, scope) {
187
+ const baseType = this.checkExpr(expr.expr, scope);
188
+ if (expr.expr._isTypeRef) {
189
+ let base = baseType.kind === "named" ? baseType.underlying : baseType;
190
+ base = this.resolveType(base);
191
+ const methodType = this._resolveMethodExprType(
192
+ baseType,
193
+ base,
194
+ expr.field,
195
+ );
196
+ if (methodType) {
197
+ expr._isMethodExpr = true;
198
+ expr._methodExprRecv = baseType;
222
199
  return {
223
200
  kind: "func",
224
- params: paramTypes,
225
- returns: [ret],
226
- async: expr.async,
201
+ params: [baseType, ...(methodType.params ?? [])],
202
+ returns: methodType.returns ?? [],
203
+ variadic: methodType.variadic,
227
204
  };
228
205
  }
206
+ }
207
+ const ft = this.fieldType(baseType, expr.field, expr);
208
+ if (ft?.kind === "func") {
209
+ let base = baseType.kind === "named" ? baseType.underlying : baseType;
210
+ base = this.resolveType(base);
211
+ if (this._isStructOrNamedMethod(baseType, base, expr.field))
212
+ expr._isMethodValue = true;
213
+ }
214
+ return ft;
215
+ },
229
216
 
230
- case "AwaitExpr": {
231
- // await unwraps whatever the expression produces — no Promise type modelling
232
- return this.checkExpr(expr.expr, scope);
233
- }
217
+ _checkArrayBoundsExpr(btu, bt, expr) {
218
+ if (btu.kind !== "array" || btu.size == null) return;
219
+ const idx = this._constIntValue(expr.index);
220
+ if (idx === null) return;
221
+ if (idx < 0)
222
+ this.err(`invalid array index ${idx} (index must not be negative)`, expr);
223
+ else if (idx >= btu.size)
224
+ this.err(
225
+ `invalid array index ${idx} (out of bounds for ${typeStr(bt)})`,
226
+ expr,
227
+ );
228
+ },
234
229
 
235
- case "TypeConversion": {
236
- const srcType = this.checkExpr(expr.expr, scope);
237
- const targetType = this.resolveTypeNode(expr.targetType, scope);
238
- const srcResolved =
239
- srcType?.kind === "named" ? srcType.underlying : srcType;
240
- const tgtResolved =
241
- targetType?.kind === "named" ? targetType.underlying : targetType;
242
- // Reject float64(complex) — must use real()/imag()
243
- if (isNumeric(targetType) && isComplex(srcType)) {
244
- this.err(
245
- `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
246
- expr,
247
- );
248
- }
249
- // Allow complex128/64(numeric) and complex128/64(complex)
250
- if (isComplex(targetType)) {
251
- if (!isComplexOrNumeric(srcType) && !isAny(srcType)) {
252
- this.err(
253
- `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
254
- expr,
255
- );
256
- }
257
- }
258
- // Slice → array conversion: [N]T(slice)
259
- if (tgtResolved?.kind === "array" && srcResolved?.kind === "slice") {
260
- const tgtElem = tgtResolved.elem;
261
- const srcElem = srcResolved.elem;
262
- if (
263
- tgtElem &&
264
- srcElem &&
265
- !isAny(tgtElem) &&
266
- !isAny(srcElem) &&
267
- typeStr(tgtElem) !== typeStr(srcElem)
268
- ) {
269
- this.err(
270
- `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
271
- expr,
272
- );
273
- }
274
- return targetType;
275
- }
276
- // Array → slice conversion: []T(array)
277
- if (tgtResolved?.kind === "slice" && srcResolved?.kind === "array") {
278
- const tgtElem = tgtResolved.elem;
279
- const srcElem = srcResolved.elem;
280
- if (
281
- tgtElem &&
282
- srcElem &&
283
- !isAny(tgtElem) &&
284
- !isAny(srcElem) &&
285
- typeStr(tgtElem) !== typeStr(srcElem)
286
- ) {
287
- this.err(
288
- `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
289
- expr,
290
- );
291
- }
292
- return targetType;
293
- }
294
- // Reject conversions between array and incompatible types
295
- if (tgtResolved?.kind === "array" && srcResolved?.kind !== "array") {
296
- if (!isAny(srcType)) {
297
- this.err(
298
- `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
299
- expr,
300
- );
301
- }
302
- }
303
- return targetType;
304
- }
230
+ _checkIndexExpr(expr, scope) {
231
+ const bt = this.checkExpr(expr.expr, scope);
232
+ this.checkExpr(expr.index, scope);
233
+ if (isAny(bt)) return ANY;
234
+ const btu =
235
+ bt.kind === "named"
236
+ ? (this.resolveType(bt.underlying) ?? bt.underlying)
237
+ : bt;
238
+ if (btu.kind === "slice" || btu.kind === "array") {
239
+ this._checkArrayBoundsExpr(btu, bt, expr);
240
+ return btu.elem;
241
+ }
242
+ if (btu.kind === "map") {
243
+ expr._mapValueType = btu.value;
244
+ return btu.value;
245
+ }
246
+ if (isString(bt)) return INT;
247
+ return this.err(`Cannot index type ${typeStr(bt)}`, expr);
248
+ },
305
249
 
306
- case "TypeAssertExpr": {
307
- const srcType = this.checkExpr(expr.expr, scope);
308
- const targetType = this.resolveTypeNode(expr.type, scope);
309
-
310
- // In Go, the source of a type assertion must be an interface type.
311
- // We allow `any` (which is GoFront's basic any) and interface types.
312
- if (srcType && !isAny(srcType)) {
313
- let underlying =
314
- srcType.kind === "named" ? srcType.underlying : srcType;
315
- underlying = this.resolveType(underlying);
316
- if (underlying?.kind !== "interface") {
317
- this.err(
318
- `invalid type assertion: ${typeStr(srcType)} is not an interface`,
319
- expr,
320
- );
321
- }
322
- }
250
+ _checkSliceExpr(expr, scope) {
251
+ const bt = this.checkExpr(expr.expr, scope);
252
+ if (expr.low) this.checkExpr(expr.low, scope);
253
+ if (expr.high) this.checkExpr(expr.high, scope);
254
+ if (expr.max) this.checkExpr(expr.max, scope);
255
+ if (isAny(bt)) return ANY;
256
+ if (bt.kind === "slice" || bt.kind === "array")
257
+ return { kind: "slice", elem: bt.elem };
258
+ if (isString(bt)) return STRING;
259
+ return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
260
+ },
323
261
 
324
- return targetType;
325
- }
262
+ _checkFuncLit(expr, scope) {
263
+ const inner = new Scope(scope);
264
+ for (const p of expr.params)
265
+ inner.define(p.name, p.type ? this.resolveTypeNode(p.type, scope) : ANY);
266
+ const ret = expr.returnType
267
+ ? this.resolveTypeNode(expr.returnType, scope)
268
+ : VOID;
269
+ const savedDefer = this._deferCount;
270
+ this._deferCount = 0;
271
+ this.checkBlock(expr.body, inner, ret);
272
+ this._reportUnused(inner, expr);
273
+ if (this._deferCount > 0) expr.body._hasDefer = true;
274
+ this._deferCount = savedDefer;
275
+ const paramTypes = expr.params.map((p) =>
276
+ p.type ? this.resolveTypeNode(p.type, scope) : ANY,
277
+ );
278
+ return {
279
+ kind: "func",
280
+ params: paramTypes,
281
+ returns: [ret],
282
+ async: expr.async,
283
+ };
284
+ },
326
285
 
327
- case "RangeExpr": {
328
- // Type-check the iterated expression so its _type is annotated
329
- const collType = this.checkExpr(expr.expr, scope);
330
- const resolved =
331
- collType?.kind === "named" ? collType.underlying : collType;
332
- // Return a tuple of (index/key type, value type) so DefineStmt can
333
- // assign the correct types to range variables.
334
- const isString =
335
- (resolved?.kind === "basic" && resolved.name === "string") ||
336
- (resolved?.kind === "untyped" && resolved.base === "string");
337
- if (isString) {
338
- // string range: (int index, rune value) — rune is an alias for int
339
- return { kind: "tuple", types: [INT, INT] };
340
- }
341
- if (resolved?.kind === "slice" || resolved?.kind === "array") {
342
- return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
343
- }
344
- if (resolved?.kind === "map") {
345
- return {
346
- kind: "tuple",
347
- types: [resolved.key ?? ANY, resolved.value ?? ANY],
348
- };
349
- }
350
- // integer range or unknown — return single-element tuple
351
- return { kind: "tuple", types: [collType] };
352
- }
286
+ _checkElemTypeCompatible(tgtElem, srcElem, srcType, targetType, expr) {
287
+ if (
288
+ tgtElem &&
289
+ srcElem &&
290
+ !isAny(tgtElem) &&
291
+ !isAny(srcElem) &&
292
+ typeStr(tgtElem) !== typeStr(srcElem)
293
+ )
294
+ this.err(
295
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
296
+ expr,
297
+ );
298
+ },
353
299
 
354
- case "InstantiationExpr": {
355
- // Generic instantiation: Foo[int] or Foo[int, string]
356
- const baseType = this.checkExpr(expr.expr, scope);
357
- if (baseType?.kind === "generic") {
358
- const typeArgs = expr.typeArgs.map((ta) =>
359
- this.resolveTypeNode(ta, scope),
360
- );
361
- // Check constraints
362
- for (
363
- let i = 0;
364
- i < baseType.typeParams.length && i < typeArgs.length;
365
- i++
366
- ) {
367
- const tp = baseType.typeParams[i];
368
- const constraint = tp.constraint
369
- ? this.resolveTypeNode(tp.constraint, scope)
370
- : null;
371
- if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
372
- }
373
- return this.instantiateGenericFunc(baseType, typeArgs);
374
- }
375
- return baseType;
300
+ _isMutualArraySlice(tgt, src) {
301
+ return (
302
+ (tgt?.kind === "array" && src?.kind === "slice") ||
303
+ (tgt?.kind === "slice" && src?.kind === "array")
304
+ );
305
+ },
306
+
307
+ _isInvalidArrayConv(tgt, src, srcType) {
308
+ return tgt?.kind === "array" && src?.kind !== "array" && !isAny(srcType);
309
+ },
310
+
311
+ _checkTypeConversion(expr, scope) {
312
+ const srcType = this.checkExpr(expr.expr, scope);
313
+ const targetType = this.resolveTypeNode(expr.targetType, scope);
314
+ const srcResolved =
315
+ srcType?.kind === "named" ? srcType.underlying : srcType;
316
+ const tgtResolved =
317
+ targetType?.kind === "named" ? targetType.underlying : targetType;
318
+ if (isNumeric(targetType) && isComplex(srcType)) {
319
+ this.err(
320
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
321
+ expr,
322
+ );
323
+ }
324
+ if (
325
+ isComplex(targetType) &&
326
+ !isComplexOrNumeric(srcType) &&
327
+ !isAny(srcType)
328
+ ) {
329
+ this.err(
330
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
331
+ expr,
332
+ );
333
+ }
334
+ if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
335
+ this._checkElemTypeCompatible(
336
+ tgtResolved.elem,
337
+ srcResolved.elem,
338
+ srcType,
339
+ targetType,
340
+ expr,
341
+ );
342
+ return targetType;
343
+ }
344
+ if (this._isInvalidArrayConv(tgtResolved, srcResolved, srcType)) {
345
+ this.err(
346
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
347
+ expr,
348
+ );
349
+ }
350
+ return targetType;
351
+ },
352
+
353
+ _checkTypeAssertExpr(expr, scope) {
354
+ const srcType = this.checkExpr(expr.expr, scope);
355
+ const targetType = this.resolveTypeNode(expr.type, scope);
356
+
357
+ // In Go, the source of a type assertion must be an interface type.
358
+ // We allow `any` (which is GoFront's basic any) and interface types.
359
+ if (srcType && !isAny(srcType)) {
360
+ let underlying = srcType.kind === "named" ? srcType.underlying : srcType;
361
+ underlying = this.resolveType(underlying);
362
+ if (underlying?.kind !== "interface") {
363
+ this.err(
364
+ `invalid type assertion: ${typeStr(srcType)} is not an interface`,
365
+ expr,
366
+ );
376
367
  }
368
+ }
377
369
 
378
- default:
379
- return ANY;
370
+ return targetType;
371
+ },
372
+
373
+ _rangeCollTypeTuple(resolved, collType) {
374
+ if (isString(resolved)) return { kind: "tuple", types: [INT, INT] };
375
+ if (resolved?.kind === "slice" || resolved?.kind === "array")
376
+ return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
377
+ if (resolved?.kind === "map")
378
+ return {
379
+ kind: "tuple",
380
+ types: [resolved.key ?? ANY, resolved.value ?? ANY],
381
+ };
382
+ return { kind: "tuple", types: [collType] };
383
+ },
384
+
385
+ _checkRangeExpr(expr, scope) {
386
+ const collType = this.checkExpr(expr.expr, scope);
387
+ const resolved =
388
+ collType?.kind === "named" ? collType.underlying : collType;
389
+ return this._rangeCollTypeTuple(resolved, collType);
390
+ },
391
+
392
+ _checkInstantiationExpr(expr, scope) {
393
+ // Generic instantiation: Foo[int] or Foo[int, string]
394
+ const baseType = this.checkExpr(expr.expr, scope);
395
+ if (baseType?.kind === "generic") {
396
+ const typeArgs = expr.typeArgs.map((ta) =>
397
+ this.resolveTypeNode(ta, scope),
398
+ );
399
+ // Check constraints
400
+ for (
401
+ let i = 0;
402
+ i < baseType.typeParams.length && i < typeArgs.length;
403
+ i++
404
+ ) {
405
+ const tp = baseType.typeParams[i];
406
+ const constraint = tp.constraint
407
+ ? this.resolveTypeNode(tp.constraint, scope)
408
+ : null;
409
+ if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
410
+ }
411
+ return this.instantiateGenericFunc(baseType, typeArgs);
380
412
  }
381
- return ANY;
413
+ return baseType;
382
414
  },
383
415
 
384
416
  _constIntValue(expr) {
@@ -395,11 +427,36 @@ export const expressionCheckMethods = {
395
427
  return null;
396
428
  },
397
429
 
430
+ _resolveCallArgTypes(expr, scope) {
431
+ if (expr.args.length === 1 && expr.args[0].kind === "CallExpr") {
432
+ const argType = this.checkExpr(expr.args[0], scope);
433
+ if (argType?.kind === "tuple") {
434
+ expr._multiForward = true;
435
+ return argType.types;
436
+ }
437
+ return [argType];
438
+ }
439
+ return expr.args.map((a) => this.checkExpr(a, scope));
440
+ },
441
+
442
+ _resolveCallFnType(fnType, argTypes, expr, scope) {
443
+ if (fnType.kind === "named" && fnType.underlying?.kind === "func")
444
+ fnType = fnType.underlying;
445
+ if (fnType.kind === "generic") {
446
+ const resolved = this._resolveGenericFnType(
447
+ fnType,
448
+ argTypes,
449
+ expr,
450
+ scope,
451
+ );
452
+ if (!resolved) return null;
453
+ fnType = resolved;
454
+ }
455
+ return fnType;
456
+ },
457
+
398
458
  checkCall(expr, scope) {
399
459
  let fnType = this.checkExpr(expr.func, scope);
400
-
401
- // Handle built-ins before evaluating args; some (e.g. new) take type names as
402
- // arguments, which would otherwise produce false "Undefined" errors.
403
460
  if (fnType.kind === "builtin") {
404
461
  const argTypes = expr.args.map((a, i) => {
405
462
  // new(T) — first arg is a type name, not a value expression
@@ -409,20 +466,7 @@ export const expressionCheckMethods = {
409
466
  return this.checkBuiltin(fnType.name, expr, argTypes, scope);
410
467
  }
411
468
 
412
- // Multi-value forwarding: f(g()) where g() returns multiple values
413
- // When a single argument is a multi-return call, flatten its tuple into args.
414
- let argTypes;
415
- if (expr.args.length === 1 && expr.args[0].kind === "CallExpr") {
416
- const argType = this.checkExpr(expr.args[0], scope);
417
- if (argType?.kind === "tuple") {
418
- expr._multiForward = true; // flag for codegen
419
- argTypes = argType.types;
420
- } else {
421
- argTypes = [argType];
422
- }
423
- } else {
424
- argTypes = expr.args.map((a) => this.checkExpr(a, scope));
425
- }
469
+ const argTypes = this._resolveCallArgTypes(expr, scope);
426
470
 
427
471
  if (isAny(fnType)) return ANY;
428
472
 
@@ -434,300 +478,266 @@ export const expressionCheckMethods = {
434
478
  return fnType;
435
479
  }
436
480
 
437
- // Named func types (e.g. type Handler func(int) int) are callable via variable
438
- if (fnType.kind === "named" && fnType.underlying?.kind === "func") {
439
- fnType = fnType.underlying;
481
+ fnType = this._resolveCallFnType(fnType, argTypes, expr, scope);
482
+ if (!fnType) return ANY;
483
+ if (fnType.kind !== "func") {
484
+ return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
440
485
  }
441
486
 
442
- // Generic function inference
443
- if (fnType.kind === "generic") {
444
- const typeArgs = this.inferTypeArgs(fnType, argTypes);
445
- if (!typeArgs) {
446
- return this.err(
447
- `Cannot infer type arguments for generic function '${fnType.name}'`,
448
- expr,
449
- );
450
- }
451
- // Check constraints
452
- for (
453
- let i = 0;
454
- i < fnType.typeParams.length && i < typeArgs.length;
455
- i++
456
- ) {
457
- const tp = fnType.typeParams[i];
458
- const constraint = tp.constraint
459
- ? this.resolveTypeNode(tp.constraint, scope)
460
- : null;
461
- if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
462
- }
463
- fnType = this.instantiateGenericFunc(fnType, typeArgs);
464
- }
487
+ this._checkCallArgs(fnType, argTypes, expr);
465
488
 
466
- if (fnType.kind !== "func") {
467
- return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
489
+ const ret = fnType.returns;
490
+ if (!ret || ret.length === 0) return VOID;
491
+ if (ret.length === 1) return ret[0];
492
+ return { kind: "tuple", types: ret };
493
+ },
494
+
495
+ _resolveGenericFnType(fnType, argTypes, expr, scope) {
496
+ const typeArgs = this.inferTypeArgs(fnType, argTypes);
497
+ if (!typeArgs) {
498
+ this.err(
499
+ `Cannot infer type arguments for generic function '${fnType.name}'`,
500
+ expr,
501
+ );
502
+ return null;
503
+ }
504
+ // Check constraints
505
+ for (let i = 0; i < fnType.typeParams.length && i < typeArgs.length; i++) {
506
+ const tp = fnType.typeParams[i];
507
+ const constraint = tp.constraint
508
+ ? this.resolveTypeNode(tp.constraint, scope)
509
+ : null;
510
+ if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
468
511
  }
512
+ return this.instantiateGenericFunc(fnType, typeArgs);
513
+ },
469
514
 
470
- // Check arg count (allow variadic slack)
515
+ _checkArgCount(fnType, argTypes, expr) {
471
516
  const minArgs = fnType.params.filter(
472
517
  (_, i) => !fnType.variadic || i < fnType.params.length - 1,
473
518
  ).length;
474
- if (argTypes.length < minArgs) {
519
+ if (argTypes.length < minArgs)
475
520
  this.err(
476
521
  `Too few arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
477
522
  expr,
478
523
  );
479
- }
480
- if (!fnType.variadic && argTypes.length > fnType.params.length) {
524
+ if (!fnType.variadic && argTypes.length > fnType.params.length)
481
525
  this.err(
482
526
  `Too many arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
483
527
  expr,
484
528
  );
485
- }
529
+ },
486
530
 
487
- // Check each argument is assignable to its parameter type
531
+ _checkCallArgs(fnType, argTypes, expr) {
532
+ this._checkArgCount(fnType, argTypes, expr);
488
533
  for (let i = 0; i < fnType.params.length && i < argTypes.length; i++) {
489
534
  const paramIdx =
490
535
  fnType.variadic && i >= fnType.params.length - 1
491
536
  ? fnType.params.length - 1
492
537
  : i;
493
- // Spread arg (f(slice...)) passes a whole slice into a variadic — skip per-element check
494
538
  if (expr.args[i]?._spread) continue;
495
539
  this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
496
540
  }
497
-
498
- const ret = fnType.returns;
499
- if (!ret || ret.length === 0) return VOID;
500
- if (ret.length === 1) return ret[0];
501
- return { kind: "tuple", types: ret };
502
541
  },
503
542
 
504
543
  checkBuiltin(name, expr, argTypes, scope) {
505
- switch (name) {
506
- case "len":
507
- case "cap": {
508
- // Compile-time len() for fixed arrays
509
- if (
510
- name === "len" &&
511
- argTypes[0]?.kind === "array" &&
512
- argTypes[0].size != null
513
- ) {
514
- expr._constLen = argTypes[0].size;
515
- } else if (
516
- name === "len" &&
517
- argTypes[0]?.kind === "named" &&
518
- argTypes[0].underlying?.kind === "array" &&
519
- argTypes[0].underlying.size != null
520
- ) {
521
- expr._constLen = argTypes[0].underlying.size;
522
- }
523
- return INT;
524
- }
525
- case "append": {
526
- const sliceType = argTypes[0] ?? ANY;
527
- if (isArray(sliceType)) {
528
- this.err(`cannot append to array (type ${typeStr(sliceType)})`, expr);
529
- return sliceType;
530
- }
531
- return sliceType;
532
- }
533
- case "copy":
534
- return INT;
535
- case "delete":
536
- return VOID;
537
- case "make": {
538
- if (expr.args.length < 1) return ANY;
539
- const typeArg = expr.args[0];
540
- const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
541
- return this.resolveTypeNode(typeNode, scope) ?? ANY;
542
- }
543
- case "new": {
544
- if (expr.args.length < 1) return ANY;
545
- const inner = this.resolveTypeNode(expr.args[0], scope);
546
- return { kind: "pointer", base: inner };
547
- }
548
- case "print":
549
- case "println":
550
- case "panic":
551
- return VOID;
552
- case "recover":
553
- return ANY;
554
- case "error":
555
- return ERROR;
556
- case "min":
557
- case "max":
558
- return argTypes[0] ?? ANY;
559
- case "clear":
560
- return VOID;
561
- case "complex": {
562
- if (expr.args.length !== 2) {
563
- this.err("complex() requires exactly 2 arguments", expr);
564
- return ANY;
565
- }
566
- const crt = argTypes[0];
567
- const cit = argTypes[1];
568
- if (!isNumeric(crt)) {
569
- this.err(`cannot use ${typeStr(crt)} as float in complex()`, expr);
570
- return ANY;
571
- }
572
- if (!isNumeric(cit)) {
573
- this.err(`cannot use ${typeStr(cit)} as float in complex()`, expr);
574
- return ANY;
575
- }
576
- if (crt.kind === "untyped" && cit.kind === "untyped")
577
- return UNTYPED_COMPLEX;
578
- return COMPLEX128;
579
- }
580
- case "real":
581
- case "imag": {
582
- if (expr.args.length !== 1) {
583
- this.err(`${name}() requires exactly 1 argument`, expr);
584
- return ANY;
585
- }
586
- const zt = argTypes[0];
587
- if (!isComplex(zt)) {
588
- this.err(`cannot use ${typeStr(zt)} as complex in ${name}()`, expr);
589
- return ANY;
590
- }
591
- if (zt.kind === "untyped") return UNTYPED_FLOAT;
592
- return FLOAT64;
593
- }
594
- default:
595
- return ANY;
544
+ const gen = BUILTIN_CHECK[name];
545
+ return gen ? gen(this, name, expr, argTypes, scope) : ANY;
546
+ },
547
+
548
+ _constLenFromType(arg0) {
549
+ if (arg0?.kind === "array" && arg0.size != null) return arg0.size;
550
+ if (
551
+ arg0?.kind === "named" &&
552
+ arg0.underlying?.kind === "array" &&
553
+ arg0.underlying.size != null
554
+ )
555
+ return arg0.underlying.size;
556
+ return null;
557
+ },
558
+
559
+ _checkBuiltinLen(name, expr, argTypes) {
560
+ if (name === "len") {
561
+ const size = this._constLenFromType(argTypes[0]);
562
+ if (size != null) expr._constLen = size;
563
+ }
564
+ return INT;
565
+ },
566
+
567
+ _checkBuiltinComplex(expr, argTypes) {
568
+ if (expr.args.length !== 2) {
569
+ this.err("complex() requires exactly 2 arguments", expr);
570
+ return ANY;
571
+ }
572
+ const crt = argTypes[0];
573
+ const cit = argTypes[1];
574
+ if (!isNumeric(crt)) {
575
+ this.err(`cannot use ${typeStr(crt)} as float in complex()`, expr);
576
+ return ANY;
577
+ }
578
+ if (!isNumeric(cit)) {
579
+ this.err(`cannot use ${typeStr(cit)} as float in complex()`, expr);
580
+ return ANY;
581
+ }
582
+ if (crt.kind === "untyped" && cit.kind === "untyped")
583
+ return UNTYPED_COMPLEX;
584
+ return COMPLEX128;
585
+ },
586
+
587
+ _checkBuiltinRealImag(name, expr, argTypes) {
588
+ if (expr.args.length !== 1) {
589
+ this.err(`${name}() requires exactly 1 argument`, expr);
590
+ return ANY;
591
+ }
592
+ const zt = argTypes[0];
593
+ if (!isComplex(zt)) {
594
+ this.err(`cannot use ${typeStr(zt)} as complex in ${name}()`, expr);
595
+ return ANY;
596
596
  }
597
+ if (zt.kind === "untyped") return UNTYPED_FLOAT;
598
+ return FLOAT64;
599
+ },
600
+
601
+ _inferArraySize(expr, typeNode, base) {
602
+ if (
603
+ typeNode?.kind === "ArrayType" &&
604
+ typeNode.inferLen &&
605
+ base?.kind === "array"
606
+ )
607
+ base.size = computeMaxIndex(expr.elems) + 1;
608
+ },
609
+
610
+ _isSliceOrArray(base) {
611
+ return base?.kind === "slice" || base?.kind === "array";
612
+ },
613
+
614
+ _checkUnknownCompositeLit(expr, scope, t) {
615
+ for (const elem of expr.elems) {
616
+ if (elem.kind === "KeyValueExpr") this.checkExpr(elem.value, scope);
617
+ else this.checkExpr(elem, scope);
618
+ }
619
+ return t ?? ANY;
597
620
  },
598
621
 
599
622
  checkCompositeLit(expr, scope, hintType = null) {
600
- // null typeExpr = implicit lit inside a slice/map: {X:1} in []Point{{X:1}}
601
623
  const t =
602
624
  expr.typeExpr === null
603
625
  ? (hintType ?? ANY)
604
626
  : this.resolveTypeNode(expr.typeExpr, scope);
605
627
  const base = t.kind === "named" ? t.underlying : t;
606
-
607
- // [...]T size inference: count elements and set size
608
628
  const typeNode = expr.typeExpr;
609
- if (
610
- typeNode?.kind === "ArrayType" &&
611
- typeNode.inferLen &&
612
- base?.kind === "array"
613
- ) {
614
- const maxIndex = computeMaxIndex(expr.elems);
615
- base.size = maxIndex + 1;
616
- // If t is a named type wrapping the array, the underlying is already base
617
- // If t === base, it's the same object
618
- }
629
+ this._inferArraySize(expr, typeNode, base);
630
+ if (base?.kind === "struct")
631
+ return this._checkStructCompositeLit(expr, scope, t, base);
632
+ if (this._isSliceOrArray(base))
633
+ return this._checkSliceArrayCompositeLit(expr, scope, t, base, typeNode);
634
+ if (base?.kind === "map")
635
+ return this._checkMapCompositeLit(expr, scope, t, base);
636
+ return this._checkUnknownCompositeLit(expr, scope, t);
637
+ },
619
638
 
620
- if (base?.kind === "struct") {
621
- // Detect positional (unkeyed) literal: all elements must be non-KeyValueExpr
622
- const hasPositional = expr.elems.some((e) => e.kind !== "KeyValueExpr");
623
- const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
624
- if (hasPositional && hasKeyed) {
625
- this.err("mixture of field:value and value initializers", expr);
626
- }
627
- if (hasPositional) {
628
- // Map positional elements to struct fields by declaration order
629
- const fieldNames = [...(base.fields?.keys() ?? [])];
630
- for (let i = 0; i < expr.elems.length; i++) {
631
- const elem = expr.elems[i];
632
- const fieldName = fieldNames[i];
633
- if (!fieldName) {
634
- this.err("too many values in struct literal", elem);
635
- continue;
636
- }
637
- const fieldType = base.fields.get(fieldName);
638
- const vt = this.checkExpr(elem, scope);
639
- if (fieldType) this.assertAssignable(fieldType, vt, elem);
640
- // Annotate for codegen
641
- elem._positionalField = fieldName;
642
- }
643
- return t;
639
+ _checkStructPositionalElems(expr, scope, t, base) {
640
+ const fieldNames = [...(base.fields?.keys() ?? [])];
641
+ for (let i = 0; i < expr.elems.length; i++) {
642
+ const elem = expr.elems[i];
643
+ const fieldName = fieldNames[i];
644
+ if (!fieldName) {
645
+ this.err("too many values in struct literal", elem);
646
+ continue;
644
647
  }
645
- for (const elem of expr.elems) {
646
- if (elem.kind === "KeyValueExpr") {
647
- const keyName = elem.key.name;
648
- const fieldType = base.fields?.get(keyName);
649
- if (fieldType) {
648
+ const fieldType = base.fields.get(fieldName);
649
+ const vt = this.checkExpr(elem, scope);
650
+ if (fieldType) this.assertAssignable(fieldType, vt, elem);
651
+ elem._positionalField = fieldName;
652
+ }
653
+ return t;
654
+ },
655
+
656
+ _checkStructKeyedElems(expr, scope, t, base) {
657
+ for (const elem of expr.elems) {
658
+ if (elem.kind === "KeyValueExpr") {
659
+ const keyName = elem.key.name;
660
+ const fieldType = base.fields?.get(keyName);
661
+ if (fieldType) {
662
+ const vt = this.checkExpr(elem.value, scope);
663
+ this.assertAssignable(fieldType, vt, elem.value);
664
+ } else {
665
+ const embed = base._embeds?.find(
666
+ (e) => (e.kind === "named" ? e.name : null) === keyName,
667
+ );
668
+ if (embed) {
650
669
  const vt = this.checkExpr(elem.value, scope);
651
- this.assertAssignable(fieldType, vt, elem.value);
670
+ this.assertAssignable(embed, vt, elem.value);
671
+ elem._isEmbedInit = true;
652
672
  } else {
653
- // Check if it's an embedded type name (e.g. Dog{Animal: Animal{...}})
654
- const embed = base._embeds?.find(
655
- (e) => (e.kind === "named" ? e.name : null) === keyName,
656
- );
657
- if (embed) {
658
- const vt = this.checkExpr(elem.value, scope);
659
- this.assertAssignable(embed, vt, elem.value);
660
- elem._isEmbedInit = true;
661
- } else {
662
- this.err(`Unknown field '${keyName}'`, elem.key);
663
- }
673
+ this.err(`Unknown field '${keyName}'`, elem.key);
664
674
  }
665
- } else {
666
- this.checkExpr(elem, scope);
667
675
  }
676
+ } else {
677
+ this.checkExpr(elem, scope);
668
678
  }
669
- return t;
670
679
  }
680
+ return t;
681
+ },
671
682
 
672
- if (base?.kind === "slice" || base?.kind === "array") {
673
- for (const elem of expr.elems) {
674
- if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
675
- const et = this.checkCompositeLit(elem, scope, base.elem);
676
- elem._type = et;
677
- } else if (elem.kind === "KeyValueExpr") {
678
- const vt = this.checkExpr(elem.value, scope);
679
- this.assertAssignable(base.elem, vt, elem.value);
680
- } else {
681
- const et = this.checkExpr(elem, scope);
682
- this.assertAssignable(base.elem, et, elem);
683
- }
684
- }
683
+ _checkStructCompositeLit(expr, scope, t, base) {
684
+ const hasPositional = expr.elems.some((e) => e.kind !== "KeyValueExpr");
685
+ const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
686
+ if (hasPositional && hasKeyed)
687
+ this.err("mixture of field:value and value initializers", expr);
688
+ if (hasPositional)
689
+ return this._checkStructPositionalElems(expr, scope, t, base);
690
+ return this._checkStructKeyedElems(expr, scope, t, base);
691
+ },
685
692
 
686
- // Composite literal element count validation for explicit-size arrays
687
- if (base.kind === "array" && base.size != null && !typeNode?.inferLen) {
688
- const maxIndex = computeMaxIndex(expr.elems);
689
- if (maxIndex >= base.size) {
690
- this.err(
691
- `array index ${maxIndex} out of bounds [0:${base.size}]`,
692
- expr,
693
- );
694
- }
693
+ _checkSliceArrayCompositeLit(expr, scope, t, base, typeNode) {
694
+ for (const elem of expr.elems) {
695
+ if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
696
+ const et = this.checkCompositeLit(elem, scope, base.elem);
697
+ elem._type = et;
698
+ } else if (elem.kind === "KeyValueExpr") {
699
+ const vt = this.checkExpr(elem.value, scope);
700
+ this.assertAssignable(base.elem, vt, elem.value);
701
+ } else {
702
+ const et = this.checkExpr(elem, scope);
703
+ this.assertAssignable(base.elem, et, elem);
695
704
  }
705
+ }
696
706
 
697
- return t;
698
- }
699
-
700
- if (base?.kind === "map") {
701
- for (const elem of expr.elems) {
702
- if (elem.kind === "KeyValueExpr") {
703
- const kt = this.checkExpr(elem.key, scope);
704
- // value may be an implicit composite lit
705
- let vt;
706
- if (
707
- elem.value.kind === "CompositeLit" &&
708
- elem.value.typeExpr === null
709
- ) {
710
- vt = this.checkCompositeLit(elem.value, scope, base.value);
711
- elem.value._type = vt;
712
- } else {
713
- vt = this.checkExpr(elem.value, scope);
714
- }
715
- this.assertAssignable(base.key, kt, elem.key);
716
- this.assertAssignable(base.value, vt, elem.value);
717
- }
707
+ // Composite literal element count validation for explicit-size arrays
708
+ if (base.kind === "array" && base.size != null && !typeNode?.inferLen) {
709
+ const maxIndex = computeMaxIndex(expr.elems);
710
+ if (maxIndex >= base.size) {
711
+ this.err(
712
+ `array index ${maxIndex} out of bounds [0:${base.size}]`,
713
+ expr,
714
+ );
718
715
  }
719
- return t;
720
716
  }
721
717
 
722
- // Unknown type context — still check elements
718
+ return t;
719
+ },
720
+
721
+ _checkMapCompositeLit(expr, scope, t, base) {
723
722
  for (const elem of expr.elems) {
724
723
  if (elem.kind === "KeyValueExpr") {
725
- this.checkExpr(elem.value, scope);
726
- } else {
727
- this.checkExpr(elem, scope);
724
+ const kt = this.checkExpr(elem.key, scope);
725
+ // value may be an implicit composite lit
726
+ let vt;
727
+ if (
728
+ elem.value.kind === "CompositeLit" &&
729
+ elem.value.typeExpr === null
730
+ ) {
731
+ vt = this.checkCompositeLit(elem.value, scope, base.value);
732
+ elem.value._type = vt;
733
+ } else {
734
+ vt = this.checkExpr(elem.value, scope);
735
+ }
736
+ this.assertAssignable(base.key, kt, elem.key);
737
+ this.assertAssignable(base.value, vt, elem.value);
728
738
  }
729
739
  }
730
- return t ?? ANY;
740
+ return t;
731
741
  },
732
742
  };
733
743