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
@@ -1,5 +1,20 @@
1
1
  // TypeChecker statement-checking methods — installed as a mixin on TypeChecker.prototype.
2
2
 
3
+ // Method-name dispatch for checkStmt — string values add no static call edges.
4
+ const CHECK_STMT_DELEGATE = {
5
+ VarDecl: "checkVarDecl",
6
+ ConstDecl: "checkConstDecl",
7
+ TypeDecl: "collectType",
8
+ DefineStmt: "_checkDefineStmt",
9
+ AssignStmt: "_checkAssignStmt",
10
+ ReturnStmt: "_checkReturnStmt",
11
+ IfStmt: "_checkIfStmt",
12
+ ForStmt: "_checkForStmt",
13
+ SwitchStmt: "_checkSwitchStmt",
14
+ TypeSwitchStmt: "_checkTypeSwitchStmt",
15
+ BranchStmt: "_checkBranchStmt",
16
+ };
17
+
3
18
  import {
4
19
  ANY,
5
20
  BOOL,
@@ -11,6 +26,9 @@ import {
11
26
  Scope,
12
27
  } from "./types.js";
13
28
 
29
+ /** @typedef {import('./index.js').TypeChecker} TypeChecker */
30
+
31
+ /** @type {ThisType<TypeChecker>} */
14
32
  export const statementCheckMethods = {
15
33
  checkBlock(block, scope, returnType) {
16
34
  for (const stmt of block.stmts) {
@@ -20,284 +38,275 @@ export const statementCheckMethods = {
20
38
 
21
39
  checkStmt(stmt, scope, returnType) {
22
40
  switch (stmt.kind) {
23
- case "VarDecl":
24
- this.checkVarDecl(stmt, scope);
41
+ case "IncDecStmt":
42
+ case "ExprStmt":
43
+ this.checkExpr(stmt.expr, scope);
25
44
  break;
26
- case "ConstDecl":
27
- this.checkConstDecl(stmt, scope);
45
+ case "DeferStmt":
46
+ if (stmt.call.kind !== "CallExpr")
47
+ this.err("defer requires a function call", stmt.call);
48
+ this.checkExpr(stmt.call, scope);
49
+ this._deferCount++;
28
50
  break;
29
- case "TypeDecl":
30
- this.collectType(stmt);
51
+ case "LabeledStmt":
52
+ this.checkStmt(stmt.body, scope, returnType);
31
53
  break;
32
-
33
- case "DefineStmt": {
34
- // comma-ok type assertion: n, ok := x.(T)
35
- if (
36
- stmt.lhs.length === 2 &&
37
- stmt.rhs.length === 1 &&
38
- stmt.rhs[0].kind === "TypeAssertExpr"
39
- ) {
40
- stmt.rhs[0]._commaOk = true;
41
- }
42
- const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
43
- // Flatten tuple returns
44
- const rhsFlat =
45
- rhs.length === 1 && rhs[0].kind === "tuple" ? rhs[0].types : rhs;
46
- // comma-ok type assertion returns [assertedType, bool]
47
- if (stmt.rhs[0]?._commaOk) {
48
- const lhsNames = stmt.lhs.map((e) => e.name ?? e);
49
- if (lhsNames[0] !== "_") scope.defineLocal(lhsNames[0], rhs[0]);
50
- if (lhsNames[1] !== "_") scope.defineLocal(lhsNames[1], BOOL);
51
- stmt._rhsTypes = [rhs[0], BOOL];
52
- break;
53
- }
54
- for (let i = 0; i < stmt.lhs.length; i++) {
55
- const name = stmt.lhs[i].name ?? stmt.lhs[i];
56
- if (name === "_") continue;
57
- // Short variable re-declaration: mark vars that already exist
58
- // in the current (not parent) scope as redeclared.
59
- if (scope.symbols.has(name)) {
60
- stmt.lhs[i]._redecl = true;
61
- }
62
- scope.defineLocal(name, defaultType(rhsFlat[i]) ?? ANY);
63
- }
64
- // Go requires at least one new variable in :=
65
- const allRedecl = stmt.lhs.every(
66
- (e) => e._redecl || (e.name ?? e) === "_",
67
- );
68
- if (allRedecl && stmt.lhs.length > 0) {
69
- this.err("no new variables on left side of :=", stmt.lhs[0]);
70
- }
71
- stmt._rhsTypes = rhsFlat.map((t) => defaultType(t) ?? ANY);
54
+ case "Block": {
55
+ const blockScope = new Scope(scope);
56
+ this.checkBlock(stmt, blockScope, returnType);
57
+ this._reportUnused(blockScope, stmt);
72
58
  break;
73
59
  }
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
- }
104
- // Check for const reassignment before evaluating RHS
105
- for (const lhs of stmt.lhs) {
106
- if (lhs.kind === "Ident" && lhs.name !== "_") {
107
- const ownerScope = scope.lookupScope(lhs.name);
108
- if (ownerScope?.isConst(lhs.name))
109
- this.err(`cannot assign to const '${lhs.name}'`, lhs);
110
- }
111
- }
112
- const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
113
- const rhsFlat =
114
- rhs.length === 1 && rhs[0]?.kind === "tuple" ? rhs[0].types : rhs;
115
- const lhsTypes = stmt.lhs.map((e) => {
116
- if (e.kind === "Ident" && e.name === "_") return ANY;
117
- return this.checkExpr(e, scope);
118
- });
119
- for (let i = 0; i < lhsTypes.length; i++) {
120
- if (stmt.lhs[i]?.kind === "Ident" && stmt.lhs[i]?.name === "_")
121
- continue;
122
- const r = rhsFlat[i] ?? ANY;
123
- if (!isAny(lhsTypes[i]) && !isAny(r)) {
124
- this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
125
- }
126
- }
60
+ default: {
61
+ const m = CHECK_STMT_DELEGATE[stmt.kind];
62
+ if (m) this[m](stmt, scope, returnType);
127
63
  break;
128
64
  }
65
+ }
66
+ },
129
67
 
130
- case "IncDecStmt":
131
- this.checkExpr(stmt.expr, scope);
132
- break;
68
+ _checkDefineCommaOkAssert(stmt, rhs, scope) {
69
+ if (!stmt.rhs[0]?._commaOk) return false;
70
+ const lhsNames = stmt.lhs.map((e) => e.name ?? e);
71
+ if (lhsNames[0] !== "_") scope.defineLocal(lhsNames[0], rhs[0]);
72
+ if (lhsNames[1] !== "_") scope.defineLocal(lhsNames[1], BOOL);
73
+ stmt._rhsTypes = [rhs[0], BOOL];
74
+ return true;
75
+ },
133
76
 
134
- case "ExprStmt":
135
- this.checkExpr(stmt.expr, scope);
136
- break;
77
+ _declareDefineVars(stmt, rhsFlat, scope) {
78
+ for (let i = 0; i < stmt.lhs.length; i++) {
79
+ const name = stmt.lhs[i].name ?? stmt.lhs[i];
80
+ if (name === "_") continue;
81
+ if (scope.symbols.has(name)) stmt.lhs[i]._redecl = true;
82
+ scope.defineLocal(name, defaultType(rhsFlat[i]) ?? ANY);
83
+ }
84
+ },
137
85
 
138
- case "ReturnStmt": {
139
- const types = stmt.values.map((v) => this.checkExpr(v, scope));
140
- stmt._types = types;
141
- if (
142
- !isVoid(returnType) &&
143
- types.length === 0 &&
144
- !returnType?._hasNamedReturns
145
- ) {
146
- this.err("Missing return value", stmt);
147
- }
148
- // Check each returned value is assignable to its declared type
149
- if (!isVoid(returnType) && types.length > 0) {
150
- const expected =
151
- returnType.kind === "tuple" ? returnType.types : [returnType];
152
- for (let i = 0; i < expected.length; i++) {
153
- if (types[i])
154
- this.assertAssignable(
155
- expected[i],
156
- types[i],
157
- stmt.values[i] ?? stmt,
158
- );
159
- }
160
- }
161
- break;
162
- }
86
+ _checkDefineStmt(stmt, scope) {
87
+ // comma-ok type assertion: n, ok := x.(T)
88
+ if (
89
+ stmt.lhs.length === 2 &&
90
+ stmt.rhs.length === 1 &&
91
+ stmt.rhs[0].kind === "TypeAssertExpr"
92
+ ) {
93
+ stmt.rhs[0]._commaOk = true;
94
+ }
95
+ const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
96
+ const rhsFlat =
97
+ rhs.length === 1 && rhs[0].kind === "tuple" ? rhs[0].types : rhs;
98
+ if (this._checkDefineCommaOkAssert(stmt, rhs, scope)) return;
99
+ this._declareDefineVars(stmt, rhsFlat, scope);
100
+ const allRedecl = stmt.lhs.every((e) => e._redecl || (e.name ?? e) === "_");
101
+ if (allRedecl && stmt.lhs.length > 0) {
102
+ this.err("no new variables on left side of :=", stmt.lhs[0]);
103
+ }
104
+ stmt._rhsTypes = rhsFlat.map((t) => defaultType(t) ?? ANY);
105
+ },
163
106
 
164
- case "IfStmt": {
165
- const inner = new Scope(scope);
166
- if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
167
- const ct = this.checkExpr(stmt.cond, inner);
168
- if (!isBool(ct) && !isAny(ct))
169
- this.err("If condition must be bool", stmt);
170
- const bodyScope = new Scope(inner);
171
- this.checkBlock(stmt.body, bodyScope, returnType);
172
- this._reportUnused(bodyScope, stmt);
173
- if (stmt.elseBody) {
174
- if (stmt.elseBody.kind === "Block") {
175
- const elseScope = new Scope(inner);
176
- this.checkBlock(stmt.elseBody, elseScope, returnType);
177
- this._reportUnused(elseScope, stmt);
178
- } else this.checkStmt(stmt.elseBody, inner, returnType);
179
- }
180
- this._reportUnused(inner, stmt);
181
- break;
182
- }
107
+ _checkAssignCommaOkMap(stmt, scope) {
108
+ if (
109
+ stmt.lhs.length !== 2 ||
110
+ stmt.rhs.length !== 1 ||
111
+ stmt.rhs[0].kind !== "IndexExpr"
112
+ )
113
+ return false;
114
+ const mapBaseType = this.checkExpr(stmt.rhs[0].expr, scope);
115
+ const resolvedBase =
116
+ mapBaseType?.kind === "named" ? mapBaseType.underlying : mapBaseType;
117
+ if (resolvedBase?.kind !== "map") return false;
118
+ this.checkExpr(stmt.rhs[0].index, scope);
119
+ stmt._commaOkMap = true;
120
+ stmt.rhs[0]._mapValueType = resolvedBase.value;
121
+ return true;
122
+ },
183
123
 
184
- case "ForStmt": {
185
- const inner = new Scope(scope);
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);
192
- if (stmt.cond) {
193
- if (stmt.cond.kind !== "RangeExpr") {
194
- const ct = this.checkExpr(stmt.cond, inner);
195
- if (!isBool(ct) && !isAny(ct))
196
- this.err("For condition must be bool", stmt);
197
- } else {
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
- }
210
- }
211
- }
212
- if (stmt.post) this.checkStmt(stmt.post, inner, returnType);
213
- this._loopDepth++;
214
- const bodyScope = new Scope(inner);
215
- this.checkBlock(stmt.body, bodyScope, returnType);
216
- this._reportUnused(bodyScope, stmt);
217
- this._loopDepth--;
218
- this._reportUnused(inner, stmt);
219
- break;
124
+ _checkConstAssignment(stmt, scope) {
125
+ for (const lhs of stmt.lhs) {
126
+ if (lhs.kind === "Ident" && lhs.name !== "_") {
127
+ const ownerScope = scope.lookupScope(lhs.name);
128
+ if (ownerScope?.isConst(lhs.name))
129
+ this.err(`cannot assign to const '${lhs.name}'`, lhs);
220
130
  }
131
+ }
132
+ },
221
133
 
222
- case "SwitchStmt": {
223
- const inner = new Scope(scope);
224
- if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
225
- if (stmt.tag) this.checkExpr(stmt.tag, inner);
226
- this._switchDepth++;
227
- for (const c of stmt.cases) {
228
- const caseScope = new Scope(inner);
229
- if (c.list) for (const e of c.list) this.checkExpr(e, caseScope);
230
- for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
231
- this._reportUnused(caseScope, stmt);
232
- }
233
- this._switchDepth--;
234
- this._reportUnused(inner, stmt);
235
- break;
236
- }
134
+ _isCommaOkTypeAssertAssign(stmt) {
135
+ return (
136
+ stmt.lhs.length === 2 &&
137
+ stmt.rhs.length === 1 &&
138
+ stmt.rhs[0].kind === "TypeAssertExpr"
139
+ );
140
+ },
237
141
 
238
- case "TypeSwitchStmt": {
239
- const inner = new Scope(scope);
240
- this.checkExpr(stmt.expr, inner);
241
- this._switchDepth++;
242
- this._typeSwitchDepth++;
243
- for (const c of stmt.cases) {
244
- const caseScope = new Scope(inner);
245
- if (stmt.assign) {
246
- // Bind the variable to the single case type, or any for multi/default
247
- const bindType =
248
- c.types?.length === 1
249
- ? this.resolveTypeNode(c.types[0], inner)
250
- : ANY;
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);
256
- }
257
- for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
258
- this._reportUnused(caseScope, stmt);
259
- }
260
- this._typeSwitchDepth--;
261
- this._switchDepth--;
262
- break;
263
- }
142
+ _checkAssignLhsType(e, scope) {
143
+ if (e.kind === "Ident" && e.name === "_") return ANY;
144
+ return this.checkExpr(e, scope);
145
+ },
264
146
 
265
- case "DeferStmt": {
266
- if (stmt.call.kind !== "CallExpr")
267
- this.err("defer requires a function call", stmt.call);
268
- this.checkExpr(stmt.call, scope);
269
- this._deferCount++;
270
- break;
147
+ _checkAssignTypes(stmt, rhsFlat, lhsTypes) {
148
+ for (let i = 0; i < lhsTypes.length; i++) {
149
+ if (stmt.lhs[i]?.kind === "Ident" && stmt.lhs[i]?.name === "_") continue;
150
+ const r = rhsFlat[i] ?? ANY;
151
+ if (!isAny(lhsTypes[i]) && !isAny(r))
152
+ this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
153
+ }
154
+ },
155
+
156
+ _checkAssignStmt(stmt, scope) {
157
+ if (this._checkAssignCommaOkMap(stmt, scope)) return;
158
+ if (this._isCommaOkTypeAssertAssign(stmt)) stmt.rhs[0]._commaOk = true;
159
+ this._checkConstAssignment(stmt, scope);
160
+ const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
161
+ const rhsFlat =
162
+ rhs.length === 1 && rhs[0]?.kind === "tuple" ? rhs[0].types : rhs;
163
+ const lhsTypes = stmt.lhs.map((e) => this._checkAssignLhsType(e, scope));
164
+ this._checkAssignTypes(stmt, rhsFlat, lhsTypes);
165
+ },
166
+
167
+ _checkReturnStmt(stmt, scope, returnType) {
168
+ const types = stmt.values.map((v) => this.checkExpr(v, scope));
169
+ stmt._types = types;
170
+ if (
171
+ !isVoid(returnType) &&
172
+ types.length === 0 &&
173
+ !returnType?._hasNamedReturns
174
+ ) {
175
+ this.err("Missing return value", stmt);
176
+ }
177
+ // Check each returned value is assignable to its declared type
178
+ if (!isVoid(returnType) && types.length > 0) {
179
+ const expected =
180
+ returnType.kind === "tuple" ? returnType.types : [returnType];
181
+ for (let i = 0; i < expected.length; i++) {
182
+ if (types[i])
183
+ this.assertAssignable(expected[i], types[i], stmt.values[i] ?? stmt);
271
184
  }
185
+ }
186
+ },
272
187
 
273
- case "LabeledStmt":
274
- this.checkStmt(stmt.body, scope, returnType);
275
- break;
188
+ _checkIfStmt(stmt, scope, returnType) {
189
+ const inner = new Scope(scope);
190
+ if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
191
+ const ct = this.checkExpr(stmt.cond, inner);
192
+ if (!isBool(ct) && !isAny(ct)) this.err("If condition must be bool", stmt);
193
+ const bodyScope = new Scope(inner);
194
+ this.checkBlock(stmt.body, bodyScope, returnType);
195
+ this._reportUnused(bodyScope, stmt);
196
+ if (stmt.elseBody) {
197
+ if (stmt.elseBody.kind === "Block") {
198
+ const elseScope = new Scope(inner);
199
+ this.checkBlock(stmt.elseBody, elseScope, returnType);
200
+ this._reportUnused(elseScope, stmt);
201
+ } else this.checkStmt(stmt.elseBody, inner, returnType);
202
+ }
203
+ this._reportUnused(inner, stmt);
204
+ },
276
205
 
277
- case "BranchStmt": {
278
- const kw = stmt.keyword;
279
- if (kw === "continue" && this._loopDepth === 0)
280
- this.err("continue statement outside for loop", stmt);
281
- else if (
282
- kw === "break" &&
283
- this._loopDepth === 0 &&
284
- this._switchDepth === 0
285
- )
286
- this.err("break statement outside for loop or switch", stmt);
287
- else if (kw === "fallthrough" && this._switchDepth === 0)
288
- this.err("fallthrough statement outside switch", stmt);
289
- else if (kw === "fallthrough" && this._typeSwitchDepth > 0)
290
- this.err("cannot fallthrough in type switch", stmt);
291
- break;
206
+ _checkForCond(stmt, inner) {
207
+ if (stmt.cond.kind !== "RangeExpr") {
208
+ const ct = this.checkExpr(stmt.cond, inner);
209
+ if (!isBool(ct) && !isAny(ct))
210
+ this.err("For condition must be bool", stmt);
211
+ } else {
212
+ const ct = this.checkExpr(stmt.cond.expr, inner);
213
+ const info = iteratorYieldParams(ct);
214
+ if (info) {
215
+ stmt.cond._isIterator = true;
216
+ stmt.cond._yieldParams = info.yieldParams;
217
+ } else if (ct?.kind === "func") {
218
+ this.err("cannot range over func: not an iterator function", stmt);
292
219
  }
293
- case "Block": {
294
- const blockScope = new Scope(scope);
295
- this.checkBlock(stmt, blockScope, returnType);
296
- this._reportUnused(blockScope, stmt);
297
- break;
220
+ }
221
+ },
222
+
223
+ _checkForStmt(stmt, scope, returnType) {
224
+ const inner = new Scope(scope);
225
+ let iterInfo = null;
226
+ if (stmt.init?.rhs?.[0]?.kind === "RangeExpr") {
227
+ iterInfo = this._checkRangeIterStmt(stmt.init, inner, returnType);
228
+ }
229
+ if (!iterInfo && stmt.init) this.checkStmt(stmt.init, inner, returnType);
230
+ if (stmt.cond) this._checkForCond(stmt, inner);
231
+ if (stmt.post) this.checkStmt(stmt.post, inner, returnType);
232
+ this._loopDepth++;
233
+ const bodyScope = new Scope(inner);
234
+ this.checkBlock(stmt.body, bodyScope, returnType);
235
+ this._reportUnused(bodyScope, stmt);
236
+ this._loopDepth--;
237
+ this._reportUnused(inner, stmt);
238
+ },
239
+
240
+ _checkSwitchStmt(stmt, scope, returnType) {
241
+ const inner = new Scope(scope);
242
+ if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
243
+ if (stmt.tag) this.checkExpr(stmt.tag, inner);
244
+ this._switchDepth++;
245
+ for (const c of stmt.cases) {
246
+ const caseScope = new Scope(inner);
247
+ if (c.list) for (const e of c.list) this.checkExpr(e, caseScope);
248
+ for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
249
+ this._reportUnused(caseScope, stmt);
250
+ }
251
+ this._switchDepth--;
252
+ this._reportUnused(inner, stmt);
253
+ },
254
+
255
+ _checkTypeSwitchStmt(stmt, scope, returnType) {
256
+ const inner = new Scope(scope);
257
+ this.checkExpr(stmt.expr, inner);
258
+ this._switchDepth++;
259
+ this._typeSwitchDepth++;
260
+ for (const c of stmt.cases) {
261
+ const caseScope = new Scope(inner);
262
+ if (stmt.assign) {
263
+ // Bind the variable to the single case type, or any for multi/default
264
+ const bindType =
265
+ c.types?.length === 1 ? this.resolveTypeNode(c.types[0], inner) : ANY;
266
+ caseScope.defineLocal(stmt.assign, bindType);
267
+ // The capture variable is always considered used — it's the whole
268
+ // point of the switch-with-assign form. Each case body uses its own
269
+ // typed copy of the variable.
270
+ caseScope.lookup(stmt.assign);
298
271
  }
299
- default:
300
- break;
272
+ for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
273
+ this._reportUnused(caseScope, stmt);
274
+ }
275
+ this._typeSwitchDepth--;
276
+ this._switchDepth--;
277
+ },
278
+
279
+ _checkBranchStmt(stmt) {
280
+ const kw = stmt.keyword;
281
+ if (kw === "continue" && this._loopDepth === 0)
282
+ this.err("continue statement outside for loop", stmt);
283
+ else if (kw === "break" && this._loopDepth === 0 && this._switchDepth === 0)
284
+ this.err("break statement outside for loop or switch", stmt);
285
+ else if (kw === "fallthrough" && this._switchDepth === 0)
286
+ this.err("fallthrough statement outside switch", stmt);
287
+ else if (kw === "fallthrough" && this._typeSwitchDepth > 0)
288
+ this.err("cannot fallthrough in type switch", stmt);
289
+ },
290
+
291
+ _checkRangeIterInvalidFunc(iterType, initStmt) {
292
+ const underlying =
293
+ iterType?.kind === "named" ? iterType.underlying : iterType;
294
+ if (underlying?.kind === "func")
295
+ this.err("cannot range over func: not an iterator function", initStmt);
296
+ },
297
+
298
+ _bindRangeIterDefineVars(lhs, info, scope) {
299
+ for (let i = 0; i < lhs.length; i++) {
300
+ const name = lhs[i].name ?? lhs[i];
301
+ if (name === "_") continue;
302
+ const varType = info.yieldParams[i] ?? ANY;
303
+ scope.defineLocal(name, varType);
304
+ }
305
+ },
306
+
307
+ _checkRangeIterAssignVars(lhs, scope) {
308
+ for (const e of lhs) {
309
+ if (e.kind === "Ident" && e.name !== "_") this.checkExpr(e, scope);
301
310
  }
302
311
  },
303
312
 
@@ -306,12 +315,7 @@ export const statementCheckMethods = {
306
315
  const iterType = this.checkExpr(rangeExpr.expr, scope);
307
316
  const info = iteratorYieldParams(iterType);
308
317
  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
- }
318
+ this._checkRangeIterInvalidFunc(iterType, initStmt);
315
319
  return null;
316
320
  }
317
321
 
@@ -323,26 +327,12 @@ export const statementCheckMethods = {
323
327
  );
324
328
  }
325
329
 
326
- // Annotate for codegen
327
330
  rangeExpr._isIterator = true;
328
331
  rangeExpr._yieldParams = info.yieldParams;
329
332
 
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
- }
333
+ if (initStmt.kind === "DefineStmt")
334
+ this._bindRangeIterDefineVars(lhs, info, scope);
335
+ else this._checkRangeIterAssignVars(lhs, scope);
346
336
  return info;
347
337
  },
348
338
  };