gofront 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +50 -19
- package/src/backend/js/index.js +100 -55
- package/src/backend/js/runtime.js +102 -0
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +96 -16
- package/src/backend/js/stdlib/errors.js +2 -2
- package/src/backend/js/stdlib/fmt.js +26 -8
- package/src/backend/js/stdlib/index.js +3 -0
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +1 -1
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +34 -9
- package/src/backend/js/stdlib/strings.js +4 -1
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +3 -3
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +486 -33
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +606 -5009
- package/src/backend/wasm/encode.js +212 -64
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +1426 -49
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +159 -35
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +275 -72
- package/src/compiler.js +144 -21
- package/src/index.js +86 -115
- package/src/lower/boxing.js +3 -4
- package/src/lower/captures.js +10 -17
- 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 +5 -10
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/resolver.js +2 -1
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +142 -2
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +17 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +206 -7
- package/src/vendor.js +2 -2
|
@@ -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;
|
|
@@ -291,10 +299,42 @@ export const expressionCheckMethods = {
|
|
|
291
299
|
expr._mapValueType = btu.value;
|
|
292
300
|
return btu.value;
|
|
293
301
|
}
|
|
302
|
+
if (btu.kind === "shared") {
|
|
303
|
+
expr._sharedElem = btu.elem;
|
|
304
|
+
const idx = this._constIntValue(expr.index);
|
|
305
|
+
if (idx !== null && idx < 0)
|
|
306
|
+
this.err(`invalid index ${idx} (index must not be negative)`, expr);
|
|
307
|
+
return btu.elem;
|
|
308
|
+
}
|
|
294
309
|
if (isString(bt)) return INT;
|
|
295
310
|
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
296
311
|
},
|
|
297
312
|
|
|
313
|
+
// copy(dst, src): element types must be identical when either side is a
|
|
314
|
+
// shared buffer (the wasm emitter uses one element type for both sides).
|
|
315
|
+
_checkBuiltinCopy(e, at) {
|
|
316
|
+
const elemOf = (t) => {
|
|
317
|
+
const u = this.resolveType(t?.kind === "named" ? t.underlying : t);
|
|
318
|
+
return u?.kind === "slice" || u?.kind === "shared" ? u.elem : null;
|
|
319
|
+
};
|
|
320
|
+
const [dt, st] = at;
|
|
321
|
+
if (!sharedInfo(dt) && !sharedInfo(st)) return INT;
|
|
322
|
+
const de = elemOf(dt);
|
|
323
|
+
const se = elemOf(st);
|
|
324
|
+
if (!de || !se) {
|
|
325
|
+
this.err(
|
|
326
|
+
`copy: cannot copy between ${typeStr(dt)} and ${typeStr(st)}`,
|
|
327
|
+
e,
|
|
328
|
+
);
|
|
329
|
+
} else if (typeStr(de) !== typeStr(se)) {
|
|
330
|
+
this.err(
|
|
331
|
+
`copy: element types differ (${typeStr(dt)} vs ${typeStr(st)})`,
|
|
332
|
+
e,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
return INT;
|
|
336
|
+
},
|
|
337
|
+
|
|
298
338
|
_checkSliceExpr(expr, scope) {
|
|
299
339
|
const bt = this.checkExpr(expr.expr, scope);
|
|
300
340
|
if (expr.low) this.checkExpr(expr.low, scope);
|
|
@@ -305,10 +345,25 @@ export const expressionCheckMethods = {
|
|
|
305
345
|
if (bt.kind === "slice" || bt.kind === "array")
|
|
306
346
|
return { kind: "slice", elem: bt.elem };
|
|
307
347
|
if (isString(bt)) return STRING;
|
|
348
|
+
if (sharedInfo(bt))
|
|
349
|
+
return this.err(
|
|
350
|
+
`Cannot slice type ${typeStr(bt)}: use .Subarray(lo, hi) to create a view of a shared buffer`,
|
|
351
|
+
expr,
|
|
352
|
+
);
|
|
308
353
|
return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
|
|
309
354
|
},
|
|
310
355
|
|
|
311
356
|
_checkFuncLit(expr, scope) {
|
|
357
|
+
const savedSharedAlloc = this._sharedAllocOk;
|
|
358
|
+
this._sharedAllocOk = false;
|
|
359
|
+
try {
|
|
360
|
+
return this._checkFuncLitBody(expr, scope);
|
|
361
|
+
} finally {
|
|
362
|
+
this._sharedAllocOk = savedSharedAlloc;
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
_checkFuncLitBody(expr, scope) {
|
|
312
367
|
if (
|
|
313
368
|
(this.target === "wasm" || this.target === "both") &&
|
|
314
369
|
(expr.async || expr.isAsync)
|
|
@@ -431,7 +486,11 @@ export const expressionCheckMethods = {
|
|
|
431
486
|
|
|
432
487
|
_rangeCollTypeTuple(resolved, collType) {
|
|
433
488
|
if (isString(resolved)) return { kind: "tuple", types: [INT, RUNE] };
|
|
434
|
-
if (
|
|
489
|
+
if (
|
|
490
|
+
resolved?.kind === "slice" ||
|
|
491
|
+
resolved?.kind === "array" ||
|
|
492
|
+
resolved?.kind === "shared"
|
|
493
|
+
)
|
|
435
494
|
return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
|
|
436
495
|
if (resolved?.kind === "map")
|
|
437
496
|
return {
|
|
@@ -467,6 +526,9 @@ export const expressionCheckMethods = {
|
|
|
467
526
|
: null;
|
|
468
527
|
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
469
528
|
}
|
|
529
|
+
expr._typeArgs = typeArgs;
|
|
530
|
+
if (expr.expr) expr.expr._typeArgs = typeArgs;
|
|
531
|
+
expr._genericFnType = baseType;
|
|
470
532
|
return this.instantiateGenericFunc(baseType, typeArgs);
|
|
471
533
|
}
|
|
472
534
|
// The parser's type-arg heuristic mistakes `xs[d.Field]` (uppercase field
|
|
@@ -546,6 +608,8 @@ export const expressionCheckMethods = {
|
|
|
546
608
|
|
|
547
609
|
checkCall(expr, scope) {
|
|
548
610
|
let fnType = this.checkExpr(expr.func, scope);
|
|
611
|
+
this._checkSharedAllocSite(expr, scope);
|
|
612
|
+
this._checkBoundaryCallInLoop(expr, scope);
|
|
549
613
|
if (fnType.kind === "builtin") {
|
|
550
614
|
const argTypes = expr.args.map((a, i) => {
|
|
551
615
|
// new(T) — first arg is a type name, not a value expression
|
|
@@ -576,12 +640,79 @@ export const expressionCheckMethods = {
|
|
|
576
640
|
|
|
577
641
|
this._checkCallArgs(fnType, argTypes, expr);
|
|
578
642
|
|
|
643
|
+
// Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
|
|
644
|
+
// how their result derives from the first argument.
|
|
645
|
+
if (fnType._derivedReturn) {
|
|
646
|
+
const derived = this._derivedReturnType(fnType._derivedReturn, argTypes);
|
|
647
|
+
if (derived) return derived;
|
|
648
|
+
}
|
|
649
|
+
|
|
579
650
|
const ret = fnType.returns;
|
|
580
651
|
if (!ret || ret.length === 0) return VOID;
|
|
581
652
|
if (ret.length === 1) return ret[0];
|
|
582
653
|
return { kind: "tuple", types: ret };
|
|
583
654
|
},
|
|
584
655
|
|
|
656
|
+
// `shared.NewXxx(n)` allocates from a linear memory that is sized once at
|
|
657
|
+
// startup (no memory.grow → no detached TypedArray views on the JS side).
|
|
658
|
+
// Allocation is therefore only legal in package-level var initializers and
|
|
659
|
+
// init(), which both run inside the module start function.
|
|
660
|
+
// JS→WASM calls copy non-shared arguments across the boundary on every
|
|
661
|
+
// call. Inside a loop that dominates the per-iteration cost, so hint that
|
|
662
|
+
// the loop belongs on the WASM side (or the data in gofront/shared).
|
|
663
|
+
_checkBoundaryCallInLoop(expr, scope) {
|
|
664
|
+
if (this._loopDepth === 0 || this.target !== "js") return;
|
|
665
|
+
const fn = expr.func;
|
|
666
|
+
if (fn.kind !== "SelectorExpr" || fn.expr.kind !== "Ident") return;
|
|
667
|
+
const ns = scope.lookup(fn.expr.name);
|
|
668
|
+
if (ns?.kind !== "namespace" || ns._target !== "wasm") return;
|
|
669
|
+
const member = ns.members[fn.field];
|
|
670
|
+
if (member?.kind !== "func") return;
|
|
671
|
+
this.warn(
|
|
672
|
+
`'${fn.expr.name}.${fn.field}' crosses the JS→WASM boundary inside a loop`,
|
|
673
|
+
fn.expr, // CallExpr nodes carry no position; the callee Ident does
|
|
674
|
+
"hint: each call copies its arguments; move the loop into the wasm package or pass gofront/shared buffers",
|
|
675
|
+
);
|
|
676
|
+
},
|
|
677
|
+
|
|
678
|
+
_checkSharedAllocSite(expr, scope) {
|
|
679
|
+
const f = expr.func;
|
|
680
|
+
if (
|
|
681
|
+
f.kind !== "SelectorExpr" ||
|
|
682
|
+
f.expr.kind !== "Ident" ||
|
|
683
|
+
f.expr.name !== "shared" ||
|
|
684
|
+
!f.field.startsWith("New") ||
|
|
685
|
+
scope.lookup("shared") !== this.globals.lookup("shared")
|
|
686
|
+
)
|
|
687
|
+
return;
|
|
688
|
+
if (this._sharedAllocOk) return;
|
|
689
|
+
const why =
|
|
690
|
+
this.target === "wasm"
|
|
691
|
+
? "shared buffers are allocated once at startup"
|
|
692
|
+
: "shared buffers are allocated once at startup so the code stays portable to wasm packages";
|
|
693
|
+
this.err(
|
|
694
|
+
`shared.${f.field} must be called from a package-level var initializer or init(); ${why}`,
|
|
695
|
+
expr,
|
|
696
|
+
);
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
_derivedReturnType(mode, argTypes) {
|
|
700
|
+
const arg = argTypes[0];
|
|
701
|
+
const under = arg?.kind === "named" ? arg.underlying : arg;
|
|
702
|
+
switch (mode) {
|
|
703
|
+
case "arg0":
|
|
704
|
+
return under ? arg : null;
|
|
705
|
+
case "keysOfArg0":
|
|
706
|
+
return under?.key ? { kind: "slice", elem: under.key } : null;
|
|
707
|
+
case "valuesOfArg0": {
|
|
708
|
+
const v = under?.value ?? under?.elem;
|
|
709
|
+
return v ? { kind: "slice", elem: v } : null;
|
|
710
|
+
}
|
|
711
|
+
default:
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
},
|
|
715
|
+
|
|
585
716
|
_resolveGenericFnType(fnType, argTypes, expr, scope) {
|
|
586
717
|
const typeArgs = this.inferTypeArgs(fnType, argTypes);
|
|
587
718
|
if (!typeArgs) {
|
|
@@ -599,6 +730,10 @@ export const expressionCheckMethods = {
|
|
|
599
730
|
: null;
|
|
600
731
|
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
601
732
|
}
|
|
733
|
+
expr._typeArgs = typeArgs;
|
|
734
|
+
if (expr.func) expr.func._typeArgs = typeArgs;
|
|
735
|
+
expr._genericFnType = fnType;
|
|
736
|
+
if (expr.func) expr.func._genericFnType = fnType;
|
|
602
737
|
return this.instantiateGenericFunc(fnType, typeArgs);
|
|
603
738
|
},
|
|
604
739
|
|
|
@@ -650,6 +785,11 @@ export const expressionCheckMethods = {
|
|
|
650
785
|
if (name === "len") {
|
|
651
786
|
const size = this._constLenFromType(argTypes[0]);
|
|
652
787
|
if (size != null) expr._constLen = size;
|
|
788
|
+
} else if (sharedInfo(argTypes[0])) {
|
|
789
|
+
this.err(
|
|
790
|
+
`cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
|
|
791
|
+
expr,
|
|
792
|
+
);
|
|
653
793
|
}
|
|
654
794
|
return INT;
|
|
655
795
|
},
|
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) {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import {
|
|
5
5
|
ANY,
|
|
6
6
|
BASIC_TYPES,
|
|
7
|
+
COMPARABLE,
|
|
7
8
|
defaultType,
|
|
8
9
|
isAny,
|
|
9
10
|
isTypedArraySlice,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
typedArrayConstructorForElem,
|
|
13
14
|
typeStr,
|
|
14
15
|
VOID,
|
|
16
|
+
WASM_STDLIB_MEMBERS,
|
|
15
17
|
} from "./types.js";
|
|
16
18
|
|
|
17
19
|
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
@@ -81,8 +83,7 @@ export const resolveMethods = {
|
|
|
81
83
|
switch (node.kind) {
|
|
82
84
|
case "TypeName": {
|
|
83
85
|
if (BASIC_TYPES[node.name]) return BASIC_TYPES[node.name];
|
|
84
|
-
if (node.name === "comparable")
|
|
85
|
-
return { kind: "basic", name: "comparable" };
|
|
86
|
+
if (node.name === "comparable") return COMPARABLE;
|
|
86
87
|
if (scope) {
|
|
87
88
|
const fromScope = scope.lookup(node.name);
|
|
88
89
|
if (fromScope?.kind === "typeParam") return fromScope;
|
|
@@ -99,12 +100,16 @@ export const resolveMethods = {
|
|
|
99
100
|
case "GenericTypeName": {
|
|
100
101
|
const base = this.types.get(node.name);
|
|
101
102
|
if (!base) return this.err(`Unknown type '${node.name}'`, node);
|
|
102
|
-
if (base.kind === "named" && base._generic)
|
|
103
|
-
|
|
103
|
+
if (base.kind === "named" && base._generic) {
|
|
104
|
+
const res = this.instantiateGenericType(
|
|
104
105
|
base._generic,
|
|
105
106
|
node.typeArgs,
|
|
106
107
|
scope,
|
|
107
108
|
);
|
|
109
|
+
node._typeArgs = res.typeArgs;
|
|
110
|
+
node._genericType = base;
|
|
111
|
+
return res;
|
|
112
|
+
}
|
|
108
113
|
return this.err(`Type '${node.name}' is not generic`, node);
|
|
109
114
|
}
|
|
110
115
|
case "TypeParam": {
|
|
@@ -266,12 +271,15 @@ export const resolveMethods = {
|
|
|
266
271
|
const typeArgs = typeArgNodes.map((n) => this.resolveTypeNode(n, scope));
|
|
267
272
|
for (let i = 0; i < generic.typeParams.length; i++)
|
|
268
273
|
map.set(generic.typeParams[i].name, typeArgs[i] ?? ANY);
|
|
274
|
+
// Resolve the declaration with its type params in scope (as typeParams,
|
|
275
|
+
// never as value symbols), then substitute the concrete arguments.
|
|
269
276
|
const typeScope = new Scope(this.globals);
|
|
270
|
-
for (const
|
|
277
|
+
for (const tp of generic.typeParams) typeScope.define(tp.name, tp);
|
|
271
278
|
const underlying = this.resolveTypeNode(generic.declNode.type, typeScope);
|
|
272
279
|
const instantiated = this.substituteType(underlying, map);
|
|
273
280
|
if (instantiated.kind === "struct") {
|
|
274
281
|
instantiated.name = generic.declNode.name;
|
|
282
|
+
instantiated.typeArgs = typeArgs;
|
|
275
283
|
if (!instantiated.methods) instantiated.methods = new Map();
|
|
276
284
|
if (generic.methods) {
|
|
277
285
|
for (const [mName, mType] of generic.methods)
|
|
@@ -281,6 +289,7 @@ export const resolveMethods = {
|
|
|
281
289
|
return {
|
|
282
290
|
kind: "named",
|
|
283
291
|
name: generic.declNode.name,
|
|
292
|
+
typeArgs,
|
|
284
293
|
underlying: instantiated,
|
|
285
294
|
};
|
|
286
295
|
},
|
|
@@ -406,6 +415,9 @@ export const resolveMethods = {
|
|
|
406
415
|
return this._fieldTypeInterface(base, baseType, field, node);
|
|
407
416
|
case "namespace":
|
|
408
417
|
return this._fieldTypeNamespace(base, field, node);
|
|
418
|
+
case "shared":
|
|
419
|
+
if (base.methods?.has(field)) return base.methods.get(field);
|
|
420
|
+
return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
|
|
409
421
|
case "slice":
|
|
410
422
|
if (isTypedArraySlice(base)) {
|
|
411
423
|
return this._fieldTypeTypedArraySlice(base, baseType, field, node);
|
|
@@ -475,7 +487,24 @@ export const resolveMethods = {
|
|
|
475
487
|
`cannot refer to unexported name ${base.name}.${field}`,
|
|
476
488
|
node,
|
|
477
489
|
);
|
|
478
|
-
if (field in base.members)
|
|
490
|
+
if (field in base.members) {
|
|
491
|
+
if (
|
|
492
|
+
(this.target === "wasm" || this.target === "both") &&
|
|
493
|
+
WASM_STDLIB_MEMBERS.has(base.name)
|
|
494
|
+
) {
|
|
495
|
+
const allowed = WASM_STDLIB_MEMBERS.get(base.name);
|
|
496
|
+
if (allowed && !allowed.has(field)) {
|
|
497
|
+
const name = `${base.name}.${field}`;
|
|
498
|
+
this.recordBlocker("unsupported stdlib member", name);
|
|
499
|
+
return this.err(
|
|
500
|
+
`'${name}' is not yet available in wasm packages`,
|
|
501
|
+
node,
|
|
502
|
+
"move this call to a JS package, or use a supported alternative",
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return base.members[field];
|
|
507
|
+
}
|
|
479
508
|
return this.err(`No member '${field}' in namespace ${base.name}`, node);
|
|
480
509
|
},
|
|
481
510
|
};
|
|
@@ -43,13 +43,6 @@ export const statementCheckMethods = {
|
|
|
43
43
|
this.checkExpr(stmt.expr, scope);
|
|
44
44
|
break;
|
|
45
45
|
case "DeferStmt":
|
|
46
|
-
if (this.target === "wasm" || this.target === "both") {
|
|
47
|
-
this.recordBlocker("defer statement");
|
|
48
|
-
this.err(
|
|
49
|
-
"'defer' is not yet supported in wasm packages (planned)",
|
|
50
|
-
stmt,
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
46
|
if (stmt.call.kind !== "CallExpr")
|
|
54
47
|
this.err("defer requires a function call", stmt.call);
|
|
55
48
|
this.checkExpr(stmt.call, scope);
|
|
@@ -540,6 +540,14 @@ export function setupCoreGlobals(globals, types) {
|
|
|
540
540
|
},
|
|
541
541
|
});
|
|
542
542
|
|
|
543
|
+
// Slice helpers that return a slice of the same type as their first argument.
|
|
544
|
+
const sameSlice = (params, extra = {}) => ({
|
|
545
|
+
kind: "func",
|
|
546
|
+
params,
|
|
547
|
+
returns: [ANY],
|
|
548
|
+
_derivedReturn: "arg0",
|
|
549
|
+
...extra,
|
|
550
|
+
});
|
|
543
551
|
globals.define("slices", {
|
|
544
552
|
kind: "namespace",
|
|
545
553
|
name: "slices",
|
|
@@ -558,26 +566,16 @@ export function setupCoreGlobals(globals, types) {
|
|
|
558
566
|
Min: { kind: "func", params: [ANY], returns: [ANY] },
|
|
559
567
|
MaxFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
|
|
560
568
|
MinFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
|
|
561
|
-
Clone:
|
|
562
|
-
Compact:
|
|
563
|
-
CompactFunc:
|
|
569
|
+
Clone: sameSlice([ANY]),
|
|
570
|
+
Compact: sameSlice([ANY]),
|
|
571
|
+
CompactFunc: sameSlice([ANY, ANY]),
|
|
564
572
|
Concat: { kind: "func", params: [ANY], returns: [ANY], variadic: true },
|
|
565
|
-
Delete:
|
|
566
|
-
DeleteFunc:
|
|
567
|
-
Insert: {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
variadic: true,
|
|
572
|
-
},
|
|
573
|
-
Replace: {
|
|
574
|
-
kind: "func",
|
|
575
|
-
params: [ANY, INT, INT, ANY],
|
|
576
|
-
returns: [ANY],
|
|
577
|
-
variadic: true,
|
|
578
|
-
},
|
|
579
|
-
Grow: { kind: "func", params: [ANY, INT], returns: [ANY] },
|
|
580
|
-
Clip: { kind: "func", params: [ANY], returns: [ANY] },
|
|
573
|
+
Delete: sameSlice([ANY, INT, INT]),
|
|
574
|
+
DeleteFunc: sameSlice([ANY, ANY]),
|
|
575
|
+
Insert: sameSlice([ANY, INT, ANY], { variadic: true }),
|
|
576
|
+
Replace: sameSlice([ANY, INT, INT, ANY], { variadic: true }),
|
|
577
|
+
Grow: sameSlice([ANY, INT]),
|
|
578
|
+
Clip: sameSlice([ANY]),
|
|
581
579
|
},
|
|
582
580
|
});
|
|
583
581
|
|
|
@@ -160,9 +160,24 @@ export function setupExtendedGlobals(globals, types) {
|
|
|
160
160
|
kind: "namespace",
|
|
161
161
|
name: "maps",
|
|
162
162
|
members: {
|
|
163
|
-
Keys: {
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
Keys: {
|
|
164
|
+
kind: "func",
|
|
165
|
+
params: [ANY],
|
|
166
|
+
returns: [ANY],
|
|
167
|
+
_derivedReturn: "keysOfArg0",
|
|
168
|
+
},
|
|
169
|
+
Values: {
|
|
170
|
+
kind: "func",
|
|
171
|
+
params: [ANY],
|
|
172
|
+
returns: [ANY],
|
|
173
|
+
_derivedReturn: "valuesOfArg0",
|
|
174
|
+
},
|
|
175
|
+
Clone: {
|
|
176
|
+
kind: "func",
|
|
177
|
+
params: [ANY],
|
|
178
|
+
returns: [ANY],
|
|
179
|
+
_derivedReturn: "arg0",
|
|
180
|
+
},
|
|
166
181
|
Copy: { kind: "func", params: [ANY, ANY], returns: [VOID] },
|
|
167
182
|
Equal: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
|
|
168
183
|
EqualFunc: { kind: "func", params: [ANY, ANY, ANY], returns: [BOOL] },
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// `gofront/shared` — fixed-size linear-memory buffers shared zero-copy between
|
|
2
|
+
// wasm packages and JS. Each `shared.Xxx` is a named type whose underlying kind
|
|
3
|
+
// is "shared" (not a slice): it supports indexing, len() and `.Subarray(lo, hi)`
|
|
4
|
+
// but not slicing, append or implicit conversion to GC slices.
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
FLOAT32,
|
|
8
|
+
FLOAT64,
|
|
9
|
+
INT,
|
|
10
|
+
INT8,
|
|
11
|
+
INT16,
|
|
12
|
+
INT32,
|
|
13
|
+
UINT8,
|
|
14
|
+
UINT16,
|
|
15
|
+
UINT32,
|
|
16
|
+
} from "../types.js";
|
|
17
|
+
|
|
18
|
+
// key → [elem type, JS TypedArray ctor, byte size]
|
|
19
|
+
export const SHARED_KINDS = {
|
|
20
|
+
Float32: { elem: FLOAT32, ctor: "Float32Array", bytes: 4, key: "f32" },
|
|
21
|
+
Float64: { elem: FLOAT64, ctor: "Float64Array", bytes: 8, key: "f64" },
|
|
22
|
+
Int8: { elem: INT8, ctor: "Int8Array", bytes: 1, key: "i8" },
|
|
23
|
+
Int16: { elem: INT16, ctor: "Int16Array", bytes: 2, key: "i16" },
|
|
24
|
+
Int32: { elem: INT32, ctor: "Int32Array", bytes: 4, key: "i32" },
|
|
25
|
+
Uint8: { elem: UINT8, ctor: "Uint8Array", bytes: 1, key: "u8" },
|
|
26
|
+
Uint16: { elem: UINT16, ctor: "Uint16Array", bytes: 2, key: "u16" },
|
|
27
|
+
Uint32: { elem: UINT32, ctor: "Uint32Array", bytes: 4, key: "u32" },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function setupSharedGlobals(globals, types) {
|
|
31
|
+
const members = {};
|
|
32
|
+
for (const [name, info] of Object.entries(SHARED_KINDS)) {
|
|
33
|
+
const t = {
|
|
34
|
+
kind: "named",
|
|
35
|
+
name: `shared.${name}`,
|
|
36
|
+
underlying: { kind: "shared", elem: info.elem, shared: name },
|
|
37
|
+
};
|
|
38
|
+
t.underlying.methods = new Map([
|
|
39
|
+
["Subarray", { kind: "func", params: [INT, INT], returns: [t] }],
|
|
40
|
+
]);
|
|
41
|
+
types.set(t.name, t);
|
|
42
|
+
members[name] = t;
|
|
43
|
+
members[`New${name}`] = { kind: "func", params: [INT], returns: [t] };
|
|
44
|
+
}
|
|
45
|
+
globals.define("shared", { kind: "namespace", name: "shared", members });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Returns the SHARED_KINDS entry for a (possibly named) shared type, or null.
|
|
49
|
+
export function sharedInfo(t) {
|
|
50
|
+
const u = t?.kind === "named" ? t.underlying : t;
|
|
51
|
+
if (u?.kind !== "shared") return null;
|
|
52
|
+
return { name: u.shared, ...SHARED_KINDS[u.shared] };
|
|
53
|
+
}
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import { setupCoreGlobals } from "./stdlib/core.js";
|
|
11
11
|
import { setupExtendedGlobals } from "./stdlib/extended.js";
|
|
12
|
+
import { setupSharedGlobals } from "./stdlib/shared.js";
|
|
12
13
|
import { setupWebGlobals } from "./stdlib/web.js";
|
|
13
14
|
|
|
14
15
|
export function setupGlobals(globals, types) {
|
|
15
16
|
setupCoreGlobals(globals, types);
|
|
16
17
|
setupExtendedGlobals(globals, types);
|
|
18
|
+
setupSharedGlobals(globals, types);
|
|
17
19
|
setupWebGlobals(globals, types);
|
|
18
20
|
}
|