@ttsc/wasm 0.18.4 → 0.19.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 +10 -4
- package/dist/go.mod +1 -0
- package/dist/ttsc.wasm +0 -0
- package/go.mod +1 -0
- package/host/api.go +1 -1
- package/host/host.go +15 -108
- package/host/plugin.go +115 -47
- package/lib/src/MemFSError.js +4 -0
- package/lib/src/MemFSError.js.map +1 -1
- package/lib/src/bootTtsc.js +93 -37
- package/lib/src/bootTtsc.js.map +1 -1
- package/lib/src/createMemFS.js +173 -10
- package/lib/src/createMemFS.js.map +1 -1
- package/package.json +1 -1
- package/shim-vendor/shim/ast/lint.go +10 -0
- package/shim-vendor/shim/ast/shim.go +44 -0
- package/shim-vendor/shim/checker/enums_gen.go +19 -0
- package/shim-vendor/shim/checker/shim.go +130 -0
- package/shim-vendor/shim/diagnosticwriter/lint.go +30 -3
- package/shim-vendor/shim/scanner/regexp.go +27 -0
- package/src/MemFSError.ts +4 -0
- package/src/bootTtsc.ts +115 -47
- package/src/createMemFS.ts +170 -10
- package/shim-vendor/shim/checker/signature_api_completeness.go +0 -58
package/src/MemFSError.ts
CHANGED
|
@@ -27,6 +27,8 @@ function errnoForCode(code: string): number {
|
|
|
27
27
|
return -2;
|
|
28
28
|
case "EBADF":
|
|
29
29
|
return -9;
|
|
30
|
+
case "EBUSY":
|
|
31
|
+
return -16;
|
|
30
32
|
case "EEXIST":
|
|
31
33
|
return -17;
|
|
32
34
|
case "ENOTDIR":
|
|
@@ -37,6 +39,8 @@ function errnoForCode(code: string): number {
|
|
|
37
39
|
return -22;
|
|
38
40
|
case "ESPIPE":
|
|
39
41
|
return -29;
|
|
42
|
+
case "ENOTEMPTY":
|
|
43
|
+
return -39;
|
|
40
44
|
default:
|
|
41
45
|
return -1;
|
|
42
46
|
}
|
package/src/bootTtsc.ts
CHANGED
|
@@ -109,56 +109,124 @@ async function bootTtscOnce(
|
|
|
109
109
|
|
|
110
110
|
const host = options.host ?? createMemFS();
|
|
111
111
|
const globalAny = globalThis as Record<string, unknown>;
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
globalAny[apiName + "Ready"] = () => {
|
|
127
|
-
delete globalAny[apiName + "Failed"];
|
|
128
|
-
resolve();
|
|
129
|
-
};
|
|
130
|
-
globalAny[apiName + "Failed"] = (err: unknown) => {
|
|
131
|
-
delete globalAny[apiName + "Ready"];
|
|
132
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
133
|
-
};
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
const goCtor = (globalAny as { Go?: new () => IGoInstance }).Go;
|
|
137
|
-
if (typeof goCtor !== "function") {
|
|
138
|
-
throw new Error(
|
|
139
|
-
`bootTtsc: globalThis.Go was not installed by ${wasmExecUrl} — the file may not have loaded (CSP block, wrong content type, 404), or it is not the wasm_exec.js shipped with the Go toolchain.`,
|
|
140
|
-
);
|
|
112
|
+
// Install fs / process only if they aren't already in place, and remember
|
|
113
|
+
// whether THIS attempt installed them. The Go runtime this boot starts reads
|
|
114
|
+
// `globalThis.fs` when it runs, so the returned `host` must be the exact host
|
|
115
|
+
// backing those globals. A caller booting a second wasm over the same MemFS
|
|
116
|
+
// (or reusing one host across retries) reuses the same shims. When an earlier
|
|
117
|
+
// failed attempt already installed a different host's shims, those are torn
|
|
118
|
+
// down on failure below so this attempt can install its own.
|
|
119
|
+
const installedFs = !globalAny.fs;
|
|
120
|
+
const installedProcess = !globalAny.process;
|
|
121
|
+
let processShim: unknown;
|
|
122
|
+
if (installedFs) globalAny.fs = host.fs;
|
|
123
|
+
if (installedProcess) {
|
|
124
|
+
processShim = createProcessShim();
|
|
125
|
+
globalAny.process = processShim;
|
|
141
126
|
}
|
|
142
|
-
const go = new goCtor();
|
|
143
127
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
128
|
+
// Any failure after global installation must leave the globals as this
|
|
129
|
+
// attempt found them, so a retry installs its own host's fs (and the returned
|
|
130
|
+
// host keeps matching the runtime's filesystem). Only remove what we
|
|
131
|
+
// installed and only while it is still ours — never stomp a foreign fs or one
|
|
132
|
+
// a concurrently-booted runtime already claimed.
|
|
133
|
+
const restoreGlobals = (): void => {
|
|
134
|
+
if (installedFs && globalAny.fs === host.fs) delete globalAny.fs;
|
|
135
|
+
if (installedProcess && globalAny.process === processShim)
|
|
136
|
+
delete globalAny.process;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// wasm_exec.js installs `globalThis.Go`. It also reads globalThis.fs at
|
|
141
|
+
// module-eval time, so this import must follow the assignment above.
|
|
142
|
+
importScripts(wasmExecUrl);
|
|
143
|
+
|
|
144
|
+
// Race the Ready resolver against a Failed signal so a wasm-side fault
|
|
145
|
+
// (e.g. `host.Expose` refusing a duplicate call) surfaces here instead of
|
|
146
|
+
// hanging on `await ready` forever.
|
|
147
|
+
let readyCb!: () => void;
|
|
148
|
+
let failedCb!: (err: unknown) => void;
|
|
149
|
+
const ready = new Promise<void>((resolve, reject) => {
|
|
150
|
+
readyCb = () => {
|
|
151
|
+
delete globalAny[apiName + "Failed"];
|
|
152
|
+
resolve();
|
|
153
|
+
};
|
|
154
|
+
failedCb = (err: unknown) => {
|
|
155
|
+
delete globalAny[apiName + "Ready"];
|
|
156
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
157
|
+
};
|
|
158
|
+
globalAny[apiName + "Ready"] = readyCb;
|
|
159
|
+
globalAny[apiName + "Failed"] = failedCb;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const goCtor = (globalAny as { Go?: new () => IGoInstance }).Go;
|
|
163
|
+
if (typeof goCtor !== "function") {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`bootTtsc: globalThis.Go was not installed by ${wasmExecUrl} — the file may not have loaded (CSP block, wrong content type, 404), or it is not the wasm_exec.js shipped with the Go toolchain.`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const go = new goCtor();
|
|
169
|
+
|
|
170
|
+
const response = await fetch(wasmUrl);
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`bootTtsc: failed to fetch ${wasmUrl}: ${response.status}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const wasm = await WebAssembly.instantiateStreaming(
|
|
177
|
+
response,
|
|
178
|
+
go.importObject,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// A normal host keeps `go.run` pending forever after signaling Ready, so a
|
|
182
|
+
// settlement (fulfil OR reject) BEFORE Ready means the Go runtime exited or
|
|
183
|
+
// panicked before it could register — e.g. an early `host.Expose` panic
|
|
184
|
+
// that never reached the Failed bridge. Race that early exit against
|
|
185
|
+
// readiness so the boot rejects with an actionable cause instead of hanging.
|
|
186
|
+
// The standard Go runner discards the exit code, so an unknown early exit
|
|
187
|
+
// can only synthesize a generic message; known host validation failures
|
|
188
|
+
// reject through Failed above and keep their original cause.
|
|
189
|
+
const runPromise = Promise.resolve(go.run(wasm.instance));
|
|
190
|
+
const earlyExit = runPromise.then(
|
|
191
|
+
() => {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`bootTtsc: the ${apiName} wasm runtime exited before signaling readiness (the host may have panicked; check the wasm stderr).`,
|
|
194
|
+
);
|
|
195
|
+
},
|
|
196
|
+
(err: unknown) => {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`bootTtsc: the ${apiName} wasm runtime failed before signaling readiness: ${
|
|
199
|
+
err instanceof Error ? err.message : String(err)
|
|
200
|
+
}`,
|
|
201
|
+
);
|
|
202
|
+
},
|
|
160
203
|
);
|
|
161
|
-
|
|
204
|
+
// When Ready wins, the long-running runtime's eventual `go.run` settlement
|
|
205
|
+
// must not surface as an unhandled rejection. Attach a terminal handler to
|
|
206
|
+
// the losing branch.
|
|
207
|
+
earlyExit.catch(() => {});
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
await Promise.race([ready, earlyExit]);
|
|
211
|
+
} finally {
|
|
212
|
+
// Drop this attempt's readiness bridge so a later boot for the same
|
|
213
|
+
// apiName installs a clean pair and no stale resolver survives.
|
|
214
|
+
if (globalAny[apiName + "Ready"] === readyCb)
|
|
215
|
+
delete globalAny[apiName + "Ready"];
|
|
216
|
+
if (globalAny[apiName + "Failed"] === failedCb)
|
|
217
|
+
delete globalAny[apiName + "Failed"];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const api = (globalAny as Record<string, ITtscApi | undefined>)[apiName];
|
|
221
|
+
if (!api)
|
|
222
|
+
throw new Error(
|
|
223
|
+
`bootTtsc: ${apiName} global was not set — was the wasm built with host.Expose(${JSON.stringify(apiName)}, ...)?`,
|
|
224
|
+
);
|
|
225
|
+
return { api, host };
|
|
226
|
+
} catch (err) {
|
|
227
|
+
restoreGlobals();
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
162
230
|
}
|
|
163
231
|
|
|
164
232
|
/**
|
package/src/createMemFS.ts
CHANGED
|
@@ -160,6 +160,59 @@ export function createMemFS(): IMemFSHost {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/** Absolute parent directory of a normalized path (`/` for a top-level path). */
|
|
164
|
+
function parentDir(norm: string): string {
|
|
165
|
+
const idx = norm.lastIndexOf("/");
|
|
166
|
+
return idx <= 0 ? "/" : norm.slice(0, idx);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** True when `dir` has at least one descendant node in the tree. */
|
|
170
|
+
function hasChildren(dir: string): boolean {
|
|
171
|
+
const prefix = dir === "/" ? "/" : dir + "/";
|
|
172
|
+
for (const key of nodes.keys()) {
|
|
173
|
+
if (key !== dir && key.startsWith(prefix)) return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Grow or shrink a file's byte buffer to exactly `length`, zero-filling any
|
|
180
|
+
* extension. Callers validate `length >= 0` first.
|
|
181
|
+
*/
|
|
182
|
+
function resizeFileData(data: Uint8Array, length: number): Uint8Array {
|
|
183
|
+
const next = new Uint8Array(length);
|
|
184
|
+
next.set(data.subarray(0, Math.min(length, data.byteLength)));
|
|
185
|
+
return next;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Move the entire subtree rooted at `src` to `dest`, overwriting any existing
|
|
190
|
+
* `dest` node. Every descendant key is re-parented so no old-prefix node is
|
|
191
|
+
* left orphaned, and open descriptors that referenced a moved path follow to
|
|
192
|
+
* the new location (a rename must not strand an fd's inode).
|
|
193
|
+
*/
|
|
194
|
+
function moveSubtree(src: string, dest: string): void {
|
|
195
|
+
const srcPrefix = src + "/";
|
|
196
|
+
const moves: Array<[string, INode]> = [];
|
|
197
|
+
for (const [key, node] of nodes) {
|
|
198
|
+
if (key === src) moves.push([dest, node]);
|
|
199
|
+
else if (key.startsWith(srcPrefix))
|
|
200
|
+
moves.push([dest + "/" + key.slice(srcPrefix.length), node]);
|
|
201
|
+
}
|
|
202
|
+
// Delete the whole source subtree first so a nested overwrite cannot leave
|
|
203
|
+
// a stale descendant behind, then reinsert at the destination prefix. Any
|
|
204
|
+
// pre-existing `dest` node is replaced by the reinsert.
|
|
205
|
+
for (const key of [...nodes.keys()]) {
|
|
206
|
+
if (key === src || key.startsWith(srcPrefix)) nodes.delete(key);
|
|
207
|
+
}
|
|
208
|
+
for (const [key, node] of moves) nodes.set(key, node);
|
|
209
|
+
for (const entry of fdTable.values()) {
|
|
210
|
+
if (entry.path === src) entry.path = dest;
|
|
211
|
+
else if (entry.path.startsWith(srcPrefix))
|
|
212
|
+
entry.path = dest + "/" + entry.path.slice(srcPrefix.length);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
163
216
|
function mkdirp(p: string): void {
|
|
164
217
|
const segments = normalize(p).split("/").filter(Boolean);
|
|
165
218
|
let cursor = "";
|
|
@@ -281,10 +334,8 @@ export function createMemFS(): IMemFSHost {
|
|
|
281
334
|
console.error("[wasm]", line);
|
|
282
335
|
return buf.length;
|
|
283
336
|
}
|
|
284
|
-
// Open-file fds (>= 100): append to the underlying file.
|
|
285
|
-
// path
|
|
286
|
-
// stdout/stderr via /tmp/* temp files (os.Pipe is not implemented on
|
|
287
|
-
// js/wasm so we mirror its semantics via files).
|
|
337
|
+
// Open-file fds (>= 100): append to the underlying file. Browser-hosted
|
|
338
|
+
// tools use this path for ordinary virtual files and explicit outputs.
|
|
288
339
|
const entry = fdTable.get(fd);
|
|
289
340
|
if (entry) {
|
|
290
341
|
const node = nodes.get(entry.path);
|
|
@@ -508,10 +559,19 @@ export function createMemFS(): IMemFSHost {
|
|
|
508
559
|
|
|
509
560
|
unlink(p, callback) {
|
|
510
561
|
const norm = normalize(p);
|
|
511
|
-
|
|
562
|
+
const node = nodes.get(norm);
|
|
563
|
+
if (!node) {
|
|
512
564
|
callback(new MemFSError("ENOENT", "unlink", norm));
|
|
513
565
|
return;
|
|
514
566
|
}
|
|
567
|
+
// POSIX unlink refuses directories (EISDIR/EPERM). Go's os.Remove tries
|
|
568
|
+
// unlink first and only falls back to rmdir when it fails, so a false
|
|
569
|
+
// success here would delete just the directory node and orphan every
|
|
570
|
+
// descendant. Reject so the rmdir path (which validates emptiness) runs.
|
|
571
|
+
if (node.kind === "dir") {
|
|
572
|
+
callback(new MemFSError("EISDIR", "unlink", norm));
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
515
575
|
nodes.delete(norm);
|
|
516
576
|
callback(null);
|
|
517
577
|
},
|
|
@@ -523,14 +583,73 @@ export function createMemFS(): IMemFSHost {
|
|
|
523
583
|
callback(new MemFSError("ENOENT", "rename", src));
|
|
524
584
|
return;
|
|
525
585
|
}
|
|
586
|
+
if (src === "/") {
|
|
587
|
+
callback(new MemFSError("EBUSY", "rename", src));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
526
590
|
const dest = normalize(to);
|
|
527
|
-
|
|
528
|
-
|
|
591
|
+
// Renaming a path onto itself is a defined no-op success.
|
|
592
|
+
if (src === dest) {
|
|
593
|
+
callback(null);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
// A directory cannot be moved inside itself or its own descendants.
|
|
597
|
+
if (node.kind === "dir" && dest.startsWith(src + "/")) {
|
|
598
|
+
callback(new MemFSError("EINVAL", "rename", src));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
// The destination's parent must already exist as a directory.
|
|
602
|
+
const parent = nodes.get(parentDir(dest));
|
|
603
|
+
if (!parent) {
|
|
604
|
+
callback(new MemFSError("ENOENT", "rename", dest));
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (parent.kind !== "dir") {
|
|
608
|
+
callback(new MemFSError("ENOTDIR", "rename", dest));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
// Reconcile against an existing destination before mutating anything so a
|
|
612
|
+
// rejected rename leaves the tree untouched (no partial state).
|
|
613
|
+
const destNode = nodes.get(dest);
|
|
614
|
+
if (destNode) {
|
|
615
|
+
if (node.kind === "file") {
|
|
616
|
+
if (destNode.kind === "dir") {
|
|
617
|
+
callback(new MemFSError("EISDIR", "rename", dest));
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
} else if (destNode.kind !== "dir") {
|
|
621
|
+
callback(new MemFSError("ENOTDIR", "rename", dest));
|
|
622
|
+
return;
|
|
623
|
+
} else if (hasChildren(dest)) {
|
|
624
|
+
callback(new MemFSError("ENOTEMPTY", "rename", dest));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
moveSubtree(src, dest);
|
|
529
629
|
callback(null);
|
|
530
630
|
},
|
|
531
631
|
|
|
532
632
|
rmdir(p, callback) {
|
|
533
|
-
|
|
633
|
+
const norm = normalize(p);
|
|
634
|
+
const node = nodes.get(norm);
|
|
635
|
+
if (!node) {
|
|
636
|
+
callback(new MemFSError("ENOENT", "rmdir", norm));
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (node.kind !== "dir") {
|
|
640
|
+
callback(new MemFSError("ENOTDIR", "rmdir", norm));
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (norm === "/") {
|
|
644
|
+
callback(new MemFSError("EBUSY", "rmdir", norm));
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (hasChildren(norm)) {
|
|
648
|
+
callback(new MemFSError("ENOTEMPTY", "rmdir", norm));
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
nodes.delete(norm);
|
|
652
|
+
callback(null);
|
|
534
653
|
},
|
|
535
654
|
|
|
536
655
|
chmod(_p, _mode, callback) {
|
|
@@ -560,10 +679,51 @@ export function createMemFS(): IMemFSHost {
|
|
|
560
679
|
readlink(_p, callback) {
|
|
561
680
|
callback(new MemFSError("EINVAL", "readlink"), "");
|
|
562
681
|
},
|
|
563
|
-
truncate(
|
|
682
|
+
truncate(p, length, callback) {
|
|
683
|
+
const norm = normalize(p);
|
|
684
|
+
const node = nodes.get(norm);
|
|
685
|
+
if (!node) {
|
|
686
|
+
callback(new MemFSError("ENOENT", "truncate", norm));
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (node.kind !== "file") {
|
|
690
|
+
callback(new MemFSError("EISDIR", "truncate", norm));
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (!Number.isInteger(length) || length < 0) {
|
|
694
|
+
callback(new MemFSError("EINVAL", "truncate", norm));
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
node.data = resizeFileData(node.data, length);
|
|
698
|
+
node.mtimeMs = Date.now();
|
|
564
699
|
callback(null);
|
|
565
700
|
},
|
|
566
|
-
ftruncate(
|
|
701
|
+
ftruncate(fd, length, callback) {
|
|
702
|
+
// Pipe ends and the reserved stdout/stderr fds have no truncatable file.
|
|
703
|
+
if (pipes.has(fd)) {
|
|
704
|
+
callback(new MemFSError("EINVAL", "ftruncate"));
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const entry = fdTable.get(fd);
|
|
708
|
+
if (!entry) {
|
|
709
|
+
callback(new MemFSError("EBADF", "ftruncate"));
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (entry.isStdout || entry.isStderr) {
|
|
713
|
+
callback(new MemFSError("EINVAL", "ftruncate"));
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const node = nodes.get(entry.path);
|
|
717
|
+
if (!node || node.kind !== "file") {
|
|
718
|
+
callback(new MemFSError("EINVAL", "ftruncate", entry.path));
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (!Number.isInteger(length) || length < 0) {
|
|
722
|
+
callback(new MemFSError("EINVAL", "ftruncate", entry.path));
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
node.data = resizeFileData(node.data, length);
|
|
726
|
+
node.mtimeMs = Date.now();
|
|
567
727
|
callback(null);
|
|
568
728
|
},
|
|
569
729
|
pipe2(_flags, callback) {
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
package checker
|
|
2
|
-
|
|
3
|
-
// Compile-time guard: the signature-introspection surface that typia's
|
|
4
|
-
// plain.classify from/new construction-strategy detection depends on must
|
|
5
|
-
// compose end-to-end through the shim.
|
|
6
|
-
//
|
|
7
|
-
// 0.15.5 exposed a CONSUMER of a *Signature (Checker_getMinArgumentCount)
|
|
8
|
-
// without the PRODUCER that yields one (Checker_getSignaturesOfType): a
|
|
9
|
-
// *Signature was nameable (the `Signature` alias) but unobtainable. The closure
|
|
10
|
-
// auditor checks that every escaping type can be NAMED, not that it can be
|
|
11
|
-
// PRODUCED, so it did not catch the gap and the feature stayed blocked.
|
|
12
|
-
//
|
|
13
|
-
// This lives in a NORMAL (non-_test) file on purpose. The shim/checker package
|
|
14
|
-
// is a nested Go module that no CI job runs `go test` against, so a _test.go
|
|
15
|
-
// guard would never be compiled and would silently stop guarding. As ordinary
|
|
16
|
-
// package code it is type-checked by every build that links the shim (typia's
|
|
17
|
-
// native plugin, ttsc's lint engine), so dropping or renaming any leg below
|
|
18
|
-
// turns those builds red. It never runs (assigned to blank, never called).
|
|
19
|
-
var _ = func(c *Checker, instanceType *Type) {
|
|
20
|
-
// class instance type -> class symbol -> constructor (static) type, which
|
|
21
|
-
// carries both the construct signatures and the static `from` member.
|
|
22
|
-
classSymbol := Type_getTypeNameSymbol(instanceType)
|
|
23
|
-
ctorType := Checker_getTypeOfSymbol(c, classSymbol)
|
|
24
|
-
|
|
25
|
-
// `new C(seed)` strategy: construct signatures -> arity (min args +
|
|
26
|
-
// parameter count, together disambiguating `()` from `(x?)`) -> return type
|
|
27
|
-
// -> the seed parameter's type. The seed is Signature_parameters[0]'s type,
|
|
28
|
-
// EXCEPT a rest-ONLY parameter `(...xs: S[])` — when
|
|
29
|
-
// `hasRestParameter && parameterCount == 1` — whose seed is the rest ELEMENT
|
|
30
|
-
// (getRestTypeOfSignature). A leading-required + rest-tail `(s, ...r)` still
|
|
31
|
-
// has hasRestParameter true but its seed is the first parameter S.
|
|
32
|
-
for _, sig := range Checker_getSignaturesOfType(c, ctorType, SignatureKindConstruct) {
|
|
33
|
-
_ = Checker_getMinArgumentCount(c, sig)
|
|
34
|
-
_ = Signature_parameterCount(sig)
|
|
35
|
-
_ = Checker_getReturnTypeOfSignature(c, sig)
|
|
36
|
-
if Signature_hasRestParameter(sig) {
|
|
37
|
-
_ = Checker_getRestTypeOfSignature(c, sig)
|
|
38
|
-
}
|
|
39
|
-
for _, p := range Signature_parameters(sig) {
|
|
40
|
-
_ = Checker_getTypeOfSymbol(c, p)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// `C.from(seed)` strategy: static `from` member -> its call signatures, read
|
|
45
|
-
// identically (arity, return type, seed parameter / rest element type).
|
|
46
|
-
fromType := Checker_getTypeOfPropertyOfType(c, ctorType, "from")
|
|
47
|
-
for _, sig := range Checker_getSignaturesOfType(c, fromType, SignatureKindCall) {
|
|
48
|
-
_ = Checker_getMinArgumentCount(c, sig)
|
|
49
|
-
_ = Signature_parameterCount(sig)
|
|
50
|
-
_ = Checker_getReturnTypeOfSignature(c, sig)
|
|
51
|
-
if Signature_hasRestParameter(sig) {
|
|
52
|
-
_ = Checker_getRestTypeOfSignature(c, sig)
|
|
53
|
-
}
|
|
54
|
-
for _, p := range Signature_parameters(sig) {
|
|
55
|
-
_ = Checker_getTypeOfSymbol(c, p)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|