gofront 1.5.1 → 1.6.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 +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +288 -128
- package/src/backend/js/index.js +138 -56
- package/src/backend/js/runtime.js +1055 -53
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +151 -83
- package/src/backend/js/stdlib/base64.js +12 -0
- package/src/backend/js/stdlib/binary.js +102 -0
- package/src/backend/js/stdlib/bits.js +15 -1
- package/src/backend/js/stdlib/builder.js +28 -9
- package/src/backend/js/stdlib/bytes.js +75 -18
- package/src/backend/js/stdlib/errors.js +22 -3
- package/src/backend/js/stdlib/fmt.js +95 -20
- package/src/backend/js/stdlib/hex.js +17 -0
- package/src/backend/js/stdlib/html.js +1 -1
- package/src/backend/js/stdlib/index.js +19 -0
- package/src/backend/js/stdlib/io.js +105 -3
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/math.js +13 -1
- package/src/backend/js/stdlib/path.js +20 -6
- package/src/backend/js/stdlib/rand.js +10 -5
- package/src/backend/js/stdlib/sha256.js +12 -0
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +53 -27
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +52 -11
- package/src/backend/js/stdlib/strings.js +152 -16
- package/src/backend/js/stdlib/sync.js +13 -0
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +93 -22
- package/src/backend/js/stdlib/unicode.js +2 -2
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +1677 -58
- package/src/backend/wasm/const-fold.js +205 -0
- package/src/backend/wasm/emit-builtins.js +1230 -0
- package/src/backend/wasm/emit-exprs.js +2096 -0
- package/src/backend/wasm/emit-maps.js +1999 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +13 -0
- package/src/backend/wasm/emit-stmts.js +2466 -0
- package/src/backend/wasm/emit.js +656 -5031
- package/src/backend/wasm/encode.js +259 -85
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +2432 -86
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +236 -0
- package/src/backend/wasm/stdlib/bytes.js +5039 -0
- package/src/backend/wasm/stdlib/core.js +8903 -0
- package/src/backend/wasm/stdlib/index.js +40 -0
- package/src/backend/wasm/stdlib/io.js +2161 -0
- package/src/backend/wasm/stdlib/packages.js +2400 -0
- package/src/backend/wasm/stdlib/pure.js +720 -0
- package/src/backend/wasm/stdlib/time.js +1200 -0
- package/src/backend/wasm/types.js +312 -36
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +276 -73
- package/src/compiler.js +162 -25
- package/src/dts-parser.js +79 -126
- package/src/index.js +88 -121
- package/src/lexer.js +80 -29
- package/src/lower/boxing.js +4 -6
- package/src/lower/captures.js +12 -23
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +35 -19
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +20 -68
- package/src/parser/declarations.js +3 -6
- package/src/parser/expressions.js +3 -8
- package/src/parser/index.js +18 -3
- package/src/parser/statements.js +25 -38
- package/src/parser/types.js +1 -2
- package/src/resolver.js +3 -27
- package/src/templ-lexer.js +29 -63
- package/src/templ-parser.js +22 -45
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +123 -14
- package/src/typechecker/expressions.js +244 -4
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +71 -12
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +292 -103
- package/src/typechecker/stdlib/extended.js +471 -4
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +545 -8
- package/src/vendor.js +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Assignability, binary result types, and interface satisfaction.
|
|
2
2
|
// Installed as a mixin on TypeChecker.prototype.
|
|
3
3
|
|
|
4
|
+
import { sharedInfo } from "./stdlib/shared.js";
|
|
4
5
|
import {
|
|
5
6
|
ANY,
|
|
6
7
|
BOOL,
|
|
@@ -159,6 +160,7 @@ export const assignabilityMethods = {
|
|
|
159
160
|
target = this.resolveType(target);
|
|
160
161
|
source = this.resolveType(source);
|
|
161
162
|
this._markIfaceBox(target, source, node);
|
|
163
|
+
this._checkWasmIfaceProvider(target, source, node);
|
|
162
164
|
if (this._assertAssignableEarlyReturn(target, source)) return;
|
|
163
165
|
if (isPointer(target) && isPointer(source)) {
|
|
164
166
|
this.assertAssignable(
|
|
@@ -180,24 +182,99 @@ export const assignabilityMethods = {
|
|
|
180
182
|
// Tags struct values / struct pointers flowing into an interface so codegen can
|
|
181
183
|
// tell `T` from `*T` at runtime (both compile to the same class instance).
|
|
182
184
|
_markIfaceBox(target, source, node) {
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
+
if (
|
|
186
|
+
this._skipIfaceMark ||
|
|
187
|
+
!node ||
|
|
188
|
+
typeof node !== "object" ||
|
|
189
|
+
!target ||
|
|
190
|
+
!source
|
|
191
|
+
)
|
|
192
|
+
return;
|
|
185
193
|
if (target.kind === "typeParam") return;
|
|
186
194
|
const tBase = this.resolveType(
|
|
187
195
|
target.kind === "named" ? target.underlying : target,
|
|
188
196
|
);
|
|
189
197
|
if (!isAny(target) && tBase?.kind !== "interface") return;
|
|
190
|
-
const structOf = (t) => {
|
|
191
|
-
const r = this.resolveType(t);
|
|
192
|
-
const b = r?.kind === "named" ? this.resolveType(r.underlying) : r;
|
|
193
|
-
return b?.kind === "struct";
|
|
194
|
-
};
|
|
195
198
|
const sBase = this.resolveType(
|
|
196
199
|
source.kind === "named" ? source.underlying : source,
|
|
197
200
|
);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
const info = this._classifyIfaceBox(sBase);
|
|
202
|
+
if (!info) return;
|
|
203
|
+
node._ifaceBox = info.box;
|
|
204
|
+
if (info.ptrFields) node._ifacePtrFields = info.ptrFields;
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
_getStructPtrFields(st, prefix = "", seen = new Set()) {
|
|
208
|
+
if (st?.kind !== "struct" || !st?.fields) return [];
|
|
209
|
+
if (seen.has(st)) return [];
|
|
210
|
+
const nextSeen = new Set(seen);
|
|
211
|
+
nextSeen.add(st);
|
|
212
|
+
const ptrs = [];
|
|
213
|
+
for (const [fname, ftype] of st.fields.entries()) {
|
|
214
|
+
const full = prefix ? `${prefix}.${fname}` : fname;
|
|
215
|
+
const rf = this.resolveType(ftype);
|
|
216
|
+
const bf = rf?.kind === "named" ? this.resolveType(rf.underlying) : rf;
|
|
217
|
+
if (isPointer(bf)) {
|
|
218
|
+
ptrs.push(full);
|
|
219
|
+
} else if (bf?.kind === "struct") {
|
|
220
|
+
ptrs.push(...this._getStructPtrFields(bf, full, nextSeen));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return ptrs;
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
_classifyIfaceBox(sBase) {
|
|
227
|
+
if (sBase?.kind === "struct") {
|
|
228
|
+
const pf = this._getStructPtrFields(sBase);
|
|
229
|
+
return { box: "value", ptrFields: pf.length > 0 ? pf : null };
|
|
230
|
+
}
|
|
231
|
+
if (sBase?.kind === "pointer") {
|
|
232
|
+
const st = this.resolveType(
|
|
233
|
+
sBase.base?.kind === "named" ? sBase.base.underlying : sBase.base,
|
|
234
|
+
);
|
|
235
|
+
if (st?.kind === "struct") {
|
|
236
|
+
const pf = this._getStructPtrFields(st);
|
|
237
|
+
return { box: "ptr", ptrFields: pf.length > 0 ? pf : null };
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
if (sBase?.kind === "map") {
|
|
242
|
+
const valType = this.resolveType(sBase.value ?? sBase.val ?? sBase.elem);
|
|
243
|
+
const keyType = this.resolveType(sBase.key);
|
|
244
|
+
return {
|
|
245
|
+
box: isPointer(valType) || isPointer(keyType) ? "ptr_map" : "map",
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (sBase?.kind === "slice") {
|
|
249
|
+
const elemType = this.resolveType(sBase.elem);
|
|
250
|
+
if (isPointer(elemType)) return { box: "ptr_slice" };
|
|
251
|
+
const elemBase =
|
|
252
|
+
elemType?.kind === "named"
|
|
253
|
+
? this.resolveType(elemType.underlying)
|
|
254
|
+
: elemType;
|
|
255
|
+
const pf =
|
|
256
|
+
elemBase?.kind === "struct" ? this._getStructPtrFields(elemBase) : [];
|
|
257
|
+
return pf.length > 0 ? { box: "slice", ptrFields: pf } : null;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
// A js package cannot provide an implementation of a wasm interface: the
|
|
263
|
+
// value would cross into wasm, and JS-implemented proxies are not supported.
|
|
264
|
+
_checkWasmIfaceProvider(target, source, node) {
|
|
265
|
+
if (this.target !== "js" || target?.kind !== "named") return;
|
|
266
|
+
if (target._target !== "wasm" || !source) return;
|
|
267
|
+
const iface = this.resolveType(target.underlying);
|
|
268
|
+
if (iface?.kind !== "interface" || !(iface.methods?.size > 0)) return;
|
|
269
|
+
const ptr = source.kind === "pointer";
|
|
270
|
+
const src = ptr ? this.resolveType(source.base) : source;
|
|
271
|
+
if (src?.kind !== "named" || src._target === "wasm") return;
|
|
272
|
+
if (this.resolveType(src.underlying)?.kind === "interface") return;
|
|
273
|
+
if (!this.implements(source, iface, node)) return;
|
|
274
|
+
this.err(
|
|
275
|
+
`type '${ptr ? "*" : ""}${src.name}' (${src._target ?? "js"}) cannot implement wasm interface '${target.name}' across the boundary; implement it in a wasm package or pass a func callback`,
|
|
276
|
+
node,
|
|
277
|
+
);
|
|
201
278
|
},
|
|
202
279
|
|
|
203
280
|
_assertAssignableEarlyReturn(target, source) {
|
|
@@ -222,6 +299,23 @@ export const assignabilityMethods = {
|
|
|
222
299
|
);
|
|
223
300
|
return;
|
|
224
301
|
}
|
|
302
|
+
const sBase = this.resolveType(
|
|
303
|
+
source.kind === "named" ? source.underlying : source,
|
|
304
|
+
);
|
|
305
|
+
if (sharedInfo(source) && tBase?.kind === "slice") {
|
|
306
|
+
this.err(
|
|
307
|
+
`cannot use ${typeStr(source)} as ${typeStr(target)}: a linear-memory buffer cannot be passed as a GC slice without an explicit copy`,
|
|
308
|
+
node,
|
|
309
|
+
);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (tBase?.kind === "shared" && sBase?.kind === "slice") {
|
|
313
|
+
this.err(
|
|
314
|
+
`cannot use ${typeStr(source)} as ${typeStr(target)}: a GC slice cannot be used as a shared buffer; copy its elements into one allocated with shared.New${tBase.shared}`,
|
|
315
|
+
node,
|
|
316
|
+
);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
225
319
|
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
226
320
|
},
|
|
227
321
|
|
|
@@ -334,18 +428,30 @@ export const assignabilityMethods = {
|
|
|
334
428
|
return true;
|
|
335
429
|
},
|
|
336
430
|
|
|
431
|
+
_unpackReturns(rList) {
|
|
432
|
+
if (
|
|
433
|
+
rList.length === 1 &&
|
|
434
|
+
(rList[0]?.kind === "tuple" || rList[0]?.kind === "TupleType") &&
|
|
435
|
+
rList[0].types
|
|
436
|
+
) {
|
|
437
|
+
return rList[0].types;
|
|
438
|
+
}
|
|
439
|
+
return rList;
|
|
440
|
+
},
|
|
441
|
+
|
|
337
442
|
_implementsMethod(required, actual) {
|
|
338
443
|
if (!actual) return false;
|
|
339
444
|
const rp = required.params ?? [],
|
|
340
445
|
ap = actual.params ?? [];
|
|
341
|
-
const rr = required.returns ?? [],
|
|
342
|
-
ar = actual.returns ?? [];
|
|
446
|
+
const rr = this._unpackReturns(required.returns ?? []),
|
|
447
|
+
ar = this._unpackReturns(actual.returns ?? []);
|
|
343
448
|
if (!this._sigParamsMatch(rp, ap)) return false;
|
|
344
449
|
if (!!required.variadic !== !!actual.variadic) return false;
|
|
345
450
|
return this._sigParamsMatch(rr, ar);
|
|
346
451
|
},
|
|
347
452
|
|
|
348
453
|
implements(srcType, iface, _node) {
|
|
454
|
+
const isPtr = srcType?.kind === "pointer";
|
|
349
455
|
// *T has the full method set of T (value + pointer receivers).
|
|
350
456
|
if (srcType?.kind === "pointer") srcType = this.resolveType(srcType.base);
|
|
351
457
|
let base = srcType.kind === "named" ? srcType.underlying : srcType;
|
|
@@ -360,13 +466,16 @@ export const assignabilityMethods = {
|
|
|
360
466
|
}
|
|
361
467
|
const methodMap =
|
|
362
468
|
base?.kind === "struct"
|
|
363
|
-
? base.methods
|
|
469
|
+
? base.methods || srcType.methods
|
|
364
470
|
: srcType.kind === "named"
|
|
365
471
|
? srcType.methods
|
|
366
472
|
: null;
|
|
367
473
|
if (!methodMap) return false;
|
|
368
474
|
for (const [name, required] of iface.methods) {
|
|
369
|
-
|
|
475
|
+
const actual = methodMap.get(name);
|
|
476
|
+
if (!actual) return false;
|
|
477
|
+
if (!isPtr && actual._ptrRecv) return false;
|
|
478
|
+
if (!this._implementsMethod(required, actual)) return false;
|
|
370
479
|
}
|
|
371
480
|
return true;
|
|
372
481
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// TypeChecker expression-checking methods — installed as a mixin on TypeChecker.prototype.
|
|
2
2
|
|
|
3
|
+
import { sharedInfo } from "./stdlib/shared.js";
|
|
3
4
|
import {
|
|
4
5
|
ANY,
|
|
5
6
|
BOOL,
|
|
@@ -42,6 +43,13 @@ const BUILTIN_CHECK = {
|
|
|
42
43
|
s.err(`cannot append to array (type ${typeStr(st)})`, e);
|
|
43
44
|
return st;
|
|
44
45
|
}
|
|
46
|
+
if (sharedInfo(st)) {
|
|
47
|
+
s.err(
|
|
48
|
+
`cannot append to ${typeStr(st)}: shared buffers have a fixed size`,
|
|
49
|
+
e,
|
|
50
|
+
);
|
|
51
|
+
return st;
|
|
52
|
+
}
|
|
45
53
|
const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
|
|
46
54
|
if (sb?.kind === "slice") {
|
|
47
55
|
for (let i = 1; i < e.args.length; i++) {
|
|
@@ -55,7 +63,7 @@ const BUILTIN_CHECK = {
|
|
|
55
63
|
}
|
|
56
64
|
return st;
|
|
57
65
|
},
|
|
58
|
-
copy: () =>
|
|
66
|
+
copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
|
|
59
67
|
delete: () => VOID,
|
|
60
68
|
make: (s, _n, e, _at, sc) => {
|
|
61
69
|
if (e.args.length < 1) return ANY;
|
|
@@ -258,6 +266,16 @@ export const expressionCheckMethods = {
|
|
|
258
266
|
if (this._isStructOrNamedMethod(baseType, base, expr.field))
|
|
259
267
|
expr._isMethodValue = true;
|
|
260
268
|
}
|
|
269
|
+
if (
|
|
270
|
+
baseType?.kind === "namespace" &&
|
|
271
|
+
this.types.has(`${baseType.name}.${expr.field}`) &&
|
|
272
|
+
!(
|
|
273
|
+
baseType.name === "binary" &&
|
|
274
|
+
(expr.field === "LittleEndian" || expr.field === "BigEndian")
|
|
275
|
+
)
|
|
276
|
+
) {
|
|
277
|
+
expr._isTypeRef = true;
|
|
278
|
+
}
|
|
261
279
|
return ft;
|
|
262
280
|
},
|
|
263
281
|
|
|
@@ -291,10 +309,42 @@ export const expressionCheckMethods = {
|
|
|
291
309
|
expr._mapValueType = btu.value;
|
|
292
310
|
return btu.value;
|
|
293
311
|
}
|
|
312
|
+
if (btu.kind === "shared") {
|
|
313
|
+
expr._sharedElem = btu.elem;
|
|
314
|
+
const idx = this._constIntValue(expr.index);
|
|
315
|
+
if (idx !== null && idx < 0)
|
|
316
|
+
this.err(`invalid index ${idx} (index must not be negative)`, expr);
|
|
317
|
+
return btu.elem;
|
|
318
|
+
}
|
|
294
319
|
if (isString(bt)) return INT;
|
|
295
320
|
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
296
321
|
},
|
|
297
322
|
|
|
323
|
+
// copy(dst, src): element types must be identical when either side is a
|
|
324
|
+
// shared buffer (the wasm emitter uses one element type for both sides).
|
|
325
|
+
_checkBuiltinCopy(e, at) {
|
|
326
|
+
const elemOf = (t) => {
|
|
327
|
+
const u = this.resolveType(t?.kind === "named" ? t.underlying : t);
|
|
328
|
+
return u?.kind === "slice" || u?.kind === "shared" ? u.elem : null;
|
|
329
|
+
};
|
|
330
|
+
const [dt, st] = at;
|
|
331
|
+
if (!sharedInfo(dt) && !sharedInfo(st)) return INT;
|
|
332
|
+
const de = elemOf(dt);
|
|
333
|
+
const se = elemOf(st);
|
|
334
|
+
if (!de || !se) {
|
|
335
|
+
this.err(
|
|
336
|
+
`copy: cannot copy between ${typeStr(dt)} and ${typeStr(st)}`,
|
|
337
|
+
e,
|
|
338
|
+
);
|
|
339
|
+
} else if (typeStr(de) !== typeStr(se)) {
|
|
340
|
+
this.err(
|
|
341
|
+
`copy: element types differ (${typeStr(dt)} vs ${typeStr(st)})`,
|
|
342
|
+
e,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return INT;
|
|
346
|
+
},
|
|
347
|
+
|
|
298
348
|
_checkSliceExpr(expr, scope) {
|
|
299
349
|
const bt = this.checkExpr(expr.expr, scope);
|
|
300
350
|
if (expr.low) this.checkExpr(expr.low, scope);
|
|
@@ -305,10 +355,25 @@ export const expressionCheckMethods = {
|
|
|
305
355
|
if (bt.kind === "slice" || bt.kind === "array")
|
|
306
356
|
return { kind: "slice", elem: bt.elem };
|
|
307
357
|
if (isString(bt)) return STRING;
|
|
358
|
+
if (sharedInfo(bt))
|
|
359
|
+
return this.err(
|
|
360
|
+
`Cannot slice type ${typeStr(bt)}: use .Subarray(lo, hi) to create a view of a shared buffer`,
|
|
361
|
+
expr,
|
|
362
|
+
);
|
|
308
363
|
return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
|
|
309
364
|
},
|
|
310
365
|
|
|
311
366
|
_checkFuncLit(expr, scope) {
|
|
367
|
+
const savedSharedAlloc = this._sharedAllocOk;
|
|
368
|
+
this._sharedAllocOk = false;
|
|
369
|
+
try {
|
|
370
|
+
return this._checkFuncLitBody(expr, scope);
|
|
371
|
+
} finally {
|
|
372
|
+
this._sharedAllocOk = savedSharedAlloc;
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
_checkFuncLitBody(expr, scope) {
|
|
312
377
|
if (
|
|
313
378
|
(this.target === "wasm" || this.target === "both") &&
|
|
314
379
|
(expr.async || expr.isAsync)
|
|
@@ -409,6 +474,81 @@ export const expressionCheckMethods = {
|
|
|
409
474
|
return targetType;
|
|
410
475
|
},
|
|
411
476
|
|
|
477
|
+
_checkConversionValidity(srcType, targetType, expr) {
|
|
478
|
+
if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
|
|
479
|
+
const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
|
|
480
|
+
const tgtResolved =
|
|
481
|
+
targetType.kind === "named" ? targetType.underlying : targetType;
|
|
482
|
+
|
|
483
|
+
if (isNumeric(targetType) && isComplex(srcType)) {
|
|
484
|
+
this.err(
|
|
485
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
|
|
486
|
+
expr,
|
|
487
|
+
);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (isComplex(targetType) && !isComplexOrNumeric(srcType)) {
|
|
491
|
+
this.err(
|
|
492
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
493
|
+
expr,
|
|
494
|
+
);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
498
|
+
this._checkElemTypeCompatible(
|
|
499
|
+
tgtResolved.elem,
|
|
500
|
+
srcResolved.elem,
|
|
501
|
+
srcType,
|
|
502
|
+
targetType,
|
|
503
|
+
expr,
|
|
504
|
+
);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (this._isInvalidArrayConv(tgtResolved, srcResolved, srcType)) {
|
|
508
|
+
this.err(
|
|
509
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
510
|
+
expr,
|
|
511
|
+
);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
|
|
515
|
+
if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
|
|
516
|
+
if (
|
|
517
|
+
isString(srcResolved) &&
|
|
518
|
+
tgtResolved?.kind === "slice" &&
|
|
519
|
+
(tgtResolved.elem?.name === "byte" ||
|
|
520
|
+
tgtResolved.elem?.name === "rune" ||
|
|
521
|
+
tgtResolved.elem?.name === "uint8" ||
|
|
522
|
+
tgtResolved.elem?.name === "int32")
|
|
523
|
+
)
|
|
524
|
+
return;
|
|
525
|
+
if (
|
|
526
|
+
isString(tgtResolved) &&
|
|
527
|
+
(isNumeric(srcResolved) ||
|
|
528
|
+
(srcResolved?.kind === "slice" &&
|
|
529
|
+
(srcResolved.elem?.name === "byte" ||
|
|
530
|
+
srcResolved.elem?.name === "rune" ||
|
|
531
|
+
srcResolved.elem?.name === "uint8" ||
|
|
532
|
+
srcResolved.elem?.name === "int32")))
|
|
533
|
+
)
|
|
534
|
+
return;
|
|
535
|
+
if (srcResolved?.kind === "pointer" && tgtResolved?.kind === "pointer") {
|
|
536
|
+
const sBase =
|
|
537
|
+
srcResolved.base?.kind === "named"
|
|
538
|
+
? srcResolved.base.underlying
|
|
539
|
+
: srcResolved.base;
|
|
540
|
+
const tBase =
|
|
541
|
+
tgtResolved.base?.kind === "named"
|
|
542
|
+
? tgtResolved.base.underlying
|
|
543
|
+
: tgtResolved.base;
|
|
544
|
+
if (typeStr(sBase) === typeStr(tBase)) return;
|
|
545
|
+
}
|
|
546
|
+
this.err(
|
|
547
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
548
|
+
expr,
|
|
549
|
+
);
|
|
550
|
+
},
|
|
551
|
+
|
|
412
552
|
_checkTypeAssertExpr(expr, scope) {
|
|
413
553
|
const srcType = this.checkExpr(expr.expr, scope);
|
|
414
554
|
const targetType = this.resolveTypeNode(expr.type, scope);
|
|
@@ -431,7 +571,11 @@ export const expressionCheckMethods = {
|
|
|
431
571
|
|
|
432
572
|
_rangeCollTypeTuple(resolved, collType) {
|
|
433
573
|
if (isString(resolved)) return { kind: "tuple", types: [INT, RUNE] };
|
|
434
|
-
if (
|
|
574
|
+
if (
|
|
575
|
+
resolved?.kind === "slice" ||
|
|
576
|
+
resolved?.kind === "array" ||
|
|
577
|
+
resolved?.kind === "shared"
|
|
578
|
+
)
|
|
435
579
|
return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
|
|
436
580
|
if (resolved?.kind === "map")
|
|
437
581
|
return {
|
|
@@ -467,6 +611,9 @@ export const expressionCheckMethods = {
|
|
|
467
611
|
: null;
|
|
468
612
|
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
469
613
|
}
|
|
614
|
+
expr._typeArgs = typeArgs;
|
|
615
|
+
if (expr.expr) expr.expr._typeArgs = typeArgs;
|
|
616
|
+
expr._genericFnType = baseType;
|
|
470
617
|
return this.instantiateGenericFunc(baseType, typeArgs);
|
|
471
618
|
}
|
|
472
619
|
// The parser's type-arg heuristic mistakes `xs[d.Field]` (uppercase field
|
|
@@ -546,6 +693,8 @@ export const expressionCheckMethods = {
|
|
|
546
693
|
|
|
547
694
|
checkCall(expr, scope) {
|
|
548
695
|
let fnType = this.checkExpr(expr.func, scope);
|
|
696
|
+
this._checkSharedAllocSite(expr, scope);
|
|
697
|
+
this._checkBoundaryCallInLoop(expr, scope);
|
|
549
698
|
if (fnType.kind === "builtin") {
|
|
550
699
|
const argTypes = expr.args.map((a, i) => {
|
|
551
700
|
// new(T) — first arg is a type name, not a value expression
|
|
@@ -565,6 +714,14 @@ export const expressionCheckMethods = {
|
|
|
565
714
|
if (fnType.kind === "named" && expr.func._isTypeRef) {
|
|
566
715
|
expr._isTypeConversion = true;
|
|
567
716
|
expr._conversionTargetType = fnType;
|
|
717
|
+
if (expr.args.length !== 1) {
|
|
718
|
+
return this.err(
|
|
719
|
+
`cannot convert ${expr.args.length} arguments to type ${typeStr(fnType)}`,
|
|
720
|
+
expr,
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
const argType = argTypes[0];
|
|
724
|
+
this._checkConversionValidity(argType, fnType, expr);
|
|
568
725
|
return fnType;
|
|
569
726
|
}
|
|
570
727
|
|
|
@@ -576,12 +733,81 @@ export const expressionCheckMethods = {
|
|
|
576
733
|
|
|
577
734
|
this._checkCallArgs(fnType, argTypes, expr);
|
|
578
735
|
|
|
736
|
+
// Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
|
|
737
|
+
// how their result derives from the first argument.
|
|
738
|
+
if (fnType._derivedReturn) {
|
|
739
|
+
const derived = this._derivedReturnType(fnType._derivedReturn, argTypes);
|
|
740
|
+
if (derived) return derived;
|
|
741
|
+
}
|
|
742
|
+
|
|
579
743
|
const ret = fnType.returns;
|
|
580
744
|
if (!ret || ret.length === 0) return VOID;
|
|
581
745
|
if (ret.length === 1) return ret[0];
|
|
582
746
|
return { kind: "tuple", types: ret };
|
|
583
747
|
},
|
|
584
748
|
|
|
749
|
+
// `shared.NewXxx(n)` allocates from a linear memory that is sized once at
|
|
750
|
+
// startup (no memory.grow → no detached TypedArray views on the JS side).
|
|
751
|
+
// Allocation is therefore only legal in package-level var initializers and
|
|
752
|
+
// init(), which both run inside the module start function.
|
|
753
|
+
// JS→WASM calls copy non-shared arguments across the boundary on every
|
|
754
|
+
// call. Inside a loop that dominates the per-iteration cost, so hint that
|
|
755
|
+
// the loop belongs on the WASM side (or the data in gofront/shared).
|
|
756
|
+
_checkBoundaryCallInLoop(expr, scope) {
|
|
757
|
+
if (this._loopDepth === 0 || this.target !== "js") return;
|
|
758
|
+
const fn = expr.func;
|
|
759
|
+
if (fn.kind !== "SelectorExpr" || fn.expr.kind !== "Ident") return;
|
|
760
|
+
const ns = scope.lookup(fn.expr.name);
|
|
761
|
+
if (ns?.kind !== "namespace" || ns._target !== "wasm") return;
|
|
762
|
+
const member = ns.members[fn.field];
|
|
763
|
+
if (member?.kind !== "func") return;
|
|
764
|
+
this.warn(
|
|
765
|
+
`'${fn.expr.name}.${fn.field}' crosses the JS→WASM boundary inside a loop`,
|
|
766
|
+
fn.expr, // CallExpr nodes carry no position; the callee Ident does
|
|
767
|
+
"hint: each call copies its arguments; move the loop into the wasm package or pass gofront/shared buffers",
|
|
768
|
+
);
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
_checkSharedAllocSite(expr, scope) {
|
|
772
|
+
const f = expr.func;
|
|
773
|
+
if (
|
|
774
|
+
f.kind !== "SelectorExpr" ||
|
|
775
|
+
f.expr.kind !== "Ident" ||
|
|
776
|
+
f.expr.name !== "shared" ||
|
|
777
|
+
!f.field.startsWith("New") ||
|
|
778
|
+
scope.lookup("shared") !== this.globals.lookup("shared")
|
|
779
|
+
)
|
|
780
|
+
return;
|
|
781
|
+
if (this._sharedAllocOk) return;
|
|
782
|
+
const why =
|
|
783
|
+
this.target === "wasm"
|
|
784
|
+
? "shared buffers are allocated once at startup"
|
|
785
|
+
: "shared buffers are allocated once at startup so the code stays portable to wasm packages";
|
|
786
|
+
this.err(
|
|
787
|
+
`shared.${f.field} must be called from a package-level var initializer or init(); ${why}`,
|
|
788
|
+
expr,
|
|
789
|
+
);
|
|
790
|
+
},
|
|
791
|
+
|
|
792
|
+
_derivedReturnType(mode, argTypes) {
|
|
793
|
+
const arg = argTypes[0];
|
|
794
|
+
const under = arg?.kind === "named" ? arg.underlying : arg;
|
|
795
|
+
switch (mode) {
|
|
796
|
+
case "arg0":
|
|
797
|
+
return under ? arg : null;
|
|
798
|
+
case "elemOfArg0":
|
|
799
|
+
return under?.elem ?? null;
|
|
800
|
+
case "keysOfArg0":
|
|
801
|
+
return under?.key ? { kind: "slice", elem: under.key } : null;
|
|
802
|
+
case "valuesOfArg0": {
|
|
803
|
+
const v = under?.value ?? under?.elem;
|
|
804
|
+
return v ? { kind: "slice", elem: v } : null;
|
|
805
|
+
}
|
|
806
|
+
default:
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
|
|
585
811
|
_resolveGenericFnType(fnType, argTypes, expr, scope) {
|
|
586
812
|
const typeArgs = this.inferTypeArgs(fnType, argTypes);
|
|
587
813
|
if (!typeArgs) {
|
|
@@ -599,6 +825,10 @@ export const expressionCheckMethods = {
|
|
|
599
825
|
: null;
|
|
600
826
|
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
601
827
|
}
|
|
828
|
+
expr._typeArgs = typeArgs;
|
|
829
|
+
if (expr.func) expr.func._typeArgs = typeArgs;
|
|
830
|
+
expr._genericFnType = fnType;
|
|
831
|
+
if (expr.func) expr.func._genericFnType = fnType;
|
|
602
832
|
return this.instantiateGenericFunc(fnType, typeArgs);
|
|
603
833
|
},
|
|
604
834
|
|
|
@@ -620,13 +850,18 @@ export const expressionCheckMethods = {
|
|
|
620
850
|
|
|
621
851
|
_checkCallArgs(fnType, argTypes, expr) {
|
|
622
852
|
this._checkArgCount(fnType, argTypes, expr);
|
|
623
|
-
|
|
853
|
+
const maxLen = fnType.variadic
|
|
854
|
+
? argTypes.length
|
|
855
|
+
: Math.min(fnType.params.length, argTypes.length);
|
|
856
|
+
for (let i = 0; i < maxLen; i++) {
|
|
624
857
|
const paramIdx =
|
|
625
858
|
fnType.variadic && i >= fnType.params.length - 1
|
|
626
859
|
? fnType.params.length - 1
|
|
627
860
|
: i;
|
|
628
861
|
if (expr.args[i]?._spread) continue;
|
|
629
|
-
|
|
862
|
+
const targetParam = fnType.params[paramIdx];
|
|
863
|
+
const node = expr._multiForward ? null : expr.args[i];
|
|
864
|
+
this.assertAssignable(targetParam, argTypes[i], node);
|
|
630
865
|
}
|
|
631
866
|
},
|
|
632
867
|
|
|
@@ -650,6 +885,11 @@ export const expressionCheckMethods = {
|
|
|
650
885
|
if (name === "len") {
|
|
651
886
|
const size = this._constLenFromType(argTypes[0]);
|
|
652
887
|
if (size != null) expr._constLen = size;
|
|
888
|
+
} else if (sharedInfo(argTypes[0])) {
|
|
889
|
+
this.err(
|
|
890
|
+
`cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
|
|
891
|
+
expr,
|
|
892
|
+
);
|
|
653
893
|
}
|
|
654
894
|
return INT;
|
|
655
895
|
},
|
package/src/typechecker/index.js
CHANGED
|
@@ -45,6 +45,7 @@ export class TypeChecker {
|
|
|
45
45
|
this.types = new Map(); // named types
|
|
46
46
|
this.globals = new Scope();
|
|
47
47
|
this.errors = [];
|
|
48
|
+
this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
|
|
48
49
|
this.target = "js";
|
|
49
50
|
this.pkgName = null;
|
|
50
51
|
this.blockers = new Map();
|
|
@@ -77,7 +78,8 @@ export class TypeChecker {
|
|
|
77
78
|
// Add an imported GoFront package as a qualified namespace.
|
|
78
79
|
// e.g. addPackageNamespace('utils', symbolsMap, typesMap)
|
|
79
80
|
// lets callers type-check `utils.Foo` via SelectorExpr.
|
|
80
|
-
|
|
81
|
+
// `target` is the imported package's compile target ("js"/"wasm"/"both").
|
|
82
|
+
addPackageNamespace(pkgName, symbols, types, target = null) {
|
|
81
83
|
const members = {};
|
|
82
84
|
for (const [name, type] of symbols) members[name] = type;
|
|
83
85
|
// _gofront: true marks this as a GoFront package — exported identifier rules apply
|
|
@@ -86,6 +88,7 @@ export class TypeChecker {
|
|
|
86
88
|
name: pkgName,
|
|
87
89
|
members,
|
|
88
90
|
_gofront: true,
|
|
91
|
+
_target: target,
|
|
89
92
|
});
|
|
90
93
|
for (const [name, type] of types) {
|
|
91
94
|
this.types.set(name, type);
|
|
@@ -171,6 +174,21 @@ export class TypeChecker {
|
|
|
171
174
|
return TAINTED_ANY; // tainted recovery type — suppresses downstream cascade errors
|
|
172
175
|
}
|
|
173
176
|
|
|
177
|
+
// Non-fatal diagnostic: formatted like an error but collected separately so
|
|
178
|
+
// the build still succeeds. The compiler prints these as warnings.
|
|
179
|
+
warn(msg, node, hint = null) {
|
|
180
|
+
const e = new TypeCheckError(
|
|
181
|
+
msg,
|
|
182
|
+
node,
|
|
183
|
+
this._currentFile,
|
|
184
|
+
this._currentSource,
|
|
185
|
+
hint,
|
|
186
|
+
);
|
|
187
|
+
// `log.warn` adds the "warning:" label, so strip the error one here:
|
|
188
|
+
// "Type error in main.go at line 3:5: msg" → "main.go at line 3:5: msg".
|
|
189
|
+
this.warnings.push(e.message.replace(/^Type error(?: in)?[: ]\s*/, ""));
|
|
190
|
+
}
|
|
191
|
+
|
|
174
192
|
_reportUnused(scope, node) {
|
|
175
193
|
for (const name of scope.unusedLocals()) {
|
|
176
194
|
this.err(`'${name}' declared and not used`, node);
|
|
@@ -524,6 +542,7 @@ export class TypeChecker {
|
|
|
524
542
|
? existing
|
|
525
543
|
: { kind: "named", name: decl.name };
|
|
526
544
|
named.underlying = underlying;
|
|
545
|
+
named._target = this.target;
|
|
527
546
|
named._generic = {
|
|
528
547
|
typeParams: typeParamTypes,
|
|
529
548
|
declNode: decl,
|
|
@@ -549,6 +568,7 @@ export class TypeChecker {
|
|
|
549
568
|
? existing
|
|
550
569
|
: { kind: "named", name: decl.name };
|
|
551
570
|
named.underlying = underlying;
|
|
571
|
+
named._target = this.target;
|
|
552
572
|
if (underlying.kind === "struct") {
|
|
553
573
|
underlying.name = decl.name;
|
|
554
574
|
underlying.methods = new Map();
|
|
@@ -777,7 +797,10 @@ export class TypeChecker {
|
|
|
777
797
|
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
778
798
|
}
|
|
779
799
|
const returnType = this._setupFuncReturnType(decl, inner, outer);
|
|
800
|
+
const savedSharedAlloc = this._sharedAllocOk;
|
|
801
|
+
this._sharedAllocOk = decl.name === "init";
|
|
780
802
|
this._runFuncBody(decl, inner, returnType);
|
|
803
|
+
this._sharedAllocOk = savedSharedAlloc;
|
|
781
804
|
this._checkMissingReturn(decl, returnType, "function");
|
|
782
805
|
}
|
|
783
806
|
|
|
@@ -870,6 +893,8 @@ export class TypeChecker {
|
|
|
870
893
|
}
|
|
871
894
|
|
|
872
895
|
checkVarDecl(decl, scope) {
|
|
896
|
+
const savedSharedAlloc = this._sharedAllocOk;
|
|
897
|
+
this._sharedAllocOk = scope === this.globals;
|
|
873
898
|
for (const spec of decl.decls) {
|
|
874
899
|
let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
|
|
875
900
|
if (spec.value) {
|
|
@@ -883,6 +908,7 @@ export class TypeChecker {
|
|
|
883
908
|
if (!type) type = ANY;
|
|
884
909
|
for (const name of spec.names) scope.defineLocal(name, type);
|
|
885
910
|
}
|
|
911
|
+
this._sharedAllocOk = savedSharedAlloc;
|
|
886
912
|
}
|
|
887
913
|
|
|
888
914
|
checkConstDecl(decl, scope) {
|