gofront 0.0.8 → 1.0.0

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