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