gofront 1.8.0 → 1.9.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 +3 -3
- package/package.json +6 -5
- package/src/backend/js/expressions.js +1157 -408
- package/src/backend/js/index.js +327 -153
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +559 -2552
- package/src/backend/js/statements.js +670 -158
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +298 -19
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +91 -170
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +884 -256
- package/src/backend/wasm/emit-exprs.js +185 -77
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +524 -240
- package/src/backend/wasm/emit.js +176 -38
- package/src/backend/wasm/globals.js +48 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +44 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +299 -770
- package/src/backend/wasm/monomorph.js +139 -8
- package/src/backend/wasm/natives.js +600 -0
- package/src/backend/wasm/prescan.js +5 -153
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -32
- package/src/backend/wasm/types.js +97 -20
- package/src/backend/wasm/variadic.js +9 -4
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +113 -130
- package/src/lexer/lexer.js +106 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +355 -132
- package/src/link/type-names.js +204 -0
- package/src/link/wasm.js +30 -6
- package/src/lower/bounds.js +153 -0
- package/src/lower/captures.js +92 -33
- package/src/lower/embedding.js +62 -5
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +929 -0
- package/src/lower/functions.js +225 -7
- package/src/lower/gen-types.js +293 -0
- package/src/lower/index.js +14 -2
- package/src/lower/json-codecs.js +334 -245
- package/src/lower/ownership.js +25 -7
- package/src/lower/reachability.js +69 -51
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +30 -5
- package/src/parser/expressions.js +111 -57
- package/src/parser/statements.js +108 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +12 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +214 -19
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +156 -8
- package/src/typechecker/expressions.js +490 -108
- package/src/typechecker/index.js +346 -42
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +41 -0
- package/src/typechecker/resolve.js +490 -65
- package/src/typechecker/statements.js +288 -27
- package/src/typechecker/stdlib/excluded.js +9 -5
- package/src/typechecker/stdlib/index.js +25 -9
- package/src/typechecker/termination.js +10 -1
- package/src/typechecker/types.js +287 -575
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +171 -38
- package/stdlib/internal/jsonrt/enc.go +186 -36
- package/stdlib/internal/jsonrt/scanner.go +17 -8
- package/stdlib/io/io.go +157 -1
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +16 -14
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +391 -452
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +235 -50
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +141 -3
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/fmt-runtime.js +0 -573
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -119
- package/src/backend/js/stdlib/builder.js +0 -98
- package/src/backend/js/stdlib/bytes.js +0 -368
- package/src/backend/js/stdlib/errors.js +0 -45
- package/src/backend/js/stdlib/fmt.js +0 -517
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -1167
- package/src/backend/js/stdlib/maps.js +0 -37
- package/src/backend/js/stdlib/math.js +0 -85
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -34
- package/src/backend/js/stdlib/slices.js +0 -107
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -79
- package/src/backend/js/stdlib/strings.js +0 -301
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -41
- package/src/backend/js/stdlib/time.js +0 -152
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/stdlib/bytes.js +0 -6716
- package/src/backend/wasm/stdlib/core.js +0 -10278
- package/src/backend/wasm/stdlib/io.js +0 -3578
- package/src/backend/wasm/stdlib/packages.js +0 -2402
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -2229
- package/src/typechecker/stdlib/core.js +0 -1079
- package/src/typechecker/stdlib/extended.js +0 -1210
- package/stdlib/bytes/iter.go +0 -192
- package/stdlib/bytes/search.go +0 -240
- package/stdlib/bytes/split.go +0 -218
- package/stdlib/errors/overlay.go +0 -12
- package/stdlib/maps/iter.go +0 -65
- package/stdlib/slices/extra.go +0 -73
- package/stdlib/slices/iter.go +0 -113
- package/stdlib/strings/extra.go +0 -35
- package/stdlib/strings/iter.go +0 -122
- package/stdlib/sync/extra.go +0 -105
- package/stdlib/unicode/utf8/bytes.go +0 -239
|
@@ -15,19 +15,28 @@ const CHECK_STMT_DELEGATE = {
|
|
|
15
15
|
BranchStmt: "_checkBranchStmt",
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
import { exprText, operandKind } from "./operand-text.js";
|
|
18
19
|
import {
|
|
19
20
|
ANY,
|
|
20
21
|
BOOL,
|
|
21
22
|
defaultType,
|
|
23
|
+
is64BitInt,
|
|
22
24
|
isAny,
|
|
23
25
|
isBool,
|
|
26
|
+
isNil,
|
|
27
|
+
isString,
|
|
28
|
+
isUntyped,
|
|
24
29
|
isVoid,
|
|
25
30
|
iteratorYieldParams,
|
|
26
31
|
Scope,
|
|
32
|
+
typeStr,
|
|
27
33
|
} from "./types.js";
|
|
28
34
|
|
|
29
35
|
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
30
36
|
|
|
37
|
+
// Tag kinds with no nil value: `case nil` against them cannot compile.
|
|
38
|
+
const NON_NILABLE_TAG_KINDS = new Set(["basic", "struct", "array"]);
|
|
39
|
+
|
|
31
40
|
/** @type {ThisType<TypeChecker>} */
|
|
32
41
|
export const statementCheckMethods = {
|
|
33
42
|
checkBlock(block, scope, returnType) {
|
|
@@ -67,18 +76,41 @@ export const statementCheckMethods = {
|
|
|
67
76
|
|
|
68
77
|
_checkDefineCommaOkAssert(stmt, rhs, scope) {
|
|
69
78
|
if (!stmt.rhs[0]?._commaOk) return false;
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
|
|
79
|
+
this._declareDefineVars(stmt, [rhs[0], BOOL], scope);
|
|
80
|
+
if (stmt.lhs.every((e) => e._redecl || (e.name ?? e) === "_"))
|
|
81
|
+
this.err("no new variables on left side of :=", stmt.lhs[0]);
|
|
73
82
|
stmt._rhsTypes = [rhs[0], BOOL];
|
|
74
83
|
return true;
|
|
75
84
|
},
|
|
76
85
|
|
|
86
|
+
_assignOperand(stmt, i) {
|
|
87
|
+
if (stmt.rhs.length === stmt.lhs.length) return stmt.rhs[i];
|
|
88
|
+
return stmt.rhs.length === 1 ? stmt.rhs[0] : undefined;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
_checkRedeclAssign(target, source, stmt, i) {
|
|
92
|
+
if (!target || !source || !this._needsAssignCheck(target, source)) return;
|
|
93
|
+
this._skipIfaceMark = true;
|
|
94
|
+
this.assertAssignable(
|
|
95
|
+
target,
|
|
96
|
+
source,
|
|
97
|
+
stmt.lhs[i],
|
|
98
|
+
"assignment",
|
|
99
|
+
this._assignOperand(stmt, i),
|
|
100
|
+
);
|
|
101
|
+
this._skipIfaceMark = false;
|
|
102
|
+
},
|
|
103
|
+
|
|
77
104
|
_declareDefineVars(stmt, rhsFlat, scope) {
|
|
78
105
|
for (let i = 0; i < stmt.lhs.length; i++) {
|
|
79
106
|
const name = stmt.lhs[i].name ?? stmt.lhs[i];
|
|
80
107
|
if (name === "_") continue;
|
|
81
|
-
if (scope.symbols.has(name))
|
|
108
|
+
if (scope.symbols.has(name)) {
|
|
109
|
+
stmt.lhs[i]._redecl = true;
|
|
110
|
+
// Redeclared variables keep their type; the value must be assignable.
|
|
111
|
+
this._checkRedeclAssign(scope.symbols.get(name), rhsFlat[i], stmt, i);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
82
114
|
if (stmt.rhs.length === stmt.lhs.length)
|
|
83
115
|
this._checkConstDefault(
|
|
84
116
|
rhsFlat[i],
|
|
@@ -97,10 +129,10 @@ export const statementCheckMethods = {
|
|
|
97
129
|
)
|
|
98
130
|
return false;
|
|
99
131
|
const mapBaseType = this.checkExpr(stmt.rhs[0].expr, scope);
|
|
100
|
-
const resolvedBase =
|
|
101
|
-
mapBaseType?.kind === "named" ? mapBaseType.underlying : mapBaseType;
|
|
132
|
+
const resolvedBase = this._underlyingOrCore(mapBaseType, scope);
|
|
102
133
|
if (resolvedBase?.kind !== "map") return false;
|
|
103
134
|
const keyType = this.checkExpr(stmt.rhs[0].index, scope);
|
|
135
|
+
this.assertAssignable(resolvedBase.key, keyType, stmt.rhs[0].index);
|
|
104
136
|
this._markIfaceKey(resolvedBase, stmt.rhs[0].index, keyType);
|
|
105
137
|
stmt._commaOkMap = true;
|
|
106
138
|
stmt.rhs[0]._mapValueType = resolvedBase.value;
|
|
@@ -129,7 +161,8 @@ export const statementCheckMethods = {
|
|
|
129
161
|
if (this._checkDefineCommaOkAssert(stmt, rhs, scope)) return;
|
|
130
162
|
if (
|
|
131
163
|
stmt.rhs[0]?.kind !== "RangeExpr" &&
|
|
132
|
-
stmt.lhs.length !== rhsFlat.length
|
|
164
|
+
stmt.lhs.length !== rhsFlat.length &&
|
|
165
|
+
!rhsFlat.some((t) => t?._tainted)
|
|
133
166
|
) {
|
|
134
167
|
const vars =
|
|
135
168
|
stmt.lhs.length === 1 ? "1 variable" : `${stmt.lhs.length} variables`;
|
|
@@ -156,10 +189,10 @@ export const statementCheckMethods = {
|
|
|
156
189
|
)
|
|
157
190
|
return null;
|
|
158
191
|
const mapBaseType = this.checkExpr(stmt.rhs[0].expr, scope);
|
|
159
|
-
const resolvedBase =
|
|
160
|
-
mapBaseType?.kind === "named" ? mapBaseType.underlying : mapBaseType;
|
|
192
|
+
const resolvedBase = this._underlyingOrCore(mapBaseType, scope);
|
|
161
193
|
if (resolvedBase?.kind !== "map") return null;
|
|
162
194
|
const keyType = this.checkExpr(stmt.rhs[0].index, scope);
|
|
195
|
+
this.assertAssignable(resolvedBase.key, keyType, stmt.rhs[0].index);
|
|
163
196
|
this._markIfaceKey(resolvedBase, stmt.rhs[0].index, keyType);
|
|
164
197
|
stmt._commaOkMap = true;
|
|
165
198
|
stmt.rhs[0]._mapValueType = resolvedBase.value;
|
|
@@ -189,10 +222,37 @@ export const statementCheckMethods = {
|
|
|
189
222
|
return this.checkExpr(e, scope);
|
|
190
223
|
},
|
|
191
224
|
|
|
225
|
+
// A source-written `any` operand still goes through assertAssignable so a
|
|
226
|
+
// concrete target reports `need type assertion`; interop `any` stays exempt.
|
|
227
|
+
_needsAssignCheck(target, source) {
|
|
228
|
+
return !isAny(target) && (!isAny(source) || !!source._declared);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
// A range assignment (`for _, s = range xs`) stores each key and element
|
|
232
|
+
// into an existing variable; one of interface type boxes the value as
|
|
233
|
+
// any other conversion does. The box is recorded on a holder, not on the
|
|
234
|
+
// target, which codegen also reads as an assignable expression.
|
|
235
|
+
_markRangeAssignBox(stmt, i, target, source) {
|
|
236
|
+
if (stmt.rhs[0]?.kind !== "RangeExpr") return;
|
|
237
|
+
this._markRangeTargetBox(stmt.lhs[i], target, source);
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
_markRangeTargetBox(node, target, source) {
|
|
241
|
+
if (!target || !source) return;
|
|
242
|
+
const holder = {};
|
|
243
|
+
this._markIfaceBox(
|
|
244
|
+
this.resolveType(target),
|
|
245
|
+
this.resolveType(source),
|
|
246
|
+
holder,
|
|
247
|
+
);
|
|
248
|
+
if (holder._ifaceBox) node._rangeBox = holder;
|
|
249
|
+
},
|
|
250
|
+
|
|
192
251
|
_checkAssignTypes(stmt, rhsFlat, lhsTypes) {
|
|
193
252
|
for (let i = 0; i < lhsTypes.length; i++) {
|
|
194
253
|
if (stmt.lhs[i]?.kind === "Ident" && stmt.lhs[i]?.name === "_") continue;
|
|
195
254
|
const r = rhsFlat[i] ?? ANY;
|
|
255
|
+
this._markRangeAssignBox(stmt, i, lhsTypes[i], r);
|
|
196
256
|
if (stmt.rhs.length === lhsTypes.length && lhsTypes[i] && r)
|
|
197
257
|
this._markIfaceBox(
|
|
198
258
|
this.resolveType(lhsTypes[i]),
|
|
@@ -209,9 +269,15 @@ export const statementCheckMethods = {
|
|
|
209
269
|
this._checkConstOverflow(lhsTypes[i], r, stmt.rhs[i], "assignment")
|
|
210
270
|
)
|
|
211
271
|
continue;
|
|
212
|
-
if (
|
|
272
|
+
if (this._needsAssignCheck(lhsTypes[i], r)) {
|
|
213
273
|
this._skipIfaceMark = true;
|
|
214
|
-
this.assertAssignable(
|
|
274
|
+
this.assertAssignable(
|
|
275
|
+
lhsTypes[i],
|
|
276
|
+
r,
|
|
277
|
+
stmt.lhs[i],
|
|
278
|
+
"assignment",
|
|
279
|
+
this._assignOperand(stmt, i),
|
|
280
|
+
);
|
|
215
281
|
this._skipIfaceMark = false;
|
|
216
282
|
}
|
|
217
283
|
}
|
|
@@ -231,7 +297,8 @@ export const statementCheckMethods = {
|
|
|
231
297
|
if (
|
|
232
298
|
!stmt.rhs[0]?._commaOk &&
|
|
233
299
|
stmt.rhs[0]?.kind !== "RangeExpr" &&
|
|
234
|
-
stmt.lhs.length !== rhsFlat.length
|
|
300
|
+
stmt.lhs.length !== rhsFlat.length &&
|
|
301
|
+
!rhsFlat.some((t) => t?._tainted)
|
|
235
302
|
) {
|
|
236
303
|
const vars =
|
|
237
304
|
stmt.lhs.length === 1 ? "1 variable" : `${stmt.lhs.length} variables`;
|
|
@@ -247,9 +314,34 @@ export const statementCheckMethods = {
|
|
|
247
314
|
}
|
|
248
315
|
}
|
|
249
316
|
const lhsTypes = stmt.lhs.map((e) => this._checkAssignLhsType(e, scope));
|
|
317
|
+
// A rejected range index is not checked again as an ordinary value.
|
|
318
|
+
if (this._checkRangeIndexAssign(stmt, lhsTypes))
|
|
319
|
+
rhsFlat = [ANY, ...rhsFlat.slice(1)];
|
|
250
320
|
this._checkAssignTypes(stmt, rhsFlat, lhsTypes);
|
|
251
321
|
},
|
|
252
322
|
|
|
323
|
+
// The index of a string, slice or array range is a typed `int`: Go assigns
|
|
324
|
+
// it only to `int` (or an interface), never to another integer type.
|
|
325
|
+
_checkRangeIndexAssign(stmt, lhsTypes) {
|
|
326
|
+
const range = stmt.rhs[0];
|
|
327
|
+
const lhs = stmt.lhs[0];
|
|
328
|
+
if (range?.kind !== "RangeExpr" || range._isIterator) return false;
|
|
329
|
+
if (!lhs || (lhs.kind === "Ident" && lhs.name === "_")) return false;
|
|
330
|
+
const target = lhsTypes[0];
|
|
331
|
+
if (!target || isAny(target) || typeStr(target) === "int") return false;
|
|
332
|
+
const coll = this.resolveType(range.expr._coreType ?? range.expr._type);
|
|
333
|
+
const cu =
|
|
334
|
+
coll?.kind === "named" ? this.resolveType(coll.underlying) : coll;
|
|
335
|
+
if (!isString(cu) && cu?.kind !== "slice" && cu?.kind !== "array")
|
|
336
|
+
return false;
|
|
337
|
+
const tu = this.resolveType(
|
|
338
|
+
target.kind === "named" ? target.underlying : target,
|
|
339
|
+
);
|
|
340
|
+
if (tu?.kind === "interface") return false;
|
|
341
|
+
this.err(`Cannot assign int to ${typeStr(target)}`, lhs);
|
|
342
|
+
return true;
|
|
343
|
+
},
|
|
344
|
+
|
|
253
345
|
_checkReturnStmt(stmt, scope, returnType) {
|
|
254
346
|
const types = stmt.values.map((v) => this.checkExpr(v, scope));
|
|
255
347
|
stmt._types = types;
|
|
@@ -274,14 +366,12 @@ export const statementCheckMethods = {
|
|
|
274
366
|
);
|
|
275
367
|
return;
|
|
276
368
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
);
|
|
284
|
-
}
|
|
369
|
+
this._checkForwardedValues(
|
|
370
|
+
expected,
|
|
371
|
+
forwarded,
|
|
372
|
+
stmt.values[0],
|
|
373
|
+
"return statement",
|
|
374
|
+
);
|
|
285
375
|
return;
|
|
286
376
|
}
|
|
287
377
|
for (let i = 0; i < expected.length; i++) {
|
|
@@ -296,6 +386,35 @@ export const statementCheckMethods = {
|
|
|
296
386
|
}
|
|
297
387
|
},
|
|
298
388
|
|
|
389
|
+
// Checks each result of a forwarded multi-value call (`return f()`,
|
|
390
|
+
// `g(f())`) against its target. Every element gets its own interface-box
|
|
391
|
+
// marks in `call._fwdIface` (null where nothing is boxed): tagging the
|
|
392
|
+
// shared call node would give every element the marks of the last one.
|
|
393
|
+
// Lowering splits such forwards into temps that carry these marks.
|
|
394
|
+
_checkForwardedValues(expected, forwarded, call, ctx) {
|
|
395
|
+
const skip = this._skipIfaceMark;
|
|
396
|
+
this._skipIfaceMark = true;
|
|
397
|
+
try {
|
|
398
|
+
for (let i = 0; i < expected.length; i++)
|
|
399
|
+
this.assertAssignable(expected[i], forwarded[i], call, ctx);
|
|
400
|
+
} finally {
|
|
401
|
+
this._skipIfaceMark = skip;
|
|
402
|
+
}
|
|
403
|
+
if (!skip) this._markForwardedElems(expected, forwarded, call);
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
// Records in `call._fwdIface` one interface-box mark holder per forwarded
|
|
407
|
+
// element (null where the element is not boxed), or clears it when no
|
|
408
|
+
// element enters an interface.
|
|
409
|
+
_markForwardedElems(expected, forwarded, call) {
|
|
410
|
+
const holders = expected.map((t, i) => {
|
|
411
|
+
const holder = {};
|
|
412
|
+
this._markIfaceBox(t, forwarded[i], holder);
|
|
413
|
+
return "_ifaceSrc" in holder ? holder : null;
|
|
414
|
+
});
|
|
415
|
+
call._fwdIface = holders.some(Boolean) ? holders : undefined;
|
|
416
|
+
},
|
|
417
|
+
|
|
299
418
|
_checkIfStmt(stmt, scope, returnType) {
|
|
300
419
|
const inner = new Scope(scope);
|
|
301
420
|
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
@@ -321,6 +440,8 @@ export const statementCheckMethods = {
|
|
|
321
440
|
this.err("For condition must be bool", stmt);
|
|
322
441
|
} else {
|
|
323
442
|
const ct = this.checkExpr(stmt.cond.expr, inner);
|
|
443
|
+
const core = ct?.kind === "typeParam" ? this._coreType(ct, inner) : null;
|
|
444
|
+
if (core) stmt.cond.expr._coreType = core;
|
|
324
445
|
const info = iteratorYieldParams(ct);
|
|
325
446
|
if (info) {
|
|
326
447
|
stmt.cond._isIterator = true;
|
|
@@ -359,10 +480,18 @@ export const statementCheckMethods = {
|
|
|
359
480
|
const inner = new Scope(scope);
|
|
360
481
|
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
361
482
|
if (stmt.tag) this.checkExpr(stmt.tag, inner);
|
|
483
|
+
const tagIs64 = stmt.tag && is64BitInt(stmt.tag._type);
|
|
362
484
|
this._switchDepth++;
|
|
363
485
|
for (const c of stmt.cases) {
|
|
364
486
|
const caseScope = new Scope(inner);
|
|
365
|
-
if (c.list)
|
|
487
|
+
if (c.list) {
|
|
488
|
+
for (const e of c.list) {
|
|
489
|
+
this.checkExpr(e, caseScope);
|
|
490
|
+
this._checkCaseValue(stmt.tag, e);
|
|
491
|
+
if (stmt.tag) this._tagFloat32Const(stmt.tag._type, e._type, e);
|
|
492
|
+
if (tagIs64 && e._type?.kind === "untyped") e._is64Bit = true;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
366
495
|
for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
|
|
367
496
|
this._reportUnused(caseScope, stmt);
|
|
368
497
|
}
|
|
@@ -370,6 +499,92 @@ export const statementCheckMethods = {
|
|
|
370
499
|
this._reportUnused(inner, stmt);
|
|
371
500
|
},
|
|
372
501
|
|
|
502
|
+
// `case e:` compares `tag == e`, so `e` must convert to the tag's type
|
|
503
|
+
// (or the other way round for typed values).
|
|
504
|
+
_checkCaseValue(tag, e) {
|
|
505
|
+
const caseType = this.resolveType(e._type);
|
|
506
|
+
if (!tag) {
|
|
507
|
+
this._checkTaglessCase(e, caseType);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
let tagType = this.resolveType(tag._type);
|
|
511
|
+
// `switch "foo" {` compares as the constant's default type.
|
|
512
|
+
if (isUntyped(tagType) && !isNil(tagType)) tagType = defaultType(tagType);
|
|
513
|
+
if (this._caseValueUnchecked(tagType, caseType)) return;
|
|
514
|
+
if (isNil(caseType)) {
|
|
515
|
+
this.err(`cannot convert nil to type ${typeStr(tagType)}`, e);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (isUntyped(caseType)) {
|
|
519
|
+
if (this._isUntypedAssignable(tagType, caseType)) return;
|
|
520
|
+
this.err(
|
|
521
|
+
`cannot convert ${exprText(e)} (${operandKind(e, caseType.base, true)}) to type ${typeStr(tagType)}`,
|
|
522
|
+
e,
|
|
523
|
+
);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (
|
|
527
|
+
this._assignableQuietly(tagType, caseType) ||
|
|
528
|
+
this._assignableQuietly(caseType, tagType)
|
|
529
|
+
)
|
|
530
|
+
return;
|
|
531
|
+
this.err(
|
|
532
|
+
`invalid case ${exprText(e)} in switch on ${exprText(tag)} (mismatched types ${typeStr(caseType)} and ${typeStr(tagType)})`,
|
|
533
|
+
e,
|
|
534
|
+
);
|
|
535
|
+
},
|
|
536
|
+
|
|
537
|
+
// A tagless `switch` is `switch true`, so every case must be boolean.
|
|
538
|
+
_checkTaglessCase(e, caseType) {
|
|
539
|
+
if (!caseType || caseType._tainted || caseType.kind === "typeParam") return;
|
|
540
|
+
if (isNil(caseType)) {
|
|
541
|
+
this.err(`cannot convert nil to type bool`, e);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (isAny(caseType) || isBool(caseType)) return;
|
|
545
|
+
if (isUntyped(caseType) && caseType.base === "bool") return;
|
|
546
|
+
const under = this.resolveType(caseType.underlying ?? caseType);
|
|
547
|
+
if (isBool(under)) return;
|
|
548
|
+
this.err(
|
|
549
|
+
`invalid case ${exprText(e)} in switch (mismatched types ${typeStr(caseType)} and bool)`,
|
|
550
|
+
e,
|
|
551
|
+
);
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
// Operands whose comparison the checker cannot (or need not) judge: unknown
|
|
555
|
+
// or recovered types, `any`/interface tags, nil cases, type parameters.
|
|
556
|
+
_caseValueUnchecked(tagType, caseType) {
|
|
557
|
+
if (!tagType || !caseType || tagType._tainted || caseType._tainted)
|
|
558
|
+
return true;
|
|
559
|
+
if (isAny(tagType) || isAny(caseType)) return true;
|
|
560
|
+
if (isUntyped(tagType) || tagType.kind === "typeParam") return true;
|
|
561
|
+
if (isNil(caseType)) return this._nilCaseOk(tagType);
|
|
562
|
+
return (
|
|
563
|
+
this.resolveType(tagType.underlying ?? tagType)?.kind === "interface"
|
|
564
|
+
);
|
|
565
|
+
},
|
|
566
|
+
|
|
567
|
+
// `case nil` needs a tag with a nil value (pointer, slice, map, func,
|
|
568
|
+
// chan, interface); a basic, struct or array tag makes the caller report
|
|
569
|
+
// `cannot convert nil`.
|
|
570
|
+
_nilCaseOk(tagType) {
|
|
571
|
+
const kind = this._deepUnderlying(tagType)?.kind;
|
|
572
|
+
return !NON_NILABLE_TAG_KINDS.has(kind);
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
// Whether assertAssignable(target, source) reports nothing.
|
|
576
|
+
_assignableQuietly(target, source) {
|
|
577
|
+
const saved = this.errors;
|
|
578
|
+
const savedSkip = this._skipIfaceMark;
|
|
579
|
+
this.errors = [];
|
|
580
|
+
this._skipIfaceMark = true;
|
|
581
|
+
this.assertAssignable(target, source, null);
|
|
582
|
+
const ok = this.errors.length === 0;
|
|
583
|
+
this.errors = saved;
|
|
584
|
+
this._skipIfaceMark = savedSkip;
|
|
585
|
+
return ok;
|
|
586
|
+
},
|
|
587
|
+
|
|
373
588
|
// A case naming an interface (literal or declared) records the resolved
|
|
374
589
|
// type so backends test its method set.
|
|
375
590
|
_markInterfaceCaseType(t, scope) {
|
|
@@ -383,6 +598,23 @@ export const statementCheckMethods = {
|
|
|
383
598
|
if (u?.kind === "interface") t._type = resolved;
|
|
384
599
|
},
|
|
385
600
|
|
|
601
|
+
// Records the resolved type of a composite type node used as an assertion
|
|
602
|
+
// or type-switch target (`[]int`, `map[string]any`, `*int`, `iter.Seq[int]`)
|
|
603
|
+
// so the backend can match it against the dynamic type of an interface value.
|
|
604
|
+
_markDynamicType(node, resolved) {
|
|
605
|
+
const kind = node?.kind;
|
|
606
|
+
if (
|
|
607
|
+
kind === "SliceType" ||
|
|
608
|
+
kind === "ArrayType" ||
|
|
609
|
+
kind === "MapType" ||
|
|
610
|
+
kind === "PointerType" ||
|
|
611
|
+
kind === "FuncType" ||
|
|
612
|
+
kind === "StructType" ||
|
|
613
|
+
kind === "GenericTypeName"
|
|
614
|
+
)
|
|
615
|
+
node._assertType = resolved;
|
|
616
|
+
},
|
|
617
|
+
|
|
386
618
|
_checkTypeSwitchStmt(stmt, scope, returnType) {
|
|
387
619
|
const inner = new Scope(scope);
|
|
388
620
|
this.checkExpr(stmt.expr, inner);
|
|
@@ -396,9 +628,12 @@ export const statementCheckMethods = {
|
|
|
396
628
|
const base = t?.kind === "PointerType" ? t.base : t;
|
|
397
629
|
if (base?.kind === "GenericTypeName") this.resolveTypeNode(t, inner);
|
|
398
630
|
this._markInterfaceCaseType(t, inner);
|
|
631
|
+
if (t && t.kind !== "TypeName" && t.kind !== "InterfaceType")
|
|
632
|
+
this._markDynamicType(t, this.resolveTypeNode(t, inner));
|
|
399
633
|
}
|
|
400
634
|
if (stmt.assign) {
|
|
401
|
-
// Bind the variable to the single case type, or
|
|
635
|
+
// Bind the variable to the single case type, or the switch operand's type
|
|
636
|
+
// for multi-type and default clauses, as Go does.
|
|
402
637
|
// `case nil` binds the switch expression's own type, as in Go.
|
|
403
638
|
const only = c.types?.length === 1 ? c.types[0] : null;
|
|
404
639
|
const isNilCase = only?.kind === "TypeName" && only.name === "nil";
|
|
@@ -406,7 +641,7 @@ export const statementCheckMethods = {
|
|
|
406
641
|
? (stmt.expr._type ?? ANY)
|
|
407
642
|
: only
|
|
408
643
|
? this.resolveTypeNode(only, inner)
|
|
409
|
-
: ANY;
|
|
644
|
+
: (stmt.expr._type ?? ANY);
|
|
410
645
|
caseScope.defineLocal(stmt.assign, bindType);
|
|
411
646
|
// The capture variable is always considered used — it's the whole
|
|
412
647
|
// point of the switch-with-assign form. Each case body uses its own
|
|
@@ -453,9 +688,35 @@ export const statementCheckMethods = {
|
|
|
453
688
|
}
|
|
454
689
|
},
|
|
455
690
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
691
|
+
// Each yielded value is stored into an existing variable; one of
|
|
692
|
+
// interface type boxes the value as any other conversion does.
|
|
693
|
+
_checkRangeIterAssignVars(lhs, scope, yieldParams = []) {
|
|
694
|
+
lhs.forEach((e, i) => {
|
|
695
|
+
if (e.kind === "Ident" && e.name === "_") return;
|
|
696
|
+
const target = this.checkExpr(e, scope);
|
|
697
|
+
this._markRangeTargetBox(e, target, yieldParams[i]);
|
|
698
|
+
this._checkRangeIterAssignable(e, target, yieldParams[i]);
|
|
699
|
+
});
|
|
700
|
+
},
|
|
701
|
+
|
|
702
|
+
// The yielded value is assigned to the target, so it must be assignable
|
|
703
|
+
// to it (`for s = range seq` with `s string` and `seq` yielding `int`).
|
|
704
|
+
// The lenient `float64 = int` pair of plain assignments is reported here:
|
|
705
|
+
// the iterator loop stores the yielded value without converting it.
|
|
706
|
+
_checkRangeIterAssignable(node, target, source) {
|
|
707
|
+
if (!target || !source) return;
|
|
708
|
+
const t = this.resolveType(target);
|
|
709
|
+
const v = this.resolveType(source);
|
|
710
|
+
if (this._isNumericCoercible(t, v)) {
|
|
711
|
+
this.err(`Cannot assign ${typeStr(v)} to ${typeStr(t)}`, node);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (!this._needsAssignCheck(target, source)) return;
|
|
715
|
+
this._skipIfaceMark = true;
|
|
716
|
+
try {
|
|
717
|
+
this.assertAssignable(target, source, node, "assignment");
|
|
718
|
+
} finally {
|
|
719
|
+
this._skipIfaceMark = false;
|
|
459
720
|
}
|
|
460
721
|
},
|
|
461
722
|
|
|
@@ -481,7 +742,7 @@ export const statementCheckMethods = {
|
|
|
481
742
|
|
|
482
743
|
if (initStmt.kind === "DefineStmt")
|
|
483
744
|
this._bindRangeIterDefineVars(lhs, info, scope);
|
|
484
|
-
else this._checkRangeIterAssignVars(lhs, scope);
|
|
745
|
+
else this._checkRangeIterAssignVars(lhs, scope, info.yieldParams);
|
|
485
746
|
return info;
|
|
486
747
|
},
|
|
487
748
|
};
|
|
@@ -155,7 +155,7 @@ const TRIMMED_UNICODE_TABLES =
|
|
|
155
155
|
const TRIMMED_UNICODE_CASE =
|
|
156
156
|
"removed in v1.8.0; case ranges are not supported, use ToUpper, ToLower or ToTitle";
|
|
157
157
|
const TRIMMED_FSCAN =
|
|
158
|
-
"removed in v1.8.0; scanning from an io.Reader is not supported,
|
|
158
|
+
"removed in v1.8.0; scanning from an io.Reader is not supported, parse the text with strings and strconv";
|
|
159
159
|
|
|
160
160
|
// reason -> package path -> excluded member names
|
|
161
161
|
const EXCLUDED_MEMBER_GROUPS = {
|
|
@@ -188,6 +188,14 @@ const EXCLUDED_MEMBER_GROUPS = {
|
|
|
188
188
|
[TRIMMED_FSCAN]: {
|
|
189
189
|
fmt: ["Fscan", "Fscanf", "Fscanln"],
|
|
190
190
|
},
|
|
191
|
+
"removed in v1.9.0; reflection-based binary encoding is not supported, use binary.LittleEndian or binary.BigEndian methods (Uint32, PutUint32, AppendUint32, ...)":
|
|
192
|
+
{
|
|
193
|
+
"encoding/binary": ["Read", "Write", "Size"],
|
|
194
|
+
},
|
|
195
|
+
"removed in v1.9.0; format-string scanning is not supported, use strings.Fields with strconv.Atoi, ParseFloat or ParseBool":
|
|
196
|
+
{
|
|
197
|
+
fmt: ["Sscan", "Sscanf", "Sscanln"],
|
|
198
|
+
},
|
|
191
199
|
"removed in v1.8.0; this function is not supported": {
|
|
192
200
|
math: ["Gamma", "Erf"],
|
|
193
201
|
},
|
|
@@ -252,7 +260,6 @@ const EXCLUDED_MEMBER_GROUPS = {
|
|
|
252
260
|
"File.Chdir",
|
|
253
261
|
"File.Chmod",
|
|
254
262
|
"File.Chown",
|
|
255
|
-
"File.Close",
|
|
256
263
|
"File.Fd",
|
|
257
264
|
"File.Name",
|
|
258
265
|
"File.ReadAt",
|
|
@@ -393,7 +400,6 @@ const EXCLUDED_MEMBER_GROUPS = {
|
|
|
393
400
|
"SEEK_CUR",
|
|
394
401
|
"SEEK_END",
|
|
395
402
|
"SEEK_SET",
|
|
396
|
-
"Setenv",
|
|
397
403
|
"Signal",
|
|
398
404
|
"Signal.Signal",
|
|
399
405
|
"Signal.String",
|
|
@@ -809,8 +815,6 @@ const EXCLUDED_MEMBER_GROUPS = {
|
|
|
809
815
|
const REMOVED_PACKAGES = {
|
|
810
816
|
bufio: "removed in v1.8.0; use strings and bytes readers",
|
|
811
817
|
"net/url": "removed in v1.8.0; use the URL class from a js package",
|
|
812
|
-
context:
|
|
813
|
-
"removed in v1.8.0; use a cancel flag, or AbortController from a js package",
|
|
814
818
|
"encoding/csv":
|
|
815
819
|
"removed in v1.8.0; split lines and fields with strings.Split",
|
|
816
820
|
"container/heap":
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
// stdlib.js — built-in namespace and browser-global registration for the GoFront type
|
|
2
2
|
// checker. Called once from TypeChecker._setupGlobals() during construction.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// math, errors, time, unicode, os, slices, html, io
|
|
7
|
-
// stdlib/extended.js — gom, maps, regexp, rand, utf8, path, builder types,
|
|
8
|
-
// built-in functions
|
|
4
|
+
// Registers browser globals and built-in functions, then delegates to the
|
|
5
|
+
// shared, simd and web sub-modules.
|
|
9
6
|
|
|
10
|
-
import {
|
|
11
|
-
import { setupExtendedGlobals } from "./extended.js";
|
|
7
|
+
import { ANY, BROWSER_GLOBALS } from "../types.js";
|
|
12
8
|
import { setupSharedGlobals } from "./shared.js";
|
|
13
9
|
import { setupSimdGlobals } from "./simd.js";
|
|
14
10
|
import { setupWebGlobals } from "./web.js";
|
|
15
11
|
|
|
16
12
|
export function setupGlobals(globals, types) {
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
for (const g of BROWSER_GLOBALS) globals.define(g, ANY);
|
|
14
|
+
for (const name of [
|
|
15
|
+
"append",
|
|
16
|
+
"len",
|
|
17
|
+
"cap",
|
|
18
|
+
"make",
|
|
19
|
+
"delete",
|
|
20
|
+
"print",
|
|
21
|
+
"println",
|
|
22
|
+
"panic",
|
|
23
|
+
"recover",
|
|
24
|
+
"new",
|
|
25
|
+
"copy",
|
|
26
|
+
"error",
|
|
27
|
+
"min",
|
|
28
|
+
"max",
|
|
29
|
+
"clear",
|
|
30
|
+
"complex",
|
|
31
|
+
"real",
|
|
32
|
+
"imag",
|
|
33
|
+
])
|
|
34
|
+
globals.define(name, { kind: "builtin", name });
|
|
19
35
|
setupSharedGlobals(globals, types);
|
|
20
36
|
setupSimdGlobals(globals, types);
|
|
21
37
|
setupWebGlobals(globals, types);
|
|
@@ -64,15 +64,24 @@ export const terminationMethods = {
|
|
|
64
64
|
|
|
65
65
|
_isTerminatingSwitch(stmt) {
|
|
66
66
|
let hasDefault = false;
|
|
67
|
-
|
|
67
|
+
const cases = stmt.cases ?? [];
|
|
68
|
+
for (let i = 0; i < cases.length; i++) {
|
|
69
|
+
const c = cases[i];
|
|
68
70
|
if (!c.list) hasDefault = true;
|
|
69
71
|
if (!c.stmts?.length) return false;
|
|
70
72
|
const last = c.stmts[c.stmts.length - 1];
|
|
73
|
+
// `fallthrough` hands control to the next clause, which is checked
|
|
74
|
+
// on its own; the final clause has none to fall into.
|
|
75
|
+
if (this._isFallthrough(last) && i < cases.length - 1) continue;
|
|
71
76
|
if (!this._isTerminating(last)) return false;
|
|
72
77
|
}
|
|
73
78
|
return hasDefault;
|
|
74
79
|
},
|
|
75
80
|
|
|
81
|
+
_isFallthrough(stmt) {
|
|
82
|
+
return stmt?.kind === "BranchStmt" && stmt.keyword === "fallthrough";
|
|
83
|
+
},
|
|
84
|
+
|
|
76
85
|
_isTerminatingTypeSwitch(stmt) {
|
|
77
86
|
let hasDefault = false;
|
|
78
87
|
for (const c of stmt.cases ?? []) {
|