gofront 0.0.9 → 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.
- package/README.md +6 -6
- package/package.json +7 -4
- package/src/codegen/expressions.js +520 -1717
- package/src/codegen/index.js +576 -0
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +305 -296
- package/src/codegen/stdlib/builder.js +36 -0
- package/src/codegen/stdlib/bytes.js +62 -0
- package/src/codegen/stdlib/errors.js +17 -0
- package/src/codegen/stdlib/fmt.js +101 -0
- package/src/codegen/stdlib/gom.js +152 -0
- package/src/codegen/stdlib/html.js +17 -0
- package/src/codegen/stdlib/index.js +95 -0
- package/src/codegen/stdlib/io.js +33 -0
- package/src/codegen/stdlib/maps.js +42 -0
- package/src/codegen/stdlib/math.js +53 -0
- package/src/codegen/stdlib/os.js +10 -0
- package/src/codegen/stdlib/path.js +31 -0
- package/src/codegen/stdlib/rand.js +24 -0
- package/src/codegen/stdlib/regexp.js +51 -0
- package/src/codegen/stdlib/slices.js +48 -0
- package/src/codegen/stdlib/sort.js +28 -0
- package/src/codegen/stdlib/strconv.js +30 -0
- package/src/codegen/stdlib/strings.js +83 -0
- package/src/codegen/stdlib/time.js +54 -0
- package/src/codegen/stdlib/unicode.js +22 -0
- package/src/codegen/stdlib/utf8.js +23 -0
- package/src/codegen/templ.js +281 -0
- package/src/compiler.js +80 -74
- package/src/dts-parser.js +355 -371
- package/src/index.js +13 -49
- package/src/lexer.js +186 -272
- package/src/minifier.js +305 -318
- package/src/parser/expressions.js +51 -34
- package/src/{parser.js → parser/index.js} +4 -4
- package/src/parser/statements.js +24 -34
- package/src/parser/types.js +31 -29
- package/src/templ-parser.js +2 -2
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +258 -0
- package/src/typechecker/expressions.js +559 -549
- package/src/typechecker/index.js +555 -0
- package/src/typechecker/resolve.js +421 -0
- package/src/typechecker/statements.js +267 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +83 -0
- package/src/codegen.js +0 -882
- 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,
|
|
@@ -20,284 +35,275 @@ export const statementCheckMethods = {
|
|
|
20
35
|
|
|
21
36
|
checkStmt(stmt, scope, returnType) {
|
|
22
37
|
switch (stmt.kind) {
|
|
23
|
-
case "
|
|
24
|
-
|
|
38
|
+
case "IncDecStmt":
|
|
39
|
+
case "ExprStmt":
|
|
40
|
+
this.checkExpr(stmt.expr, scope);
|
|
25
41
|
break;
|
|
26
|
-
case "
|
|
27
|
-
|
|
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 "
|
|
30
|
-
this.
|
|
48
|
+
case "LabeledStmt":
|
|
49
|
+
this.checkStmt(stmt.body, scope, returnType);
|
|
31
50
|
break;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
};
|