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.
- package/README.md +5 -5
- package/package.json +7 -4
- package/src/cli-core.js +65 -0
- package/src/codegen/expressions.js +523 -1717
- package/src/codegen/index.js +576 -0
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +308 -296
- package/src/codegen/stdlib/builder.js +39 -0
- package/src/codegen/stdlib/bytes.js +65 -0
- package/src/codegen/stdlib/errors.js +20 -0
- package/src/codegen/stdlib/fmt.js +104 -0
- package/src/codegen/stdlib/gom.js +155 -0
- package/src/codegen/stdlib/html.js +20 -0
- package/src/codegen/stdlib/index.js +98 -0
- package/src/codegen/stdlib/io.js +36 -0
- package/src/codegen/stdlib/maps.js +45 -0
- package/src/codegen/stdlib/math.js +56 -0
- package/src/codegen/stdlib/os.js +13 -0
- package/src/codegen/stdlib/path.js +34 -0
- package/src/codegen/stdlib/rand.js +27 -0
- package/src/codegen/stdlib/regexp.js +54 -0
- package/src/codegen/stdlib/slices.js +51 -0
- package/src/codegen/stdlib/sort.js +31 -0
- package/src/codegen/stdlib/strconv.js +33 -0
- package/src/codegen/stdlib/strings.js +86 -0
- package/src/codegen/stdlib/time.js +57 -0
- package/src/codegen/stdlib/unicode.js +25 -0
- package/src/codegen/stdlib/utf8.js +26 -0
- package/src/codegen/templ.js +284 -0
- package/src/compiler.js +142 -77
- package/src/dts-parser.js +355 -371
- package/src/index.js +32 -162
- package/src/lexer.js +186 -272
- package/src/minifier.js +305 -318
- package/src/{parser.js → parser/declarations.js} +18 -175
- package/src/parser/expressions.js +51 -34
- package/src/parser/index.js +161 -0
- 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 +261 -0
- package/src/typechecker/expressions.js +568 -549
- package/src/typechecker/index.js +556 -0
- package/src/typechecker/resolve.js +428 -0
- package/src/typechecker/statements.js +270 -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 +86 -0
- package/src/typechecker/types.js +1 -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,
|
|
@@ -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 "
|
|
24
|
-
|
|
41
|
+
case "IncDecStmt":
|
|
42
|
+
case "ExprStmt":
|
|
43
|
+
this.checkExpr(stmt.expr, scope);
|
|
25
44
|
break;
|
|
26
|
-
case "
|
|
27
|
-
|
|
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 "
|
|
30
|
-
this.
|
|
51
|
+
case "LabeledStmt":
|
|
52
|
+
this.checkStmt(stmt.body, scope, returnType);
|
|
31
53
|
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);
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
142
|
+
_checkAssignLhsType(e, scope) {
|
|
143
|
+
if (e.kind === "Ident" && e.name === "_") return ANY;
|
|
144
|
+
return this.checkExpr(e, scope);
|
|
145
|
+
},
|
|
264
146
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
};
|