gofront 1.3.5 → 1.3.6
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/package.json +1 -1
- package/src/cli-core.js +118 -13
- package/src/codegen/expressions.js +408 -89
- package/src/codegen/index.js +207 -25
- package/src/codegen/runtime.js +22 -1
- package/src/codegen/statements.js +170 -45
- package/src/codegen/stdlib/builder.js +7 -2
- package/src/codegen/stdlib/fmt.js +2 -2
- package/src/codegen/stdlib/io.js +1 -1
- package/src/dev-server.js +22 -10
- package/src/index.js +8 -2
- package/src/parser/declarations.js +2 -1
- package/src/pwa.js +3 -1
- package/src/typechecker/assignability.js +50 -4
- package/src/typechecker/expressions.js +13 -1
- package/src/typechecker/index.js +46 -2
- package/src/typechecker/resolve.js +2 -0
- package/src/typechecker/statements.js +10 -1
- package/src/typechecker/stdlib/web.js +54 -26
- package/src/typechecker/types.js +3 -2
- package/src/vendor.js +12 -7
|
@@ -28,12 +28,17 @@ const BUILDER_COMMON = {
|
|
|
28
28
|
export const builderMethods = {
|
|
29
29
|
_genBuilderCall(typeName, method, expr) {
|
|
30
30
|
const recv = expr.func.expr;
|
|
31
|
-
const
|
|
32
|
-
const base = `(${recvJs}?.value ?? ${recvJs})`;
|
|
31
|
+
const base = this._unboxWriter(this.genExpr(recv));
|
|
33
32
|
const args = expr.args.map((a) => this.genExpr(a));
|
|
34
33
|
const isStr = typeName === "strings.Builder";
|
|
35
34
|
const table = isStr ? BUILDER_STR : BUILDER_BYTES;
|
|
36
35
|
const gen = table[method] ?? BUILDER_COMMON[method];
|
|
37
36
|
return gen ? gen(base, args) : undefined;
|
|
38
37
|
},
|
|
38
|
+
|
|
39
|
+
// Accepts a Builder/Buffer or a pointer box around one; evaluates `js` once.
|
|
40
|
+
_unboxWriter(js) {
|
|
41
|
+
if (/^[\w$.]+$/.test(js)) return `(${js}?.value ?? ${js})`;
|
|
42
|
+
return `((__w) => __w?.value ?? __w)(${js})`;
|
|
43
|
+
},
|
|
39
44
|
};
|
|
@@ -80,12 +80,12 @@ export const fmtMethods = {
|
|
|
80
80
|
const sprintfCall = this._buildFprintSprintfCall(fn, rest.length, restJs);
|
|
81
81
|
if (targetTypeName === "strings.Builder") {
|
|
82
82
|
const w = this.genExpr(writerArg);
|
|
83
|
-
const base =
|
|
83
|
+
const base = this._unboxWriter(w);
|
|
84
84
|
return `(${base}._buf += ${sprintfCall})`;
|
|
85
85
|
}
|
|
86
86
|
if (targetTypeName === "bytes.Buffer") {
|
|
87
87
|
const w = this.genExpr(writerArg);
|
|
88
|
-
const base =
|
|
88
|
+
const base = this._unboxWriter(w);
|
|
89
89
|
return `((__b,__s)=>{ __b._buf.push(...new TextEncoder().encode(__s)); })(${base}, ${sprintfCall})`;
|
|
90
90
|
}
|
|
91
91
|
// Generic io.Writer fallback — call .WriteString if available
|
package/src/codegen/stdlib/io.js
CHANGED
|
@@ -14,7 +14,7 @@ export const ioMethods = {
|
|
|
14
14
|
(writerType?.kind === "pointer" ? writerType.base?.name : null);
|
|
15
15
|
const w = this.genExpr(writerArg);
|
|
16
16
|
const s = this.genExpr(expr.args[1]);
|
|
17
|
-
const base =
|
|
17
|
+
const base = this._unboxWriter(w);
|
|
18
18
|
if (typeName === "strings.Builder") {
|
|
19
19
|
return `((b,s) => { b._buf += s; return [s.length, null]; })(${base}, ${s})`;
|
|
20
20
|
}
|
package/src/dev-server.js
CHANGED
|
@@ -140,13 +140,15 @@ function isInsideDir(root, target) {
|
|
|
140
140
|
return rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
function handleSseRequest(req, res, clients) {
|
|
143
|
+
function handleSseRequest(req, res, clients, lastError = null) {
|
|
144
144
|
res.writeHead(200, {
|
|
145
145
|
"Content-Type": "text/event-stream",
|
|
146
146
|
"Cache-Control": "no-cache",
|
|
147
147
|
Connection: "keep-alive",
|
|
148
148
|
});
|
|
149
149
|
res.write(": connected\n\n");
|
|
150
|
+
if (lastError)
|
|
151
|
+
res.write(`event: build-error\ndata: ${JSON.stringify(lastError)}\n\n`);
|
|
150
152
|
clients.add(res);
|
|
151
153
|
req.on?.("close", () => clients.delete(res));
|
|
152
154
|
}
|
|
@@ -216,7 +218,7 @@ export function handleDevRequest(
|
|
|
216
218
|
options = {},
|
|
217
219
|
) {
|
|
218
220
|
if (req.url === "/_gofront/events") {
|
|
219
|
-
handleSseRequest(req, res, clients);
|
|
221
|
+
handleSseRequest(req, res, clients, options.getLastError?.() ?? null);
|
|
220
222
|
return;
|
|
221
223
|
}
|
|
222
224
|
|
|
@@ -312,15 +314,19 @@ function closeServer(server, clients, timer) {
|
|
|
312
314
|
export function createDevServer(serveDir, port = 3000, options = {}) {
|
|
313
315
|
const clients = new Set();
|
|
314
316
|
const heartbeatMs = options.heartbeatInterval ?? 15000;
|
|
317
|
+
const host = options.host ?? "localhost";
|
|
318
|
+
// Kept so browsers that connect (or reload) after a failed build still see the overlay.
|
|
319
|
+
let lastError = null;
|
|
320
|
+
const requestOptions = { ...options, getLastError: () => lastError };
|
|
315
321
|
|
|
316
322
|
const server = createServer((req, res) => {
|
|
317
|
-
handleDevRequest(req, res, serveDir, clients,
|
|
323
|
+
handleDevRequest(req, res, serveDir, clients, requestOptions);
|
|
318
324
|
});
|
|
319
325
|
|
|
320
326
|
server.on("error", (err) => handleServerError(err, port));
|
|
321
|
-
server.listen(port, () => {
|
|
327
|
+
server.listen(port, host, () => {
|
|
322
328
|
const actualPort = server.address()?.port ?? port;
|
|
323
|
-
if (port !== 0) {
|
|
329
|
+
if (port !== 0 && !options.silent) {
|
|
324
330
|
console.error(`gofront: dev server → http://localhost:${actualPort}`);
|
|
325
331
|
}
|
|
326
332
|
});
|
|
@@ -329,17 +335,23 @@ export function createDevServer(serveDir, port = 3000, options = {}) {
|
|
|
329
335
|
heartbeatMs > 0 ? setInterval(() => sendPings(clients), heartbeatMs) : null;
|
|
330
336
|
heartbeatTimer?.unref?.();
|
|
331
337
|
|
|
332
|
-
const
|
|
338
|
+
const broadcastNotify = createNotify(clients);
|
|
339
|
+
const notify = (arg) => {
|
|
340
|
+
if (!arg?.type) lastError = null;
|
|
341
|
+
broadcastNotify(arg);
|
|
342
|
+
};
|
|
343
|
+
const notifyError = (err) => {
|
|
344
|
+
lastError = buildErrorPayload(err);
|
|
345
|
+
broadcastToClients(clients, "build-error", lastError);
|
|
346
|
+
};
|
|
333
347
|
|
|
334
348
|
return {
|
|
335
349
|
broadcast: (event, data) => broadcastToClients(clients, event, data),
|
|
336
350
|
notify,
|
|
337
351
|
notifyCss: (file) =>
|
|
338
352
|
broadcastToClients(clients, "css-update", { type: "css-update", file }),
|
|
339
|
-
notifyError
|
|
340
|
-
|
|
341
|
-
notifyBuildError: (err) =>
|
|
342
|
-
broadcastToClients(clients, "build-error", buildErrorPayload(err)),
|
|
353
|
+
notifyError,
|
|
354
|
+
notifyBuildError: notifyError,
|
|
343
355
|
ping: () => sendPings(clients),
|
|
344
356
|
clients,
|
|
345
357
|
server,
|
package/src/index.js
CHANGED
|
@@ -162,8 +162,14 @@ if (args[0] === "dev") {
|
|
|
162
162
|
const devOptions = parseDevArgs(args.slice(1));
|
|
163
163
|
try {
|
|
164
164
|
const dev = await handleDev(devOptions.targetDir, devOptions);
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
if (dev.initialError) {
|
|
166
|
+
console.error("gofront: ERROR");
|
|
167
|
+
for (const line of dev.initialError.message.split("\n"))
|
|
168
|
+
console.error(` ${line}`);
|
|
169
|
+
} else {
|
|
170
|
+
console.error(`gofront: OK — wrote ${dev.outputFile}`);
|
|
171
|
+
}
|
|
172
|
+
console.error(`gofront: dev server running → http://localhost:${dev.port}`);
|
|
167
173
|
console.error(`gofront: watching ${dev.srcDir} for changes...`);
|
|
168
174
|
|
|
169
175
|
const shutdown = async () => {
|
|
@@ -29,7 +29,7 @@ export const declarationParseMethods = {
|
|
|
29
29
|
this.check(T.IDENT) && !this.isReceiverTerminator(this.peek2())
|
|
30
30
|
? this.advance().value
|
|
31
31
|
: "_";
|
|
32
|
-
this.match(T.STAR);
|
|
32
|
+
const recvPointer = Boolean(this.match(T.STAR));
|
|
33
33
|
const recvType = this.parseTypeName();
|
|
34
34
|
this.expect(T.RPAREN);
|
|
35
35
|
const name = this.expect(T.IDENT).value;
|
|
@@ -40,6 +40,7 @@ export const declarationParseMethods = {
|
|
|
40
40
|
kind: "MethodDecl",
|
|
41
41
|
recvName,
|
|
42
42
|
recvType,
|
|
43
|
+
recvPointer,
|
|
43
44
|
name,
|
|
44
45
|
params,
|
|
45
46
|
returnType,
|
package/src/pwa.js
CHANGED
|
@@ -86,7 +86,9 @@ self.addEventListener("fetch", (event) => {
|
|
|
86
86
|
return response;
|
|
87
87
|
}).catch(() => {
|
|
88
88
|
if (event.request.headers?.get?.("accept")?.includes("text/html")) {
|
|
89
|
-
return caches
|
|
89
|
+
return caches
|
|
90
|
+
.match("./")
|
|
91
|
+
.then((r) => r || caches.match("./index.html"));
|
|
90
92
|
}
|
|
91
93
|
});
|
|
92
94
|
}),
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
isNumeric,
|
|
15
15
|
isPointer,
|
|
16
16
|
isString,
|
|
17
|
+
isTypedArraySlice,
|
|
17
18
|
isUntyped,
|
|
18
19
|
LOG_OPS,
|
|
19
20
|
SIZED_FLOAT_NAMES,
|
|
@@ -157,6 +158,7 @@ export const assignabilityMethods = {
|
|
|
157
158
|
if (!target || !source) return;
|
|
158
159
|
target = this.resolveType(target);
|
|
159
160
|
source = this.resolveType(source);
|
|
161
|
+
this._markIfaceBox(target, source, node);
|
|
160
162
|
if (this._assertAssignableEarlyReturn(target, source)) return;
|
|
161
163
|
if (isPointer(target) && isPointer(source)) {
|
|
162
164
|
this.assertAssignable(
|
|
@@ -170,10 +172,34 @@ export const assignabilityMethods = {
|
|
|
170
172
|
if (this._isNumericCoercible(target, source)) return;
|
|
171
173
|
if (this._checkArrayAssignable(target, source, node)) return;
|
|
172
174
|
if (this._checkTypedArrayAssignable(target, source, node)) return;
|
|
175
|
+
if (this._checkFuncAssignable(target, source, node)) return;
|
|
173
176
|
if (typeStr(target) !== typeStr(source))
|
|
174
177
|
this._assertAssignableTypeMismatch(target, source, node);
|
|
175
178
|
},
|
|
176
179
|
|
|
180
|
+
// Tags struct values / struct pointers flowing into an interface so codegen can
|
|
181
|
+
// tell `T` from `*T` at runtime (both compile to the same class instance).
|
|
182
|
+
_markIfaceBox(target, source, node) {
|
|
183
|
+
if (this._skipIfaceMark) return;
|
|
184
|
+
if (!node || typeof node !== "object" || !target || !source) return;
|
|
185
|
+
if (target.kind === "typeParam") return;
|
|
186
|
+
const tBase = this.resolveType(
|
|
187
|
+
target.kind === "named" ? target.underlying : target,
|
|
188
|
+
);
|
|
189
|
+
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
|
+
const sBase = this.resolveType(
|
|
196
|
+
source.kind === "named" ? source.underlying : source,
|
|
197
|
+
);
|
|
198
|
+
if (sBase?.kind === "struct") node._ifaceBox = "value";
|
|
199
|
+
else if (sBase?.kind === "pointer" && structOf(sBase.base))
|
|
200
|
+
node._ifaceBox = "ptr";
|
|
201
|
+
},
|
|
202
|
+
|
|
177
203
|
_assertAssignableEarlyReturn(target, source) {
|
|
178
204
|
return (
|
|
179
205
|
target?.kind === "typeParam" ||
|
|
@@ -211,9 +237,13 @@ export const assignabilityMethods = {
|
|
|
211
237
|
|
|
212
238
|
_isNumericCoercible(target, source) {
|
|
213
239
|
if (target.kind !== "basic" || source.kind !== "basic") return false;
|
|
240
|
+
const pair = `${target.name}:${source.name}`;
|
|
214
241
|
return (
|
|
215
|
-
|
|
216
|
-
|
|
242
|
+
pair === "float64:int" ||
|
|
243
|
+
pair === "int:float64" ||
|
|
244
|
+
// rune was historically int in GoFront; keep rune/int interchangeable.
|
|
245
|
+
pair === "int:int32" ||
|
|
246
|
+
pair === "int32:int"
|
|
217
247
|
);
|
|
218
248
|
},
|
|
219
249
|
|
|
@@ -258,8 +288,9 @@ export const assignabilityMethods = {
|
|
|
258
288
|
const taElem = targetTa
|
|
259
289
|
? (target.underlying?._elemType ?? target._elemType)
|
|
260
290
|
: (source.underlying?._elemType ?? source._elemType);
|
|
261
|
-
const
|
|
262
|
-
if (typeStr(taElem) === typeStr(
|
|
291
|
+
const slice = targetSlice ?? sourceSlice;
|
|
292
|
+
if (isTypedArraySlice(slice) && typeStr(taElem) === typeStr(slice.elem))
|
|
293
|
+
return true;
|
|
263
294
|
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
264
295
|
return true;
|
|
265
296
|
}
|
|
@@ -275,6 +306,21 @@ export const assignabilityMethods = {
|
|
|
275
306
|
return false;
|
|
276
307
|
},
|
|
277
308
|
|
|
309
|
+
_checkFuncAssignable(target, source, node) {
|
|
310
|
+
const tFunc = target.kind === "named" ? target.underlying : target;
|
|
311
|
+
const sFunc = source.kind === "named" ? source.underlying : source;
|
|
312
|
+
if (tFunc?.kind === "func" && sFunc?.kind === "func") {
|
|
313
|
+
if (!this._implementsMethod(tFunc, sFunc)) {
|
|
314
|
+
this.err(
|
|
315
|
+
`Cannot assign ${typeStr(source)} to ${typeStr(target)}`,
|
|
316
|
+
node,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
return false;
|
|
322
|
+
},
|
|
323
|
+
|
|
278
324
|
// ── Interface satisfaction ────────────────────────────────────────
|
|
279
325
|
|
|
280
326
|
_sigParamsMatch(reqList, actList) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isNumeric,
|
|
16
16
|
isString,
|
|
17
17
|
NIL,
|
|
18
|
+
RUNE,
|
|
18
19
|
Scope,
|
|
19
20
|
STRING,
|
|
20
21
|
TAINTED_ANY,
|
|
@@ -40,6 +41,17 @@ const BUILTIN_CHECK = {
|
|
|
40
41
|
s.err(`cannot append to array (type ${typeStr(st)})`, e);
|
|
41
42
|
return st;
|
|
42
43
|
}
|
|
44
|
+
const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
|
|
45
|
+
if (sb?.kind === "slice") {
|
|
46
|
+
for (let i = 1; i < e.args.length; i++) {
|
|
47
|
+
if (!e.args[i]._spread && at[i])
|
|
48
|
+
s._markIfaceBox(
|
|
49
|
+
s.resolveType(sb.elem),
|
|
50
|
+
s.resolveType(at[i]),
|
|
51
|
+
e.args[i],
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
43
55
|
return st;
|
|
44
56
|
},
|
|
45
57
|
copy: () => INT,
|
|
@@ -379,7 +391,7 @@ export const expressionCheckMethods = {
|
|
|
379
391
|
},
|
|
380
392
|
|
|
381
393
|
_rangeCollTypeTuple(resolved, collType) {
|
|
382
|
-
if (isString(resolved)) return { kind: "tuple", types: [INT,
|
|
394
|
+
if (isString(resolved)) return { kind: "tuple", types: [INT, RUNE] };
|
|
383
395
|
if (resolved?.kind === "slice" || resolved?.kind === "array")
|
|
384
396
|
return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
|
|
385
397
|
if (resolved?.kind === "map")
|
package/src/typechecker/index.js
CHANGED
|
@@ -185,12 +185,55 @@ export class TypeChecker {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
+
const all = [];
|
|
188
189
|
for (const p of programs) {
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
for (const d of p.decls) if (d.kind === "TypeDecl") all.push({ p, d });
|
|
191
|
+
}
|
|
192
|
+
// Collect in dependency order so aliases, generics, embedded and by-value
|
|
193
|
+
// field types declared later (or in other files) are ready when needed.
|
|
194
|
+
let pending = all;
|
|
195
|
+
while (pending.length > 0) {
|
|
196
|
+
const pendingNames = new Map(pending.map((x) => [x.d.name, x.d]));
|
|
197
|
+
const ready = pending.filter(
|
|
198
|
+
(x) => !this._typeDeclBlocked(x.d, pendingNames),
|
|
199
|
+
);
|
|
200
|
+
const batch = ready.length > 0 ? ready : pending;
|
|
201
|
+
for (const { p, d } of batch) {
|
|
202
|
+
this._setCurrentFile(p);
|
|
203
|
+
this.collectType(d);
|
|
204
|
+
}
|
|
205
|
+
pending =
|
|
206
|
+
ready.length > 0 ? pending.filter((x) => !ready.includes(x)) : [];
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
209
|
|
|
210
|
+
_typeDeclBlocked(decl, pendingNames) {
|
|
211
|
+
const visit = (node, indirect) => {
|
|
212
|
+
if (!node || typeof node !== "object") return false;
|
|
213
|
+
if (Array.isArray(node)) return node.some((n) => visit(n, indirect));
|
|
214
|
+
if (
|
|
215
|
+
(node.kind === "TypeName" || node.kind === "GenericTypeName") &&
|
|
216
|
+
node.name !== decl.name
|
|
217
|
+
) {
|
|
218
|
+
const dep = pendingNames.get(node.name);
|
|
219
|
+
if (dep && (!indirect || dep.isAlias || dep.typeParams)) return true;
|
|
220
|
+
}
|
|
221
|
+
const nextIndirect =
|
|
222
|
+
indirect ||
|
|
223
|
+
[
|
|
224
|
+
"PointerType",
|
|
225
|
+
"SliceType",
|
|
226
|
+
"MapType",
|
|
227
|
+
"FuncType",
|
|
228
|
+
"ChanType",
|
|
229
|
+
].includes(node.kind);
|
|
230
|
+
return Object.keys(node).some(
|
|
231
|
+
(k) => !k.startsWith("_") && visit(node[k], nextIndirect),
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
return visit(decl.type, false);
|
|
235
|
+
}
|
|
236
|
+
|
|
194
237
|
_collectFuncsPass(programs) {
|
|
195
238
|
for (const p of programs) {
|
|
196
239
|
this._setCurrentFile(p);
|
|
@@ -390,6 +433,7 @@ export class TypeChecker {
|
|
|
390
433
|
}
|
|
391
434
|
|
|
392
435
|
_attachMethod(decl, funcType) {
|
|
436
|
+
funcType._ptrRecv = Boolean(decl.recvPointer);
|
|
393
437
|
const recvNamedType = this.types.get(decl.recvType.name);
|
|
394
438
|
if (recvNamedType?._generic)
|
|
395
439
|
this._attachGenericMethod(recvNamedType, decl, funcType);
|
|
@@ -411,6 +411,8 @@ export const resolveMethods = {
|
|
|
411
411
|
_fieldTypeInterface(base, baseType, field, node) {
|
|
412
412
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
413
413
|
if (base.fields?.has(field)) return base.fields.get(field);
|
|
414
|
+
if (base._excluded?.has(field))
|
|
415
|
+
return this.err(`No member '${field}' on ${typeStr(baseType)}`, node);
|
|
414
416
|
if (base._isOpen) return ANY;
|
|
415
417
|
return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
|
|
416
418
|
},
|
|
@@ -148,8 +148,17 @@ export const statementCheckMethods = {
|
|
|
148
148
|
for (let i = 0; i < lhsTypes.length; i++) {
|
|
149
149
|
if (stmt.lhs[i]?.kind === "Ident" && stmt.lhs[i]?.name === "_") continue;
|
|
150
150
|
const r = rhsFlat[i] ?? ANY;
|
|
151
|
-
if (
|
|
151
|
+
if (stmt.rhs.length === lhsTypes.length && lhsTypes[i] && r)
|
|
152
|
+
this._markIfaceBox(
|
|
153
|
+
this.resolveType(lhsTypes[i]),
|
|
154
|
+
this.resolveType(r),
|
|
155
|
+
stmt.rhs[i],
|
|
156
|
+
);
|
|
157
|
+
if (!isAny(lhsTypes[i]) && !isAny(r)) {
|
|
158
|
+
this._skipIfaceMark = true;
|
|
152
159
|
this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
|
|
160
|
+
this._skipIfaceMark = false;
|
|
161
|
+
}
|
|
153
162
|
}
|
|
154
163
|
},
|
|
155
164
|
|
|
@@ -23,6 +23,25 @@ const func = (params, returns = [VOID], variadic = false) => ({
|
|
|
23
23
|
variadic,
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
// Last parameter is optional (modelled as variadic).
|
|
27
|
+
const optLast = (params, returns = [VOID]) => func(params, returns, true);
|
|
28
|
+
|
|
29
|
+
const WEBGL2_ONLY_CONSTANTS = new Set([
|
|
30
|
+
"UNIFORM_BUFFER",
|
|
31
|
+
"HALF_FLOAT",
|
|
32
|
+
"RGBA8",
|
|
33
|
+
"RGBA32F",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const WEBGL2_ONLY_METHODS = new Set([
|
|
37
|
+
"VertexAttribIPointer",
|
|
38
|
+
"DrawArraysInstanced",
|
|
39
|
+
"DrawElementsInstanced",
|
|
40
|
+
"CreateVertexArray",
|
|
41
|
+
"BindVertexArray",
|
|
42
|
+
"DeleteVertexArray",
|
|
43
|
+
]);
|
|
44
|
+
|
|
26
45
|
const WEBGL_CONSTANTS_LIST = {
|
|
27
46
|
ARRAY_BUFFER: 0x8892,
|
|
28
47
|
ELEMENT_ARRAY_BUFFER: 0x8893,
|
|
@@ -113,7 +132,7 @@ function buildArrayBufferType() {
|
|
|
113
132
|
methods,
|
|
114
133
|
};
|
|
115
134
|
named.underlying = iface;
|
|
116
|
-
methods.set("slice",
|
|
135
|
+
methods.set("slice", optLast([INT, INT], [named]));
|
|
117
136
|
return named;
|
|
118
137
|
}
|
|
119
138
|
|
|
@@ -126,20 +145,20 @@ function buildDataViewType(arrayBufferType) {
|
|
|
126
145
|
const methods = new Map([
|
|
127
146
|
["getInt8", func([INT], [INT8])],
|
|
128
147
|
["getUint8", func([INT], [UINT8])],
|
|
129
|
-
["getInt16",
|
|
130
|
-
["getUint16",
|
|
131
|
-
["getInt32",
|
|
132
|
-
["getUint32",
|
|
133
|
-
["getFloat32",
|
|
134
|
-
["getFloat64",
|
|
148
|
+
["getInt16", optLast([INT, BOOL], [INT16])],
|
|
149
|
+
["getUint16", optLast([INT, BOOL], [UINT16])],
|
|
150
|
+
["getInt32", optLast([INT, BOOL], [INT32])],
|
|
151
|
+
["getUint32", optLast([INT, BOOL], [UINT32])],
|
|
152
|
+
["getFloat32", optLast([INT, BOOL], [FLOAT32])],
|
|
153
|
+
["getFloat64", optLast([INT, BOOL], [FLOAT64])],
|
|
135
154
|
["setInt8", func([INT, INT8])],
|
|
136
155
|
["setUint8", func([INT, UINT8])],
|
|
137
|
-
["setInt16",
|
|
138
|
-
["setUint16",
|
|
139
|
-
["setInt32",
|
|
140
|
-
["setUint32",
|
|
141
|
-
["setFloat32",
|
|
142
|
-
["setFloat64",
|
|
156
|
+
["setInt16", optLast([INT, INT16, BOOL])],
|
|
157
|
+
["setUint16", optLast([INT, UINT16, BOOL])],
|
|
158
|
+
["setInt32", optLast([INT, INT32, BOOL])],
|
|
159
|
+
["setUint32", optLast([INT, UINT32, BOOL])],
|
|
160
|
+
["setFloat32", optLast([INT, FLOAT32, BOOL])],
|
|
161
|
+
["setFloat64", optLast([INT, FLOAT64, BOOL])],
|
|
143
162
|
]);
|
|
144
163
|
const named = { kind: "named", name: "DataView", underlying: null };
|
|
145
164
|
named.underlying = { kind: "interface", name: "DataView", fields, methods };
|
|
@@ -164,15 +183,16 @@ function buildTypedArrayType(name, elemType, arrayBufferType) {
|
|
|
164
183
|
_isTypedArray: true,
|
|
165
184
|
};
|
|
166
185
|
named.underlying = iface;
|
|
167
|
-
methods.set("subarray",
|
|
168
|
-
methods.set("slice",
|
|
169
|
-
methods.set("set",
|
|
186
|
+
methods.set("subarray", optLast([INT, INT], [named]));
|
|
187
|
+
methods.set("slice", optLast([INT, INT], [named]));
|
|
188
|
+
methods.set("set", optLast([ANY, INT], [VOID]));
|
|
170
189
|
return named;
|
|
171
190
|
}
|
|
172
191
|
|
|
173
|
-
function createWebGLMethods() {
|
|
192
|
+
function createWebGLMethods(isWebGL2) {
|
|
174
193
|
const methods = new Map();
|
|
175
194
|
const add = (name, fn) => {
|
|
195
|
+
if (!isWebGL2 && WEBGL2_ONLY_METHODS.has(name)) return;
|
|
176
196
|
methods.set(name, fn);
|
|
177
197
|
const camel = name[0].toLowerCase() + name.slice(1);
|
|
178
198
|
if (camel !== name) methods.set(camel, fn);
|
|
@@ -296,7 +316,7 @@ function createWebGPUMethods() {
|
|
|
296
316
|
addDev("CreateShaderModule", func([ANY], [ANY]));
|
|
297
317
|
addDev("CreateRenderPipeline", func([ANY], [ANY]));
|
|
298
318
|
addDev("CreateComputePipeline", func([ANY], [ANY]));
|
|
299
|
-
addDev("CreateCommandEncoder",
|
|
319
|
+
addDev("CreateCommandEncoder", optLast([ANY], [ANY]));
|
|
300
320
|
addDev("CreateBindGroup", func([ANY], [ANY]));
|
|
301
321
|
addDev("CreateBindGroupLayout", func([ANY], [ANY]));
|
|
302
322
|
addDev("CreatePipelineLayout", func([ANY], [ANY]));
|
|
@@ -307,7 +327,7 @@ function createWebGPUMethods() {
|
|
|
307
327
|
const camel = name[0].toLowerCase() + name.slice(1);
|
|
308
328
|
if (camel !== name) adapterMethods.set(camel, fn);
|
|
309
329
|
};
|
|
310
|
-
addAdapt("RequestDevice",
|
|
330
|
+
addAdapt("RequestDevice", optLast([ANY], [ANY]));
|
|
311
331
|
|
|
312
332
|
return { queueMethods, deviceMethods, adapterMethods };
|
|
313
333
|
}
|
|
@@ -339,21 +359,29 @@ export function setupWebGlobals(globals, types) {
|
|
|
339
359
|
}
|
|
340
360
|
|
|
341
361
|
// WebGL constants map
|
|
342
|
-
const
|
|
362
|
+
const constFields2 = new Map();
|
|
343
363
|
for (const [k] of Object.entries(WEBGL_CONSTANTS_LIST)) {
|
|
344
|
-
|
|
364
|
+
constFields2.set(k, INT);
|
|
365
|
+
}
|
|
366
|
+
const constFields1 = new Map(
|
|
367
|
+
[...constFields2].filter(([k]) => !WEBGL2_ONLY_CONSTANTS.has(k)),
|
|
368
|
+
);
|
|
369
|
+
const webgl2Only = new Set(WEBGL2_ONLY_CONSTANTS);
|
|
370
|
+
for (const m of WEBGL2_ONLY_METHODS) {
|
|
371
|
+
webgl2Only.add(m);
|
|
372
|
+
webgl2Only.add(m[0].toLowerCase() + m.slice(1));
|
|
345
373
|
}
|
|
346
374
|
|
|
347
|
-
const webglMethods = createWebGLMethods();
|
|
348
375
|
const webglContext = {
|
|
349
376
|
kind: "named",
|
|
350
377
|
name: "WebGLRenderingContext",
|
|
351
378
|
underlying: {
|
|
352
379
|
kind: "interface",
|
|
353
380
|
name: "WebGLRenderingContext",
|
|
354
|
-
fields:
|
|
355
|
-
methods:
|
|
381
|
+
fields: constFields1,
|
|
382
|
+
methods: createWebGLMethods(false),
|
|
356
383
|
_isOpen: true,
|
|
384
|
+
_excluded: webgl2Only,
|
|
357
385
|
},
|
|
358
386
|
};
|
|
359
387
|
const webgl2Context = {
|
|
@@ -362,8 +390,8 @@ export function setupWebGlobals(globals, types) {
|
|
|
362
390
|
underlying: {
|
|
363
391
|
kind: "interface",
|
|
364
392
|
name: "WebGL2RenderingContext",
|
|
365
|
-
fields:
|
|
366
|
-
methods:
|
|
393
|
+
fields: constFields2,
|
|
394
|
+
methods: createWebGLMethods(true),
|
|
367
395
|
_isOpen: true,
|
|
368
396
|
},
|
|
369
397
|
};
|
package/src/typechecker/types.js
CHANGED
|
@@ -55,7 +55,7 @@ export const UINT16 = { kind: "basic", name: "uint16" };
|
|
|
55
55
|
export const UINT32 = { kind: "basic", name: "uint32" };
|
|
56
56
|
export const UINT64 = { kind: "basic", name: "uint64" };
|
|
57
57
|
export const UINTPTR = { kind: "basic", name: "uintptr" };
|
|
58
|
-
export const RUNE =
|
|
58
|
+
export const RUNE = INT32;
|
|
59
59
|
export const FLOAT32 = { kind: "basic", name: "float32" };
|
|
60
60
|
export const FLOAT64 = { kind: "basic", name: "float64" };
|
|
61
61
|
export const STRING = { kind: "basic", name: "string" };
|
|
@@ -149,9 +149,9 @@ export const isString = makeBasicPredicate("string");
|
|
|
149
149
|
export const isBool = makeBasicPredicate("bool");
|
|
150
150
|
|
|
151
151
|
// ── TypedArray mappings ──────────────────────────────────────
|
|
152
|
+
// []float64 deliberately stays a plain Array for JSON / JS-library interop.
|
|
152
153
|
export const TYPED_ARRAY_CONSTRUCTORS = {
|
|
153
154
|
float32: "Float32Array",
|
|
154
|
-
float64: "Float64Array",
|
|
155
155
|
uint8: "Uint8Array",
|
|
156
156
|
byte: "Uint8Array",
|
|
157
157
|
int8: "Int8Array",
|
|
@@ -159,6 +159,7 @@ export const TYPED_ARRAY_CONSTRUCTORS = {
|
|
|
159
159
|
int16: "Int16Array",
|
|
160
160
|
uint32: "Uint32Array",
|
|
161
161
|
int32: "Int32Array",
|
|
162
|
+
rune: "Int32Array",
|
|
162
163
|
};
|
|
163
164
|
|
|
164
165
|
export function typedArrayConstructorForElem(elemType) {
|
package/src/vendor.js
CHANGED
|
@@ -227,9 +227,12 @@ export async function loadNodePolyfillsPlugin(projectRoot, customPlugin) {
|
|
|
227
227
|
if (!info) return null;
|
|
228
228
|
try {
|
|
229
229
|
const mod = await import(pathToFileURL(info.path).href);
|
|
230
|
-
const fn =
|
|
231
|
-
mod.nodePolyfills
|
|
232
|
-
|
|
230
|
+
const fn = [
|
|
231
|
+
mod.nodePolyfills,
|
|
232
|
+
mod.default?.nodePolyfills,
|
|
233
|
+
mod.default,
|
|
234
|
+
].find((f) => typeof f === "function");
|
|
235
|
+
return fn ? fn() : null;
|
|
233
236
|
} catch {
|
|
234
237
|
return null;
|
|
235
238
|
}
|
|
@@ -339,10 +342,12 @@ export async function bundleVendor(projectDir = ".", options = {}) {
|
|
|
339
342
|
options.nodePolyfillsPlugin ??
|
|
340
343
|
bundlerInfo.nodePolyfillsPlugin ??
|
|
341
344
|
(options.nodePolyfills === false ? null : undefined);
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
345
|
+
// The polyfill plugin is a Rolldown plugin; esbuild cannot use it.
|
|
346
|
+
const supportsPlugins =
|
|
347
|
+
typeof bundlerInfo.bundle === "function" || bundlerInfo.name === "rolldown";
|
|
348
|
+
const polyfillPlugin = supportsPlugins
|
|
349
|
+
? await loadNodePolyfillsPlugin(projectRoot, customPlugin)
|
|
350
|
+
: null;
|
|
346
351
|
|
|
347
352
|
if (typeof bundlerInfo.bundle === "function") {
|
|
348
353
|
try {
|