gofront 1.4.0 → 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 -403
- package/package.json +16 -19
- package/src/{codegen → backend/js}/expressions.js +265 -77
- package/src/{codegen → backend/js}/index.js +120 -89
- package/src/{codegen → backend/js}/runtime.js +102 -0
- package/src/{codegen → backend/js}/source-map.js +32 -0
- package/src/{codegen → backend/js}/statements.js +139 -64
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
- package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
- package/src/{codegen → backend/js}/stdlib/index.js +6 -0
- package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
- package/src/{codegen → backend/js}/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
- package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +58 -0
- package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
- package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
- package/src/{codegen → backend/js}/stdlib/time.js +3 -3
- package/src/{codegen → backend/js}/templ.js +3 -3
- package/src/backend/wasm/boundary.js +1798 -0
- 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 +1376 -0
- package/src/backend/wasm/encode.js +937 -0
- package/src/backend/wasm/glue.js +144 -0
- package/src/backend/wasm/index.js +2790 -0
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +544 -0
- package/src/backend/wasm/wat.js +302 -0
- package/src/cli-core.js +323 -75
- package/src/compiler.js +447 -14
- package/src/dev-server.js +56 -15
- package/src/index.js +99 -120
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +35 -0
- package/src/lower/captures.js +172 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +164 -0
- package/src/lower/functions.js +302 -0
- package/src/lower/globals.js +316 -0
- package/src/lower/index.js +118 -0
- package/src/lower/ownership.js +137 -0
- package/src/lower/range.js +67 -0
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +3 -1
- package/src/test-runner.js +94 -20
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +182 -3
- package/src/typechecker/index.js +261 -5
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/stdlib/core.js +34 -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 +259 -6
- package/src/vendor.js +2 -2
- package/src/codegen/stdlib/strconv.js +0 -33
- /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
package/src/typechecker/index.js
CHANGED
|
@@ -25,9 +25,18 @@ import {
|
|
|
25
25
|
TAINTED_ANY,
|
|
26
26
|
TypeCheckError,
|
|
27
27
|
typeStr,
|
|
28
|
+
VALID_TARGETS,
|
|
28
29
|
VOID,
|
|
29
30
|
} from "./types.js";
|
|
30
31
|
|
|
32
|
+
function rootIdentName(e) {
|
|
33
|
+
if (!e) return null;
|
|
34
|
+
if (e.kind === "Ident") return e.name;
|
|
35
|
+
if (e.kind === "SelectorExpr" || e.kind === "IndexExpr")
|
|
36
|
+
return rootIdentName(e.expr);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
// Re-export for consumers that import from typechecker.js
|
|
32
41
|
export { TypeCheckError, typeStr };
|
|
33
42
|
|
|
@@ -36,6 +45,11 @@ export class TypeChecker {
|
|
|
36
45
|
this.types = new Map(); // named types
|
|
37
46
|
this.globals = new Scope();
|
|
38
47
|
this.errors = [];
|
|
48
|
+
this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
|
|
49
|
+
this.target = "js";
|
|
50
|
+
this.pkgName = null;
|
|
51
|
+
this.blockers = new Map();
|
|
52
|
+
this.summary = null;
|
|
39
53
|
this._currentFile = null; // filename of file currently being checked
|
|
40
54
|
this._currentSource = null;
|
|
41
55
|
this._loopDepth = 0; // for break/continue validation
|
|
@@ -64,7 +78,8 @@ export class TypeChecker {
|
|
|
64
78
|
// Add an imported GoFront package as a qualified namespace.
|
|
65
79
|
// e.g. addPackageNamespace('utils', symbolsMap, typesMap)
|
|
66
80
|
// lets callers type-check `utils.Foo` via SelectorExpr.
|
|
67
|
-
|
|
81
|
+
// `target` is the imported package's compile target ("js"/"wasm"/"both").
|
|
82
|
+
addPackageNamespace(pkgName, symbols, types, target = null) {
|
|
68
83
|
const members = {};
|
|
69
84
|
for (const [name, type] of symbols) members[name] = type;
|
|
70
85
|
// _gofront: true marks this as a GoFront package — exported identifier rules apply
|
|
@@ -73,6 +88,7 @@ export class TypeChecker {
|
|
|
73
88
|
name: pkgName,
|
|
74
89
|
members,
|
|
75
90
|
_gofront: true,
|
|
91
|
+
_target: target,
|
|
76
92
|
});
|
|
77
93
|
for (const [name, type] of types) {
|
|
78
94
|
this.types.set(name, type);
|
|
@@ -117,17 +133,62 @@ export class TypeChecker {
|
|
|
117
133
|
return new Map(this.types);
|
|
118
134
|
}
|
|
119
135
|
|
|
120
|
-
|
|
136
|
+
recordBlocker(category, item = null) {
|
|
137
|
+
if (!this.blockers.has(category)) {
|
|
138
|
+
this.blockers.set(category, []);
|
|
139
|
+
}
|
|
140
|
+
this.blockers.get(category).push(item);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getSummary() {
|
|
144
|
+
if (this.blockers.size === 0) return null;
|
|
145
|
+
let total = 0;
|
|
146
|
+
const parts = [];
|
|
147
|
+
for (const [category, items] of this.blockers) {
|
|
148
|
+
const uniqueItems = [
|
|
149
|
+
...new Set(items.filter((x) => x != null && x !== "")),
|
|
150
|
+
];
|
|
151
|
+
const count = uniqueItems.length > 0 ? uniqueItems.length : items.length;
|
|
152
|
+
total += count;
|
|
153
|
+
const itemsStr =
|
|
154
|
+
uniqueItems.length > 0 ? ` (${uniqueItems.join(", ")})` : "";
|
|
155
|
+
// "gom usage" is a mass noun; everything else pluralises on the last word.
|
|
156
|
+
const label =
|
|
157
|
+
count === 1 || category === "gom usage" ? category : `${category}s`;
|
|
158
|
+
parts.push(`${count} ${label}${itemsStr}`);
|
|
159
|
+
}
|
|
160
|
+
const noun = total === 1 ? "blocker" : "blockers";
|
|
161
|
+
const what = this.target === "both" ? "both" : "wasm";
|
|
162
|
+
return `package '${this.pkgName}' cannot be ${what}: ${total} ${noun} — ${parts.join(", ")}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
err(msg, node, hint = null) {
|
|
121
166
|
const e = new TypeCheckError(
|
|
122
167
|
msg,
|
|
123
168
|
node,
|
|
124
169
|
this._currentFile,
|
|
125
170
|
this._currentSource,
|
|
171
|
+
hint,
|
|
126
172
|
);
|
|
127
173
|
this.errors.push(e);
|
|
128
174
|
return TAINTED_ANY; // tainted recovery type — suppresses downstream cascade errors
|
|
129
175
|
}
|
|
130
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
|
+
|
|
131
192
|
_reportUnused(scope, node) {
|
|
132
193
|
for (const name of scope.unusedLocals()) {
|
|
133
194
|
this.err(`'${name}' declared and not used`, node);
|
|
@@ -163,18 +224,181 @@ export class TypeChecker {
|
|
|
163
224
|
}
|
|
164
225
|
}
|
|
165
226
|
|
|
166
|
-
// Like check() but operates over multiple programs (same-package multi-file).
|
|
167
|
-
// All passes run across all files before moving to the next pass,
|
|
168
|
-
// so every file sees every other file's declarations.
|
|
169
227
|
checkAll(programs) {
|
|
228
|
+
for (const p of programs) {
|
|
229
|
+
const d = p._targetDirective;
|
|
230
|
+
if (!d) continue;
|
|
231
|
+
this._setCurrentFile(p);
|
|
232
|
+
if (!VALID_TARGETS.has(d.value)) {
|
|
233
|
+
this.err(
|
|
234
|
+
`unknown //gofront:target '${d.value}' (expected js, wasm or both)`,
|
|
235
|
+
d,
|
|
236
|
+
);
|
|
237
|
+
p.target = null;
|
|
238
|
+
if (this.target === d.value) this.target = "js";
|
|
239
|
+
} else if (!d.beforePackage) {
|
|
240
|
+
this.err("//gofront:target must appear before the package clause", d);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (programs.length > 0) {
|
|
244
|
+
if (!this.target || this.target === "js") {
|
|
245
|
+
const pTarget = programs.find((p) => p.target)?.target;
|
|
246
|
+
if (pTarget) this.target = pTarget;
|
|
247
|
+
}
|
|
248
|
+
if (!this.pkgName) {
|
|
249
|
+
this.pkgName = programs[0].pkg?.name ?? null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (this.target === "wasm" || this.target === "both") {
|
|
254
|
+
for (const p of programs) {
|
|
255
|
+
if (p._filename?.endsWith(".templ")) {
|
|
256
|
+
this._setCurrentFile(p);
|
|
257
|
+
const fileName = p._filename.split("/").pop();
|
|
258
|
+
this.recordBlocker(".templ file", fileName);
|
|
259
|
+
this.err(
|
|
260
|
+
`.templ files are not allowed in wasm packages; move '${fileName}' to a js package`,
|
|
261
|
+
p.pkg,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
170
267
|
this._collectTypesPass(programs);
|
|
171
268
|
this._collectFuncsPass(programs);
|
|
172
269
|
this._promoteEmbeddedMethods();
|
|
173
270
|
this._collectVarsConstsPass(programs);
|
|
174
271
|
this._checkTopDeclsPass(programs);
|
|
272
|
+
|
|
273
|
+
if (this.target === "both") {
|
|
274
|
+
this._checkMutablePackageVars(programs);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (this.blockers.size > 0) {
|
|
278
|
+
const summaryLine = this.getSummary();
|
|
279
|
+
this.summary = summaryLine;
|
|
280
|
+
const summaryErr = new Error(summaryLine);
|
|
281
|
+
summaryErr.isSummary = true;
|
|
282
|
+
this.errors.push(summaryErr);
|
|
283
|
+
}
|
|
284
|
+
|
|
175
285
|
return this.errors;
|
|
176
286
|
}
|
|
177
287
|
|
|
288
|
+
_checkMutablePackageVars(programs) {
|
|
289
|
+
const pkgVars = new Set();
|
|
290
|
+
for (const p of programs) {
|
|
291
|
+
for (const d of p.decls) {
|
|
292
|
+
if (d.kind === "VarDecl") {
|
|
293
|
+
for (const spec of d.decls) {
|
|
294
|
+
for (const name of spec.names) {
|
|
295
|
+
if (name !== "_") pkgVars.add(name);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (pkgVars.size === 0) return;
|
|
302
|
+
|
|
303
|
+
const SCOPE_KINDS = new Set([
|
|
304
|
+
"Block",
|
|
305
|
+
"FuncLit",
|
|
306
|
+
"ForStmt",
|
|
307
|
+
"IfStmt",
|
|
308
|
+
"SwitchStmt",
|
|
309
|
+
"TypeSwitchStmt",
|
|
310
|
+
"CaseClause",
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
const report = (root, node) => {
|
|
314
|
+
this.recordBlocker("mutable package variable", root);
|
|
315
|
+
this.err(
|
|
316
|
+
`package-level variable '${root}' is mutated; not allowed in 'both' packages (each target gets its own copy)`,
|
|
317
|
+
node,
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const checkBody = (body, initialLocals) => {
|
|
322
|
+
const scopes = [new Set(initialLocals)];
|
|
323
|
+
const isLocal = (name) => scopes.some((s) => s.has(name));
|
|
324
|
+
const declare = (name) => {
|
|
325
|
+
if (name && name !== "_") scopes[scopes.length - 1].add(name);
|
|
326
|
+
};
|
|
327
|
+
const flag = (e, node) => {
|
|
328
|
+
const root = rootIdentName(e);
|
|
329
|
+
if (root && pkgVars.has(root) && !isLocal(root)) report(root, node);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const walk = (node) => {
|
|
333
|
+
if (!node || typeof node !== "object") return;
|
|
334
|
+
if (Array.isArray(node)) {
|
|
335
|
+
for (const item of node) walk(item);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const opensScope = SCOPE_KINDS.has(node.kind);
|
|
339
|
+
if (opensScope) scopes.push(new Set());
|
|
340
|
+
switch (node.kind) {
|
|
341
|
+
case "FuncLit":
|
|
342
|
+
for (const p of node.params ?? []) declare(p.name);
|
|
343
|
+
for (const r of node.returnType?._namedReturns ?? [])
|
|
344
|
+
declare(r.name);
|
|
345
|
+
break;
|
|
346
|
+
case "VarDecl":
|
|
347
|
+
for (const spec of node.decls ?? [])
|
|
348
|
+
for (const name of spec.names ?? []) declare(name);
|
|
349
|
+
break;
|
|
350
|
+
case "DefineStmt":
|
|
351
|
+
for (const e of node.lhs ?? [])
|
|
352
|
+
if (e.kind === "Ident") declare(e.name);
|
|
353
|
+
break;
|
|
354
|
+
case "TypeSwitchStmt":
|
|
355
|
+
declare(node.assign);
|
|
356
|
+
break;
|
|
357
|
+
case "AssignStmt":
|
|
358
|
+
for (const e of node.lhs) flag(e, node);
|
|
359
|
+
break;
|
|
360
|
+
case "IncDecStmt":
|
|
361
|
+
flag(node.expr, node);
|
|
362
|
+
break;
|
|
363
|
+
case "UnaryExpr":
|
|
364
|
+
if (node.op === "&") flag(node.operand, node);
|
|
365
|
+
break;
|
|
366
|
+
case "SelectorExpr":
|
|
367
|
+
// Pointer-receiver method call or value: `G.Bump()` mutates G.
|
|
368
|
+
if (node._isMethodValue && node._type?._ptrRecv)
|
|
369
|
+
flag(node.expr, node);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
for (const key of Object.keys(node)) {
|
|
373
|
+
if (key.startsWith("_")) continue;
|
|
374
|
+
walk(node[key]);
|
|
375
|
+
}
|
|
376
|
+
if (opensScope) scopes.pop();
|
|
377
|
+
};
|
|
378
|
+
walk(body);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
for (const p of programs) {
|
|
382
|
+
if (p._filename?.endsWith("_test.go")) continue;
|
|
383
|
+
this._currentFile = p._filename;
|
|
384
|
+
this._currentSource = p._source;
|
|
385
|
+
for (const d of p.decls) {
|
|
386
|
+
if ((d.kind === "FuncDecl" || d.kind === "MethodDecl") && d.body) {
|
|
387
|
+
if (d.name === "init" && d.kind === "FuncDecl") continue;
|
|
388
|
+
const locals = [];
|
|
389
|
+
if (d.recvName) locals.push(d.recvName);
|
|
390
|
+
for (const prm of d.params ?? []) if (prm.name) locals.push(prm.name);
|
|
391
|
+
for (const r of d.returnType?._namedReturns ?? [])
|
|
392
|
+
if (r.name) locals.push(r.name);
|
|
393
|
+
checkBody(d.body, locals);
|
|
394
|
+
} else if (d.kind === "VarDecl") {
|
|
395
|
+
// Closures in package-level initializers run after init.
|
|
396
|
+
for (const spec of d.decls ?? []) checkBody(spec.value, []);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
178
402
|
_collectTypesPass(programs) {
|
|
179
403
|
for (const p of programs) {
|
|
180
404
|
for (const d of p.decls) {
|
|
@@ -318,6 +542,7 @@ export class TypeChecker {
|
|
|
318
542
|
? existing
|
|
319
543
|
: { kind: "named", name: decl.name };
|
|
320
544
|
named.underlying = underlying;
|
|
545
|
+
named._target = this.target;
|
|
321
546
|
named._generic = {
|
|
322
547
|
typeParams: typeParamTypes,
|
|
323
548
|
declNode: decl,
|
|
@@ -343,6 +568,7 @@ export class TypeChecker {
|
|
|
343
568
|
? existing
|
|
344
569
|
: { kind: "named", name: decl.name };
|
|
345
570
|
named.underlying = underlying;
|
|
571
|
+
named._target = this.target;
|
|
346
572
|
if (underlying.kind === "struct") {
|
|
347
573
|
underlying.name = decl.name;
|
|
348
574
|
underlying.methods = new Map();
|
|
@@ -507,6 +733,10 @@ export class TypeChecker {
|
|
|
507
733
|
}
|
|
508
734
|
|
|
509
735
|
_collectCssDecl(decl) {
|
|
736
|
+
if (this.target === "wasm" || this.target === "both") {
|
|
737
|
+
this.recordBlocker("css declaration");
|
|
738
|
+
this.err("css declarations are not allowed in wasm packages", decl);
|
|
739
|
+
}
|
|
510
740
|
const paramTypes = (decl.params || []).map((p) =>
|
|
511
741
|
this.resolveTypeNode(p.type, this.globals),
|
|
512
742
|
);
|
|
@@ -551,17 +781,40 @@ export class TypeChecker {
|
|
|
551
781
|
}
|
|
552
782
|
|
|
553
783
|
checkFuncDecl(decl, outer) {
|
|
784
|
+
if (
|
|
785
|
+
(this.target === "wasm" || this.target === "both") &&
|
|
786
|
+
(decl.async || decl.isAsync)
|
|
787
|
+
) {
|
|
788
|
+
this.recordBlocker("async function", decl.name);
|
|
789
|
+
this.err(
|
|
790
|
+
"async functions are not supported in wasm packages; keep async code in a js package",
|
|
791
|
+
decl,
|
|
792
|
+
);
|
|
793
|
+
}
|
|
554
794
|
const inner = new Scope(outer);
|
|
555
795
|
this._injectTypeParams(decl, inner, outer);
|
|
556
796
|
for (const p of decl.params) {
|
|
557
797
|
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
558
798
|
}
|
|
559
799
|
const returnType = this._setupFuncReturnType(decl, inner, outer);
|
|
800
|
+
const savedSharedAlloc = this._sharedAllocOk;
|
|
801
|
+
this._sharedAllocOk = decl.name === "init";
|
|
560
802
|
this._runFuncBody(decl, inner, returnType);
|
|
803
|
+
this._sharedAllocOk = savedSharedAlloc;
|
|
561
804
|
this._checkMissingReturn(decl, returnType, "function");
|
|
562
805
|
}
|
|
563
806
|
|
|
564
807
|
checkMethodDecl(decl, outer) {
|
|
808
|
+
if (
|
|
809
|
+
(this.target === "wasm" || this.target === "both") &&
|
|
810
|
+
(decl.async || decl.isAsync)
|
|
811
|
+
) {
|
|
812
|
+
this.recordBlocker("async function", decl.name);
|
|
813
|
+
this.err(
|
|
814
|
+
"async functions are not supported in wasm packages; keep async code in a js package",
|
|
815
|
+
decl,
|
|
816
|
+
);
|
|
817
|
+
}
|
|
565
818
|
const inner = new Scope(outer);
|
|
566
819
|
const recvTypeName =
|
|
567
820
|
decl.recvType.kind === "GenericTypeName"
|
|
@@ -640,6 +893,8 @@ export class TypeChecker {
|
|
|
640
893
|
}
|
|
641
894
|
|
|
642
895
|
checkVarDecl(decl, scope) {
|
|
896
|
+
const savedSharedAlloc = this._sharedAllocOk;
|
|
897
|
+
this._sharedAllocOk = scope === this.globals;
|
|
643
898
|
for (const spec of decl.decls) {
|
|
644
899
|
let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
|
|
645
900
|
if (spec.value) {
|
|
@@ -653,6 +908,7 @@ export class TypeChecker {
|
|
|
653
908
|
if (!type) type = ANY;
|
|
654
909
|
for (const name of spec.names) scope.defineLocal(name, type);
|
|
655
910
|
}
|
|
911
|
+
this._sharedAllocOk = savedSharedAlloc;
|
|
656
912
|
}
|
|
657
913
|
|
|
658
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
|
};
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
INT,
|
|
10
10
|
STRING,
|
|
11
11
|
UINT8,
|
|
12
|
+
UINT32,
|
|
13
|
+
UINT64,
|
|
12
14
|
VOID,
|
|
13
15
|
} from "../types.js";
|
|
14
16
|
|
|
@@ -428,6 +430,21 @@ export function setupCoreGlobals(globals, types) {
|
|
|
428
430
|
},
|
|
429
431
|
});
|
|
430
432
|
|
|
433
|
+
globals.define("bits", {
|
|
434
|
+
kind: "namespace",
|
|
435
|
+
name: "bits",
|
|
436
|
+
members: {
|
|
437
|
+
LeadingZeros32: { kind: "func", params: [UINT32], returns: [INT] },
|
|
438
|
+
TrailingZeros32: { kind: "func", params: [UINT32], returns: [INT] },
|
|
439
|
+
OnesCount32: { kind: "func", params: [UINT32], returns: [INT] },
|
|
440
|
+
RotateLeft32: { kind: "func", params: [UINT32, INT], returns: [UINT32] },
|
|
441
|
+
LeadingZeros64: { kind: "func", params: [UINT64], returns: [INT] },
|
|
442
|
+
TrailingZeros64: { kind: "func", params: [UINT64], returns: [INT] },
|
|
443
|
+
OnesCount64: { kind: "func", params: [UINT64], returns: [INT] },
|
|
444
|
+
RotateLeft64: { kind: "func", params: [UINT64, INT], returns: [UINT64] },
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
431
448
|
globals.define("errors", {
|
|
432
449
|
kind: "namespace",
|
|
433
450
|
name: "errors",
|
|
@@ -523,6 +540,14 @@ export function setupCoreGlobals(globals, types) {
|
|
|
523
540
|
},
|
|
524
541
|
});
|
|
525
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
|
+
});
|
|
526
551
|
globals.define("slices", {
|
|
527
552
|
kind: "namespace",
|
|
528
553
|
name: "slices",
|
|
@@ -541,26 +566,16 @@ export function setupCoreGlobals(globals, types) {
|
|
|
541
566
|
Min: { kind: "func", params: [ANY], returns: [ANY] },
|
|
542
567
|
MaxFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
|
|
543
568
|
MinFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
|
|
544
|
-
Clone:
|
|
545
|
-
Compact:
|
|
546
|
-
CompactFunc:
|
|
569
|
+
Clone: sameSlice([ANY]),
|
|
570
|
+
Compact: sameSlice([ANY]),
|
|
571
|
+
CompactFunc: sameSlice([ANY, ANY]),
|
|
547
572
|
Concat: { kind: "func", params: [ANY], returns: [ANY], variadic: true },
|
|
548
|
-
Delete:
|
|
549
|
-
DeleteFunc:
|
|
550
|
-
Insert: {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
variadic: true,
|
|
555
|
-
},
|
|
556
|
-
Replace: {
|
|
557
|
-
kind: "func",
|
|
558
|
-
params: [ANY, INT, INT, ANY],
|
|
559
|
-
returns: [ANY],
|
|
560
|
-
variadic: true,
|
|
561
|
-
},
|
|
562
|
-
Grow: { kind: "func", params: [ANY, INT], returns: [ANY] },
|
|
563
|
-
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]),
|
|
564
579
|
},
|
|
565
580
|
});
|
|
566
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
|
}
|