gofront 0.0.3 → 0.0.5

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.
@@ -3,10 +3,15 @@
3
3
  import {
4
4
  ANY,
5
5
  BOOL,
6
+ COMPLEX128,
6
7
  ERROR,
8
+ FLOAT64,
7
9
  INT,
8
10
  isAny,
11
+ isArray,
9
12
  isBool,
13
+ isComplex,
14
+ isComplexOrNumeric,
10
15
  isNumeric,
11
16
  isString,
12
17
  NIL,
@@ -14,6 +19,7 @@ import {
14
19
  STRING,
15
20
  typeStr,
16
21
  UNTYPED_BOOL,
22
+ UNTYPED_COMPLEX,
17
23
  UNTYPED_FLOAT,
18
24
  UNTYPED_INT,
19
25
  UNTYPED_STRING,
@@ -45,9 +51,14 @@ export const expressionCheckMethods = {
45
51
  break;
46
52
  }
47
53
 
54
+ case "ImagLit":
55
+ return UNTYPED_COMPLEX;
56
+
48
57
  case "Ident": {
49
58
  const t = scope.lookup(expr.name);
50
59
  if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
60
+ // Mark type-name identifiers so SelectorExpr can detect method expressions
61
+ if (this.types.has(expr.name)) expr._isTypeRef = true;
51
62
  return t;
52
63
  }
53
64
 
@@ -58,17 +69,35 @@ export const expressionCheckMethods = {
58
69
  return BOOL;
59
70
  }
60
71
  if (expr.op === "-" || expr.op === "+") {
61
- if (!isNumeric(ot) && !isAny(ot))
72
+ if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
62
73
  this.err(`${expr.op} requires numeric`, expr);
63
74
  return ot;
64
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
+ }
65
93
  return ot;
66
94
  }
67
95
 
68
96
  case "BinaryExpr": {
69
97
  const lt = this.checkExpr(expr.left, scope);
70
98
  const rt = this.checkExpr(expr.right, scope);
71
- if (isAny(lt) || isAny(rt)) return this.binaryResultType(expr.op, ANY);
99
+ if (isAny(lt) || isAny(rt))
100
+ return this.binaryResultType(expr.op, ANY, ANY, expr);
72
101
  return this.binaryResultType(expr.op, lt, rt, expr);
73
102
  }
74
103
 
@@ -78,14 +107,59 @@ export const expressionCheckMethods = {
78
107
 
79
108
  case "SelectorExpr": {
80
109
  const baseType = this.checkExpr(expr.expr, scope);
81
- return this.fieldType(baseType, expr.field, expr);
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
+ if (base?.kind === "struct" && base.methods?.has(expr.field)) {
115
+ const methodType = base.methods.get(expr.field);
116
+ // Return a func type with the receiver prepended to params
117
+ expr._isMethodExpr = true;
118
+ expr._methodExprRecv = baseType;
119
+ return {
120
+ kind: "func",
121
+ params: [baseType, ...(methodType.params ?? [])],
122
+ returns: methodType.returns ?? [],
123
+ variadic: methodType.variadic,
124
+ };
125
+ }
126
+ }
127
+ const ft = this.fieldType(baseType, expr.field, expr);
128
+ // Mark method selectors so codegen can emit .bind() when used as a value
129
+ if (ft?.kind === "func") {
130
+ let base = baseType.kind === "named" ? baseType.underlying : baseType;
131
+ base = this.resolveType(base);
132
+ if (base?.kind === "struct" && base.methods?.has(expr.field)) {
133
+ expr._isMethodValue = true;
134
+ }
135
+ }
136
+ return ft;
82
137
  }
83
138
 
84
139
  case "IndexExpr": {
85
140
  const bt = this.checkExpr(expr.expr, scope);
86
141
  this.checkExpr(expr.index, scope);
87
142
  if (isAny(bt)) return ANY;
88
- if (bt.kind === "slice" || bt.kind === "array") return bt.elem;
143
+ if (bt.kind === "slice" || bt.kind === "array") {
144
+ // Compile-time bounds checking for arrays
145
+ if (bt.kind === "array" && bt.size != null) {
146
+ const idx = this._constIntValue(expr.index);
147
+ if (idx !== null) {
148
+ if (idx < 0) {
149
+ this.err(
150
+ `invalid array index ${idx} (index must not be negative)`,
151
+ expr,
152
+ );
153
+ } else if (idx >= bt.size) {
154
+ this.err(
155
+ `invalid array index ${idx} (out of bounds for ${typeStr(bt)})`,
156
+ expr,
157
+ );
158
+ }
159
+ }
160
+ }
161
+ return bt.elem;
162
+ }
89
163
  if (bt.kind === "map") {
90
164
  expr._mapValueType = bt.value; // for codegen zero-value fallback
91
165
  return bt.value;
@@ -143,8 +217,74 @@ export const expressionCheckMethods = {
143
217
  }
144
218
 
145
219
  case "TypeConversion": {
146
- this.checkExpr(expr.expr, scope);
147
- return this.resolveTypeNode(expr.targetType, scope);
220
+ const srcType = this.checkExpr(expr.expr, scope);
221
+ const targetType = this.resolveTypeNode(expr.targetType, scope);
222
+ const srcResolved =
223
+ srcType?.kind === "named" ? srcType.underlying : srcType;
224
+ const tgtResolved =
225
+ targetType?.kind === "named" ? targetType.underlying : targetType;
226
+ // Reject float64(complex) — must use real()/imag()
227
+ if (isNumeric(targetType) && isComplex(srcType)) {
228
+ this.err(
229
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
230
+ expr,
231
+ );
232
+ }
233
+ // Allow complex128/64(numeric) and complex128/64(complex)
234
+ if (isComplex(targetType)) {
235
+ if (!isComplexOrNumeric(srcType) && !isAny(srcType)) {
236
+ this.err(
237
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
238
+ expr,
239
+ );
240
+ }
241
+ }
242
+ // Slice → array conversion: [N]T(slice)
243
+ if (tgtResolved?.kind === "array" && srcResolved?.kind === "slice") {
244
+ const tgtElem = tgtResolved.elem;
245
+ const srcElem = srcResolved.elem;
246
+ if (
247
+ tgtElem &&
248
+ srcElem &&
249
+ !isAny(tgtElem) &&
250
+ !isAny(srcElem) &&
251
+ typeStr(tgtElem) !== typeStr(srcElem)
252
+ ) {
253
+ this.err(
254
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
255
+ expr,
256
+ );
257
+ }
258
+ return targetType;
259
+ }
260
+ // Array → slice conversion: []T(array)
261
+ if (tgtResolved?.kind === "slice" && srcResolved?.kind === "array") {
262
+ const tgtElem = tgtResolved.elem;
263
+ const srcElem = srcResolved.elem;
264
+ if (
265
+ tgtElem &&
266
+ srcElem &&
267
+ !isAny(tgtElem) &&
268
+ !isAny(srcElem) &&
269
+ typeStr(tgtElem) !== typeStr(srcElem)
270
+ ) {
271
+ this.err(
272
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
273
+ expr,
274
+ );
275
+ }
276
+ return targetType;
277
+ }
278
+ // Reject conversions between array and incompatible types
279
+ if (tgtResolved?.kind === "array" && srcResolved?.kind !== "array") {
280
+ if (!isAny(srcType)) {
281
+ this.err(
282
+ `cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
283
+ expr,
284
+ );
285
+ }
286
+ }
287
+ return targetType;
148
288
  }
149
289
 
150
290
  case "TypeAssertExpr": {
@@ -170,7 +310,53 @@ export const expressionCheckMethods = {
170
310
 
171
311
  case "RangeExpr": {
172
312
  // Type-check the iterated expression so its _type is annotated
173
- return this.checkExpr(expr.expr, scope);
313
+ const collType = this.checkExpr(expr.expr, scope);
314
+ const resolved =
315
+ collType?.kind === "named" ? collType.underlying : collType;
316
+ // Return a tuple of (index/key type, value type) so DefineStmt can
317
+ // assign the correct types to range variables.
318
+ const isString =
319
+ (resolved?.kind === "basic" && resolved.name === "string") ||
320
+ (resolved?.kind === "untyped" && resolved.base === "string");
321
+ if (isString) {
322
+ // string range: (int index, rune value) — rune is an alias for int
323
+ return { kind: "tuple", types: [INT, INT] };
324
+ }
325
+ if (resolved?.kind === "slice" || resolved?.kind === "array") {
326
+ return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
327
+ }
328
+ if (resolved?.kind === "map") {
329
+ return {
330
+ kind: "tuple",
331
+ types: [resolved.key ?? ANY, resolved.value ?? ANY],
332
+ };
333
+ }
334
+ // integer range or unknown — return single-element tuple
335
+ return { kind: "tuple", types: [collType] };
336
+ }
337
+
338
+ case "InstantiationExpr": {
339
+ // Generic instantiation: Foo[int] or Foo[int, string]
340
+ const baseType = this.checkExpr(expr.expr, scope);
341
+ if (baseType?.kind === "generic") {
342
+ const typeArgs = expr.typeArgs.map((ta) =>
343
+ this.resolveTypeNode(ta, scope),
344
+ );
345
+ // Check constraints
346
+ for (
347
+ let i = 0;
348
+ i < baseType.typeParams.length && i < typeArgs.length;
349
+ i++
350
+ ) {
351
+ const tp = baseType.typeParams[i];
352
+ const constraint = tp.constraint
353
+ ? this.resolveTypeNode(tp.constraint, scope)
354
+ : null;
355
+ if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
356
+ }
357
+ return this.instantiateGenericFunc(baseType, typeArgs);
358
+ }
359
+ return baseType;
174
360
  }
175
361
 
176
362
  default:
@@ -179,8 +365,22 @@ export const expressionCheckMethods = {
179
365
  return ANY;
180
366
  },
181
367
 
368
+ _constIntValue(expr) {
369
+ if (expr.kind === "BasicLit" && expr.litKind === "INT")
370
+ return Number(expr.value);
371
+ if (
372
+ expr.kind === "UnaryExpr" &&
373
+ expr.op === "-" &&
374
+ expr.operand?.kind === "BasicLit" &&
375
+ expr.operand.litKind === "INT"
376
+ ) {
377
+ return -Number(expr.operand.value);
378
+ }
379
+ return null;
380
+ },
381
+
182
382
  checkCall(expr, scope) {
183
- const fnType = this.checkExpr(expr.func, scope);
383
+ let fnType = this.checkExpr(expr.func, scope);
184
384
 
185
385
  // Handle built-ins before evaluating args; some (e.g. new) take type names as
186
386
  // arguments, which would otherwise produce false "Undefined" errors.
@@ -193,10 +393,47 @@ export const expressionCheckMethods = {
193
393
  return this.checkBuiltin(fnType.name, expr, argTypes, scope);
194
394
  }
195
395
 
196
- const argTypes = expr.args.map((a) => this.checkExpr(a, scope));
396
+ // Multi-value forwarding: f(g()) where g() returns multiple values
397
+ // When a single argument is a multi-return call, flatten its tuple into args.
398
+ let argTypes;
399
+ if (expr.args.length === 1 && expr.args[0].kind === "CallExpr") {
400
+ const argType = this.checkExpr(expr.args[0], scope);
401
+ if (argType?.kind === "tuple") {
402
+ expr._multiForward = true; // flag for codegen
403
+ argTypes = argType.types;
404
+ } else {
405
+ argTypes = [argType];
406
+ }
407
+ } else {
408
+ argTypes = expr.args.map((a) => this.checkExpr(a, scope));
409
+ }
197
410
 
198
411
  if (isAny(fnType)) return ANY;
199
412
 
413
+ // Generic function inference
414
+ if (fnType.kind === "generic") {
415
+ const typeArgs = this.inferTypeArgs(fnType, argTypes);
416
+ if (!typeArgs) {
417
+ return this.err(
418
+ `Cannot infer type arguments for generic function '${fnType.name}'`,
419
+ expr,
420
+ );
421
+ }
422
+ // Check constraints
423
+ for (
424
+ let i = 0;
425
+ i < fnType.typeParams.length && i < typeArgs.length;
426
+ i++
427
+ ) {
428
+ const tp = fnType.typeParams[i];
429
+ const constraint = tp.constraint
430
+ ? this.resolveTypeNode(tp.constraint, scope)
431
+ : null;
432
+ if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
433
+ }
434
+ fnType = this.instantiateGenericFunc(fnType, typeArgs);
435
+ }
436
+
200
437
  if (fnType.kind !== "func") {
201
438
  return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
202
439
  }
@@ -238,10 +475,32 @@ export const expressionCheckMethods = {
238
475
  checkBuiltin(name, expr, argTypes, scope) {
239
476
  switch (name) {
240
477
  case "len":
241
- case "cap":
478
+ case "cap": {
479
+ // Compile-time len() for fixed arrays
480
+ if (
481
+ name === "len" &&
482
+ argTypes[0]?.kind === "array" &&
483
+ argTypes[0].size != null
484
+ ) {
485
+ expr._constLen = argTypes[0].size;
486
+ } else if (
487
+ name === "len" &&
488
+ argTypes[0]?.kind === "named" &&
489
+ argTypes[0].underlying?.kind === "array" &&
490
+ argTypes[0].underlying.size != null
491
+ ) {
492
+ expr._constLen = argTypes[0].underlying.size;
493
+ }
242
494
  return INT;
243
- case "append":
244
- return argTypes[0] ?? ANY;
495
+ }
496
+ case "append": {
497
+ const sliceType = argTypes[0] ?? ANY;
498
+ if (isArray(sliceType)) {
499
+ this.err(`cannot append to array (type ${typeStr(sliceType)})`, expr);
500
+ return sliceType;
501
+ }
502
+ return sliceType;
503
+ }
245
504
  case "copy":
246
505
  return INT;
247
506
  case "delete":
@@ -270,6 +529,39 @@ export const expressionCheckMethods = {
270
529
  return argTypes[0] ?? ANY;
271
530
  case "clear":
272
531
  return VOID;
532
+ case "complex": {
533
+ if (expr.args.length !== 2) {
534
+ this.err("complex() requires exactly 2 arguments", expr);
535
+ return ANY;
536
+ }
537
+ const crt = argTypes[0];
538
+ const cit = argTypes[1];
539
+ if (!isNumeric(crt)) {
540
+ this.err(`cannot use ${typeStr(crt)} as float in complex()`, expr);
541
+ return ANY;
542
+ }
543
+ if (!isNumeric(cit)) {
544
+ this.err(`cannot use ${typeStr(cit)} as float in complex()`, expr);
545
+ return ANY;
546
+ }
547
+ if (crt.kind === "untyped" && cit.kind === "untyped")
548
+ return UNTYPED_COMPLEX;
549
+ return COMPLEX128;
550
+ }
551
+ case "real":
552
+ case "imag": {
553
+ if (expr.args.length !== 1) {
554
+ this.err(`${name}() requires exactly 1 argument`, expr);
555
+ return ANY;
556
+ }
557
+ const zt = argTypes[0];
558
+ if (!isComplex(zt)) {
559
+ this.err(`cannot use ${typeStr(zt)} as complex in ${name}()`, expr);
560
+ return ANY;
561
+ }
562
+ if (zt.kind === "untyped") return UNTYPED_FLOAT;
563
+ return FLOAT64;
564
+ }
273
565
  default:
274
566
  return ANY;
275
567
  }
@@ -283,7 +575,44 @@ export const expressionCheckMethods = {
283
575
  : this.resolveTypeNode(expr.typeExpr, scope);
284
576
  const base = t.kind === "named" ? t.underlying : t;
285
577
 
578
+ // [...]T size inference: count elements and set size
579
+ const typeNode = expr.typeExpr;
580
+ if (
581
+ typeNode?.kind === "ArrayType" &&
582
+ typeNode.inferLen &&
583
+ base?.kind === "array"
584
+ ) {
585
+ const maxIndex = computeMaxIndex(expr.elems);
586
+ base.size = maxIndex + 1;
587
+ // If t is a named type wrapping the array, the underlying is already base
588
+ // If t === base, it's the same object
589
+ }
590
+
286
591
  if (base?.kind === "struct") {
592
+ // Detect positional (unkeyed) literal: all elements must be non-KeyValueExpr
593
+ const hasPositional = expr.elems.some((e) => e.kind !== "KeyValueExpr");
594
+ const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
595
+ if (hasPositional && hasKeyed) {
596
+ this.err("mixture of field:value and value initializers", expr);
597
+ }
598
+ if (hasPositional) {
599
+ // Map positional elements to struct fields by declaration order
600
+ const fieldNames = [...(base.fields?.keys() ?? [])];
601
+ for (let i = 0; i < expr.elems.length; i++) {
602
+ const elem = expr.elems[i];
603
+ const fieldName = fieldNames[i];
604
+ if (!fieldName) {
605
+ this.err("too many values in struct literal", elem);
606
+ continue;
607
+ }
608
+ const fieldType = base.fields.get(fieldName);
609
+ const vt = this.checkExpr(elem, scope);
610
+ if (fieldType) this.assertAssignable(fieldType, vt, elem);
611
+ // Annotate for codegen
612
+ elem._positionalField = fieldName;
613
+ }
614
+ return t;
615
+ }
287
616
  for (const elem of expr.elems) {
288
617
  if (elem.kind === "KeyValueExpr") {
289
618
  const keyName = elem.key.name;
@@ -316,11 +645,26 @@ export const expressionCheckMethods = {
316
645
  if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
317
646
  const et = this.checkCompositeLit(elem, scope, base.elem);
318
647
  elem._type = et;
319
- } else if (elem.kind !== "KeyValueExpr") {
648
+ } else if (elem.kind === "KeyValueExpr") {
649
+ const vt = this.checkExpr(elem.value, scope);
650
+ this.assertAssignable(base.elem, vt, elem.value);
651
+ } else {
320
652
  const et = this.checkExpr(elem, scope);
321
653
  this.assertAssignable(base.elem, et, elem);
322
654
  }
323
655
  }
656
+
657
+ // Composite literal element count validation for explicit-size arrays
658
+ if (base.kind === "array" && base.size != null && !typeNode?.inferLen) {
659
+ const maxIndex = computeMaxIndex(expr.elems);
660
+ if (maxIndex >= base.size) {
661
+ this.err(
662
+ `array index ${maxIndex} out of bounds [0:${base.size}]`,
663
+ expr,
664
+ );
665
+ }
666
+ }
667
+
324
668
  return t;
325
669
  }
326
670
 
@@ -357,3 +701,16 @@ export const expressionCheckMethods = {
357
701
  return t ?? ANY;
358
702
  },
359
703
  };
704
+
705
+ function computeMaxIndex(elems) {
706
+ let sequential = 0;
707
+ let maxKeyed = -1;
708
+ for (const e of elems) {
709
+ if (e.kind === "KeyValueExpr" && e.key?.value !== undefined) {
710
+ maxKeyed = Math.max(maxKeyed, Number(e.key.value));
711
+ } else {
712
+ sequential++;
713
+ }
714
+ }
715
+ return Math.max(sequential - 1, maxKeyed);
716
+ }
@@ -7,6 +7,7 @@ import {
7
7
  isAny,
8
8
  isBool,
9
9
  isVoid,
10
+ iteratorYieldParams,
10
11
  Scope,
11
12
  } from "./types.js";
12
13
 
@@ -72,9 +73,37 @@ export const statementCheckMethods = {
72
73
  }
73
74
 
74
75
  case "AssignStmt": {
76
+ // comma-ok map index: v, ok = m["key"]
77
+ // Evaluate map expr first so _type is populated, then detect the pattern.
78
+ if (
79
+ stmt.lhs.length === 2 &&
80
+ stmt.rhs.length === 1 &&
81
+ stmt.rhs[0].kind === "IndexExpr"
82
+ ) {
83
+ const mapBaseType = this.checkExpr(stmt.rhs[0].expr, scope);
84
+ const resolvedBase =
85
+ mapBaseType?.kind === "named"
86
+ ? mapBaseType.underlying
87
+ : mapBaseType;
88
+ if (resolvedBase?.kind === "map") {
89
+ this.checkExpr(stmt.rhs[0].index, scope);
90
+ stmt._commaOkMap = true;
91
+ // Store the map value type so codegen can emit the zero-value fallback
92
+ stmt.rhs[0]._mapValueType = resolvedBase.value;
93
+ break;
94
+ }
95
+ }
96
+ // comma-ok type assertion: v, ok = x.(T)
97
+ if (
98
+ stmt.lhs.length === 2 &&
99
+ stmt.rhs.length === 1 &&
100
+ stmt.rhs[0].kind === "TypeAssertExpr"
101
+ ) {
102
+ stmt.rhs[0]._commaOk = true;
103
+ }
75
104
  // Check for const reassignment before evaluating RHS
76
105
  for (const lhs of stmt.lhs) {
77
- if (lhs.kind === "Ident") {
106
+ if (lhs.kind === "Ident" && lhs.name !== "_") {
78
107
  const ownerScope = scope.lookupScope(lhs.name);
79
108
  if (ownerScope?.isConst(lhs.name))
80
109
  this.err(`cannot assign to const '${lhs.name}'`, lhs);
@@ -83,8 +112,13 @@ export const statementCheckMethods = {
83
112
  const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
84
113
  const rhsFlat =
85
114
  rhs.length === 1 && rhs[0]?.kind === "tuple" ? rhs[0].types : rhs;
86
- const lhsTypes = stmt.lhs.map((e) => this.checkExpr(e, scope));
115
+ const lhsTypes = stmt.lhs.map((e) => {
116
+ if (e.kind === "Ident" && e.name === "_") return ANY;
117
+ return this.checkExpr(e, scope);
118
+ });
87
119
  for (let i = 0; i < lhsTypes.length; i++) {
120
+ if (stmt.lhs[i]?.kind === "Ident" && stmt.lhs[i]?.name === "_")
121
+ continue;
88
122
  const r = rhsFlat[i] ?? ANY;
89
123
  if (!isAny(lhsTypes[i]) && !isAny(r)) {
90
124
  this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
@@ -149,15 +183,30 @@ export const statementCheckMethods = {
149
183
 
150
184
  case "ForStmt": {
151
185
  const inner = new Scope(scope);
152
- if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
186
+ let iterInfo = null;
187
+ if (stmt.init?.rhs?.[0]?.kind === "RangeExpr") {
188
+ iterInfo = this._checkRangeIterStmt(stmt.init, inner, returnType);
189
+ }
190
+ if (!iterInfo && stmt.init)
191
+ this.checkStmt(stmt.init, inner, returnType);
153
192
  if (stmt.cond) {
154
- // for range N { } — RangeExpr as condition is valid (Go 1.22 int range)
155
193
  if (stmt.cond.kind !== "RangeExpr") {
156
194
  const ct = this.checkExpr(stmt.cond, inner);
157
195
  if (!isBool(ct) && !isAny(ct))
158
196
  this.err("For condition must be bool", stmt);
159
197
  } else {
160
- this.checkExpr(stmt.cond.expr, inner);
198
+ // for range expr — could be int range or 0-param iterator
199
+ const ct = this.checkExpr(stmt.cond.expr, inner);
200
+ const info = iteratorYieldParams(ct);
201
+ if (info) {
202
+ stmt.cond._isIterator = true;
203
+ stmt.cond._yieldParams = info.yieldParams;
204
+ } else if (ct?.kind === "func") {
205
+ this.err(
206
+ "cannot range over func: not an iterator function",
207
+ stmt,
208
+ );
209
+ }
161
210
  }
162
211
  }
163
212
  if (stmt.post) this.checkStmt(stmt.post, inner, returnType);
@@ -200,6 +249,10 @@ export const statementCheckMethods = {
200
249
  ? this.resolveTypeNode(c.types[0], inner)
201
250
  : ANY;
202
251
  caseScope.defineLocal(stmt.assign, bindType);
252
+ // The capture variable is always considered used — it's the whole
253
+ // point of the switch-with-assign form. Each case body uses its own
254
+ // typed copy of the variable.
255
+ caseScope.lookup(stmt.assign);
203
256
  }
204
257
  for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
205
258
  this._reportUnused(caseScope, stmt);
@@ -247,4 +300,49 @@ export const statementCheckMethods = {
247
300
  break;
248
301
  }
249
302
  },
303
+
304
+ _checkRangeIterStmt(initStmt, scope, _returnType) {
305
+ const rangeExpr = initStmt.rhs[0];
306
+ const iterType = this.checkExpr(rangeExpr.expr, scope);
307
+ const info = iteratorYieldParams(iterType);
308
+ if (!info) {
309
+ // If it's a func but not a valid iterator shape, reject it
310
+ const underlying =
311
+ iterType?.kind === "named" ? iterType.underlying : iterType;
312
+ if (underlying?.kind === "func") {
313
+ this.err("cannot range over func: not an iterator function", initStmt);
314
+ }
315
+ return null;
316
+ }
317
+
318
+ const lhs = initStmt.lhs;
319
+ if (lhs.length > info.yieldParams.length) {
320
+ this.err(
321
+ `range over iterator: too many loop variables (got ${lhs.length}, max ${info.yieldParams.length})`,
322
+ initStmt,
323
+ );
324
+ }
325
+
326
+ // Annotate for codegen
327
+ rangeExpr._isIterator = true;
328
+ rangeExpr._yieldParams = info.yieldParams;
329
+
330
+ // Bind loop variables to their yield param types
331
+ if (initStmt.kind === "DefineStmt") {
332
+ for (let i = 0; i < lhs.length; i++) {
333
+ const name = lhs[i].name ?? lhs[i];
334
+ if (name === "_") continue;
335
+ const varType = info.yieldParams[i] ?? ANY;
336
+ scope.defineLocal(name, varType);
337
+ }
338
+ } else {
339
+ // AssignStmt — just type-check the lhs expressions
340
+ for (const e of lhs) {
341
+ if (e.kind === "Ident" && e.name !== "_") {
342
+ this.checkExpr(e, scope);
343
+ }
344
+ }
345
+ }
346
+ return info;
347
+ },
250
348
  };