mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Merge main
This commit is contained in:
@@ -39,6 +39,8 @@ import {
|
||||
warmNativeCaches,
|
||||
} from "./native-toolchain.js";
|
||||
import { splitLlvmProgram } from "./llvm/split.js";
|
||||
import { implicitToolchainFingerprints } from "./native/compiler-fingerprint.js";
|
||||
import { fingerprintDependencyPaths } from "./native/dependency-files.js";
|
||||
|
||||
const scratch: string[] = [];
|
||||
const TEST_CACHE_IDENTITY = "cc-cache-tests-v1";
|
||||
@@ -672,6 +674,29 @@ test.skipIf(clangExecutable === undefined)(
|
||||
},
|
||||
);
|
||||
|
||||
test.skipIf(clangExecutable === undefined)(
|
||||
"the implicit toolchain probe leaves out a header the compiler rejects",
|
||||
async () => {
|
||||
// A system header that the runtime spells, shadowed by one that is an
|
||||
// #error, as clang 18's <riscv_vector.h> is outside RISC-V. The probe
|
||||
// still covers the other headers.
|
||||
const dir = await mkdtemp(join(tmpdir(), "scriptc-probe-header-"));
|
||||
scratch.push(dir);
|
||||
await mkdir(join(dir, "sys"));
|
||||
const rejected = join(dir, "sys", "auxv.h");
|
||||
await writeFile(rejected, '#error "not for this target"\n');
|
||||
const includes = await implicitDependencyProbeIncludes(runtimeSrcDir());
|
||||
expect(includes).toContain("<sys/auxv.h>");
|
||||
const fingerprints = await implicitToolchainFingerprints(
|
||||
{ argv: [clangExecutable!, "-isystem", dir], targetArgs: [], target: null },
|
||||
"probe-rejected-header",
|
||||
);
|
||||
const dependencies = fingerprintDependencyPaths(fingerprints.complete);
|
||||
expect(dependencies).not.toContain(rejected);
|
||||
expect(dependencies.some((path) => path.endsWith("/stdatomic.h"))).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
test.skipIf(
|
||||
process.platform === "win32" ||
|
||||
clangExecutable === undefined ||
|
||||
|
||||
@@ -399,11 +399,8 @@ async function implicitToolchainFingerprintsFresh(
|
||||
];
|
||||
const [dependencyResults, linker, assembler, compilerInvocation] = await Promise.all([
|
||||
Promise.all(
|
||||
sources.map((source) =>
|
||||
execFileAsync(compiler, [...probeArgs, source], {
|
||||
cwd: probeDir,
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
}),
|
||||
sources.map((source, index) =>
|
||||
probeIncludeDependencies(compiler, probeArgs, probeDir, source, sourceGroups[index]!),
|
||||
),
|
||||
),
|
||||
execFileAsync(compiler, [...prefix, "-print-prog-name=ld"], { cwd: probeDir }),
|
||||
@@ -423,14 +420,7 @@ async function implicitToolchainFingerprintsFresh(
|
||||
const toolSpellings = [linker.stdout.trim(), assemblerSpelling].filter(
|
||||
(value, index, all) => value !== "" && all.indexOf(value) === index,
|
||||
);
|
||||
const sourceSet = new Set(sources);
|
||||
const dependencyPaths = [
|
||||
...new Set(
|
||||
dependencyResults
|
||||
.flatMap((dependencies) => parseMakeDependencies(dependencies.stdout))
|
||||
.filter((path) => !sourceSet.has(path)),
|
||||
),
|
||||
].sort();
|
||||
const dependencyPaths = [...new Set(dependencyResults.flat())].sort();
|
||||
probe = {
|
||||
compilerIdentity: compilerIdentity ?? `<unresolved>\0${compiler}`,
|
||||
compilerInvocation: normalizedProbeInvocation(compilerInvocation, probeDir),
|
||||
@@ -513,6 +503,51 @@ async function implicitToolchainFingerprintsFresh(
|
||||
};
|
||||
}
|
||||
|
||||
/** Headers the dependency probe preprocesses at a time when it probes them
|
||||
* one by one. */
|
||||
const SINGLE_HEADER_PROBES = 8;
|
||||
|
||||
/** The files the compiler reads to preprocess `source`, a probe source that
|
||||
* includes `includes`, apart from the probe's own sources. When the compiler
|
||||
* rejects `source`, for example for a header it finds that is an #error
|
||||
* outside its target (<riscv_vector.h> through clang 18 without the RISC-V
|
||||
* vector extension), each header is preprocessed on its own, and a header
|
||||
* that the compiler rejects on its own is left out, as an absent one is.
|
||||
* Throws the compiler's error when it rejects every header. */
|
||||
async function probeIncludeDependencies(
|
||||
compiler: string,
|
||||
probeArgs: readonly string[],
|
||||
probeDir: string,
|
||||
source: string,
|
||||
includes: readonly string[],
|
||||
): Promise<string[]> {
|
||||
const dependencies = async (path: string): Promise<string[]> => {
|
||||
const result = await execFileAsync(compiler, [...probeArgs, path], {
|
||||
cwd: probeDir,
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
});
|
||||
return parseMakeDependencies(result.stdout).filter((dependency) => dependency !== path);
|
||||
};
|
||||
try {
|
||||
return await dependencies(source);
|
||||
} catch (error) {
|
||||
const found: string[][] = [];
|
||||
for (let start = 0; start < includes.length; start += SINGLE_HEADER_PROBES) {
|
||||
const batch = includes.slice(start, start + SINGLE_HEADER_PROBES);
|
||||
const results = await Promise.all(
|
||||
batch.map(async (include, offset) => {
|
||||
const single = `${source.slice(0, -2)}-${start + offset}.c`;
|
||||
await writeFile(single, implicitDependencyIncludeDirective(include));
|
||||
return dependencies(single).catch(() => null);
|
||||
}),
|
||||
);
|
||||
for (const result of results) if (result !== null) found.push(result);
|
||||
}
|
||||
if (found.length === 0) throw error;
|
||||
return found.flat();
|
||||
}
|
||||
}
|
||||
|
||||
const stableImplicitToolchainMemos = new Map<string, Promise<ImplicitToolchainFingerprints>>();
|
||||
export function implicitToolchainFingerprints(
|
||||
driver: Pick<CcDriver, "argv" | "targetArgs" | "target">,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { dynUndefinedExpr, varRef } from "../../ir/build.js";
|
||||
import { dynUndefinedExpr, numLit, varRef } from "../../ir/build.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
F64,
|
||||
STRING,
|
||||
canConvertToDyn,
|
||||
funcOf,
|
||||
@@ -16,7 +17,7 @@ import {
|
||||
import { transformStmtList } from "../../ir/traverse.js";
|
||||
import { genResultRecord } from "../type-mapper.js";
|
||||
import type { DispatchSites } from "./dispatch-sites.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import type { Lowerer, UntypedLoopSite } from "./lowerer.js";
|
||||
|
||||
type GeneratorType = Extract<IrType, { kind: "generator" }>;
|
||||
type Invoke = Extract<IrExpr, { kind: "dynInvoke" }>;
|
||||
@@ -401,6 +402,310 @@ export class GeneratorDynamicDispatch {
|
||||
});
|
||||
changed = true;
|
||||
}
|
||||
return this.stepUntypedLoops(lowerer, functions) || changed;
|
||||
}
|
||||
|
||||
/** The generator types a loop over an untyped iterable can step natively:
|
||||
* sync generators for `for … of`, and both kinds for `for await` (a sync
|
||||
* generator through the async-from-sync rules). Insertion order, so a
|
||||
* type keeps its index as later waves add types. */
|
||||
private loopTypes(lowerer: Lowerer, site: UntypedLoopSite): [string, GeneratorType][] {
|
||||
const out: [string, GeneratorType][] = [];
|
||||
for (const [key, type] of this.generators) {
|
||||
if (type.async === true && !site.async) continue;
|
||||
const result = genResultRecord(type.yieldT, type.retT, lowerer.shapes, lowerer.unions);
|
||||
if (result === null) continue;
|
||||
// next() with no argument, as the protocol calls it.
|
||||
const channel = type.nextT;
|
||||
if (channel.kind !== "undefinedT" && channel.kind !== "void" && channel.kind !== "dyn")
|
||||
continue;
|
||||
if (
|
||||
!canConvertToDyn(
|
||||
result,
|
||||
(id) => lowerer.shapes.get(id),
|
||||
(id) => lowerer.unions.get(id),
|
||||
)
|
||||
)
|
||||
continue;
|
||||
out.push([key, type]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Loops over untyped iterables step the program's own generators on the
|
||||
* typed path. Once the loop has its iterator and next method, a kind local
|
||||
* records which generator type the iterator is, when its next method is
|
||||
* the generator's own (`%dyn.generator.method.next`); each step then
|
||||
* resumes the generator directly and boxes only the value, where the
|
||||
* protocol would box the result record and its promise and read them
|
||||
* back. Every other iterator, and every early exit (return() through the
|
||||
* protocol), keeps the dynamic path. The awaits are the protocol's: a
|
||||
* `for await` over an async generator awaits next()'s promise, and over a
|
||||
* sync generator awaits once for the result and, reached through
|
||||
* Symbol.iterator, once for the value. */
|
||||
private stepUntypedLoops(lowerer: Lowerer, functions: readonly IrFunction[]): boolean {
|
||||
if (this.generators.size === 0 || lowerer.untypedLoops.size === 0) return false;
|
||||
// The loop reads next through the getter only once it exists.
|
||||
if (!this.properties.has("next")) return false;
|
||||
let changed = false;
|
||||
for (const fn of functions) {
|
||||
if (this.generated.has(fn)) continue;
|
||||
const byKind = new Map<string, UntypedLoopSite>();
|
||||
const byValue = new Map<string, UntypedLoopSite>();
|
||||
for (const local of fn.locals) {
|
||||
const site = lowerer.untypedLoops.get(local);
|
||||
if (site === undefined) continue;
|
||||
byKind.set(site.kind, site);
|
||||
byValue.set(site.value, site);
|
||||
}
|
||||
if (byKind.size === 0) continue;
|
||||
fn.body = transformStmtList(fn.body, {
|
||||
expr: (expr) => expr,
|
||||
stmt: (stmt) => {
|
||||
if (stmt.kind !== "varDecl" || stmt.init === null) return stmt;
|
||||
const opened = byKind.get(stmt.localId);
|
||||
if (opened !== undefined) {
|
||||
const types = this.loopTypes(lowerer, opened);
|
||||
const init = this.loopKind(opened, types, stmt.loc);
|
||||
if (this.loopKindCount(stmt.init) === types.length) return stmt;
|
||||
changed = true;
|
||||
return { ...stmt, init };
|
||||
}
|
||||
const site = byValue.get(stmt.localId);
|
||||
if (site === undefined) return stmt;
|
||||
const types = this.loopTypes(lowerer, site);
|
||||
// The protocol step is the innermost else of the type chain.
|
||||
let fallback = stmt.init;
|
||||
let present = 0;
|
||||
while (
|
||||
fallback.kind === "ternary" &&
|
||||
fallback.cond.kind === "bin" &&
|
||||
fallback.cond.left.kind === "varRef" &&
|
||||
fallback.cond.left.localId === site.kind
|
||||
) {
|
||||
fallback = fallback.else_;
|
||||
present++;
|
||||
}
|
||||
if (present === types.length) return stmt;
|
||||
let init = fallback;
|
||||
for (let i = types.length - 1; i >= 0; i--) {
|
||||
const [, type] = types[i]!;
|
||||
const record = `${site.kind}.r${i + 1}`;
|
||||
init = {
|
||||
kind: "ternary",
|
||||
cond: {
|
||||
kind: "bin",
|
||||
op: "===",
|
||||
left: varRef(site.kind, F64, stmt.loc),
|
||||
right: numLit(i + 1, stmt.loc),
|
||||
type: BOOL,
|
||||
loc: stmt.loc,
|
||||
},
|
||||
then: this.loopStep(lowerer, fn, site, type, record, stmt.loc),
|
||||
else_: init,
|
||||
type: DYN,
|
||||
loc: stmt.loc,
|
||||
};
|
||||
}
|
||||
changed = true;
|
||||
return { ...stmt, init };
|
||||
},
|
||||
});
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/** How many generator types a loop's kind initializer tests: the chain
|
||||
* inside its own-method test (loopKind). */
|
||||
private loopKindCount(init: IrExpr): number {
|
||||
if (init.kind !== "ternary") return 0;
|
||||
let count = 0;
|
||||
for (let chain = init.then; chain.kind === "ternary"; chain = chain.else_) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
/** The loop's kind: the 1-based index of the generator type its iterator
|
||||
* is, when the next method it read is that generator's own, else 0. */
|
||||
private loopKind(
|
||||
site: UntypedLoopSite,
|
||||
types: readonly [string, GeneratorType][],
|
||||
loc: IrExpr["loc"],
|
||||
): IrExpr {
|
||||
const own: IrExpr = {
|
||||
kind: "libCall",
|
||||
fn: "dyn.sameValue",
|
||||
args: [
|
||||
varRef(site.next, DYN, loc),
|
||||
{
|
||||
kind: "dynFrom",
|
||||
value: {
|
||||
kind: "closure",
|
||||
fnName: "%dyn.generator.method.next",
|
||||
captures: [],
|
||||
type: funcOf([DYN], DYN),
|
||||
loc,
|
||||
},
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
],
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
let kind: IrExpr = numLit(0, loc);
|
||||
for (let i = types.length - 1; i >= 0; i--)
|
||||
kind = {
|
||||
kind: "ternary",
|
||||
cond: {
|
||||
kind: "libCall",
|
||||
fn: "dyn.typedRefIs",
|
||||
args: [
|
||||
varRef(site.iterator, DYN, loc),
|
||||
{ kind: "strLit", value: types[i]![0], type: STRING, loc },
|
||||
],
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: numLit(i + 1, loc),
|
||||
else_: kind,
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
return types.length === 0
|
||||
? kind
|
||||
: { kind: "ternary", cond: own, then: kind, else_: numLit(0, loc), type: F64, loc };
|
||||
}
|
||||
|
||||
/** One step of a loop over an untyped iterable that is a `type`
|
||||
* generator: sets the loop's done flag and answers the value, boxed. */
|
||||
private loopStep(
|
||||
lowerer: Lowerer,
|
||||
fn: IrFunction,
|
||||
site: UntypedLoopSite,
|
||||
type: GeneratorType,
|
||||
recordId: string,
|
||||
loc: IrExpr["loc"],
|
||||
): IrExpr {
|
||||
const gen: IrExpr = { kind: "dynCheck", value: varRef(site.iterator, DYN, loc), type, loc };
|
||||
const setDone = (value: IrExpr): IrStmt => ({ kind: "assign", localId: site.done, value, loc });
|
||||
const doneRef = varRef(site.done, BOOL, loc);
|
||||
const result = genResultRecord(type.yieldT, type.retT, lowerer.shapes, lowerer.unions)!;
|
||||
if (!fn.locals.some((local) => local.id === recordId))
|
||||
fn.locals.push({ id: recordId, name: recordId, type: result, mutable: false });
|
||||
const field = (name: "value" | "done", fieldType: IrType): IrExpr => ({
|
||||
kind: "recordGet",
|
||||
obj: varRef(recordId, result, loc),
|
||||
shapeId: result.shapeId,
|
||||
field: name,
|
||||
type: fieldType,
|
||||
loc,
|
||||
});
|
||||
const valueType = lowerer.shapes
|
||||
.get(result.shapeId)!
|
||||
.fields.find((f) => f.name === "value")!.type;
|
||||
const boxed: IrExpr = { kind: "dynFrom", value: field("value", valueType), type: DYN, loc };
|
||||
const awaitDyn = (value: IrExpr): IrExpr => ({
|
||||
kind: "libCall",
|
||||
fn: "async.awaitDyn",
|
||||
args: [value],
|
||||
type: DYN,
|
||||
loc,
|
||||
});
|
||||
const resumed: IrExpr = {
|
||||
kind: "genResume",
|
||||
mode: "next",
|
||||
gen,
|
||||
arg: null,
|
||||
type: type.async === true ? { kind: "promise", inner: result } : result,
|
||||
loc,
|
||||
};
|
||||
if (!site.async)
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: recordId, init: resumed, loc },
|
||||
setDone(field("done", BOOL)),
|
||||
],
|
||||
result: {
|
||||
kind: "ternary",
|
||||
cond: doneRef,
|
||||
then: dynUndefinedExpr(loc),
|
||||
else_: boxed,
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
if (type.async === true)
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: recordId,
|
||||
init: { kind: "awaitExpr", value: resumed, type: result, loc },
|
||||
loc,
|
||||
},
|
||||
setDone(field("done", BOOL)),
|
||||
],
|
||||
result: {
|
||||
kind: "ternary",
|
||||
cond: doneRef,
|
||||
then: dynUndefinedExpr(loc),
|
||||
else_: boxed,
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
// for await over a sync generator: the result awaits once. When the
|
||||
// loop reached it through Symbol.iterator, the async-from-sync iterator
|
||||
// also awaits the value (the final one too); a sync generator that an
|
||||
// object's Symbol.asyncIterator returns passes the value as it is, as
|
||||
// the protocol does.
|
||||
const fromSync: IrExpr =
|
||||
site.sync === null
|
||||
? { kind: "boolLit", value: false, type: BOOL, loc }
|
||||
: varRef(site.sync, BOOL, loc);
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: recordId, init: resumed, loc },
|
||||
setDone(field("done", BOOL)),
|
||||
{ kind: "exprStmt", expr: awaitDyn(dynUndefinedExpr(loc)), loc },
|
||||
],
|
||||
result: {
|
||||
kind: "ternary",
|
||||
cond: doneRef,
|
||||
then: {
|
||||
kind: "ternary",
|
||||
cond: fromSync,
|
||||
then: {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "exprStmt", expr: awaitDyn(boxed), loc }],
|
||||
result: dynUndefinedExpr(loc),
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
else_: dynUndefinedExpr(loc),
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
else_: {
|
||||
kind: "ternary",
|
||||
cond: fromSync,
|
||||
then: awaitDyn(boxed),
|
||||
else_: boxed,
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11853,6 +11853,7 @@ function lowerForOfDyn(
|
||||
const iterator = lowerer.declareHiddenLocal("%dofIterator", DYN);
|
||||
const next = lowerer.declareHiddenLocal("%dofNext", DYN);
|
||||
const fast = lowerer.declareHiddenLocal("%dofNative", BOOL);
|
||||
const generator = lowerer.declareHiddenLocal("%dofGenerator", F64);
|
||||
const done = lowerer.declareHiddenLocal("%dofDone", BOOL);
|
||||
done.mutable = true;
|
||||
const value = lowerer.declareHiddenLocal("%dofValue", DYN);
|
||||
@@ -11862,6 +11863,15 @@ function lowerForOfDyn(
|
||||
needsClose.mutable = true;
|
||||
const error = lowerer.declareHiddenLocal("%dofError", CAUGHT);
|
||||
const syncIterator = asyncLoop ? lowerer.declareHiddenLocal("%dofSync", BOOL) : null;
|
||||
lowerer.untypedLoops.set(generator, {
|
||||
kind: generator.id,
|
||||
value: value.id,
|
||||
iterator: iterator.id,
|
||||
next: next.id,
|
||||
done: done.id,
|
||||
async: asyncLoop,
|
||||
sync: syncIterator?.id ?? null,
|
||||
});
|
||||
const awaitDyn = (value: IrExpr): IrExpr => ({
|
||||
kind: "libCall",
|
||||
fn: "async.awaitDyn",
|
||||
@@ -12136,6 +12146,14 @@ function lowerForOfDyn(
|
||||
: iteratorCanStep(varRef(iterator.id, DYN, loc), varRef(next.id, DYN, loc), loc),
|
||||
loc,
|
||||
},
|
||||
// Which of the program's generator types the iterator is, once
|
||||
// generator-dynamic-dispatch.ts knows them; 0 keeps the protocol.
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: generator.id,
|
||||
init: { kind: "numLit", value: 0, type: F64, loc },
|
||||
loc,
|
||||
},
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: done.id,
|
||||
|
||||
@@ -2068,6 +2068,21 @@ function byName(fns: readonly IrFunction[]): IrFunction[] {
|
||||
);
|
||||
}
|
||||
|
||||
/** A loop over an untyped iterable (lowerForOfDyn). `kind` is a local the
|
||||
* loop sets once it has opened the iterator: 0 for the dynamic protocol,
|
||||
* or the 1-based index of the program's generator type it steps natively.
|
||||
* `value` is the per-step local whose initializer takes one step. */
|
||||
export interface UntypedLoopSite {
|
||||
kind: string;
|
||||
value: string;
|
||||
iterator: string;
|
||||
next: string;
|
||||
done: string;
|
||||
/** `for await`; `sync` names its async-from-sync flag. */
|
||||
async: boolean;
|
||||
sync: string | null;
|
||||
}
|
||||
|
||||
/** The program module a createRequire binding requires. */
|
||||
type CreateRequireTarget = NonNullable<ReturnType<typeof createRequireProgramModuleOf>>;
|
||||
|
||||
@@ -3591,6 +3606,11 @@ export class Lowerer {
|
||||
* values other than fresh literals (noteUntypedIntegrity). */
|
||||
untypedIntegrity = false;
|
||||
readonly liftedFns: IrFunction[] = [];
|
||||
/** `for … of` and `for await` loops over untyped iterables, by their
|
||||
* generator-kind local (generator-dynamic-dispatch.ts steps the program's
|
||||
* own generators there on the typed path). Local ids repeat across
|
||||
* functions; the local itself is in its function's locals. */
|
||||
readonly untypedLoops = new Map<IrLocal, UntypedLoopSite>();
|
||||
/** global-values.ts's answer per JavaScript source: the receiver of its
|
||||
* first read of the global object by a computed name, or null. */
|
||||
readonly computedGlobalReads = new Map<ts.SourceFile, ts.Expression | null>();
|
||||
|
||||
@@ -2069,3 +2069,252 @@ export function walkTypeFields(node: IrType, walker: IrFieldWalker, skipType: bo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A field ir.ts declares that this file does not read. */
|
||||
type FieldsNotRead<T, Read extends PropertyKey> = Exclude<keyof T, Read>;
|
||||
/** The string or object values a field read as a scalar can hold. */
|
||||
type ValuesNotRead<T> = Extract<NonNullable<T>, string | object>;
|
||||
/** Fails type checking unless `T` is never: run `pnpm generate:ir-fields`. */
|
||||
type Unread<T extends never> = T;
|
||||
|
||||
/** Every walked object type's fields as ir.ts declared them when this file
|
||||
* was generated. */
|
||||
export type IrFieldsCurrent = [
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "arrIntrinsic" }>, "kind" | "method" | "receiver" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "arrayGet" }>, "kind" | "arr" | "index" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "arrayHas" }>, "kind" | "arr" | "index" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "arrayLit" }>, "kind" | "elems" | "spreads" | "capacity" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "arrayLit" }>["capacity"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "arrayNewLen" }>, "kind" | "length" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "arrayState" }>, "kind" | "arr" | "index" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "assignExpr" }>, "kind" | "localId" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "awaitExpr" }>, "kind" | "value" | "type" | "adopt" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "awaitExpr" }>["adopt"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "awaitUnionExpr" }>, "kind" | "value" | "promiseTag" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "awaitUnionExpr" }>["promiseTag"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "bin" }>, "kind" | "op" | "left" | "right" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "boolLit" }>, "kind" | "value" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "boolLit" }>["value"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "bytesIntrinsic" }>, "kind" | "method" | "receiver" | "args" | "type" | "loc" | "invalidNaN">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "bytesIntrinsic" }>["invalidNaN"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "bytesNew" }>, "kind" | "source" | "from" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "bytesNew" }>["from"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "call" }>, "kind" | "callee" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "callValue" }>, "kind" | "callee" | "receiver" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "caughtCheck" }>, "kind" | "value" | "className" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "caughtNarrow" }>, "kind" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "caughtTest" }>, "kind" | "value" | "test" | "className" | "negated" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "caughtTest" }>["negated"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "caughtToDyn" }>, "kind" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "chainRecv" }>, "kind" | "id" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "classRef" }>, "kind" | "className" | "captures" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "closure" }>, "kind" | "fnName" | "captures" | "adapts" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "closure" }>["adapts"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "downcast" }>, "kind" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynArrLit" }>, "kind" | "elems" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<NonNullable<Extract<NonNullable<Extract<IrExpr, { kind: "dynCall" }>["spreads"]>, readonly unknown[]>[number]>, object>, "arg" | "what">>,
|
||||
Unread<ValuesNotRead<Extract<NonNullable<Extract<NonNullable<Extract<IrExpr, { kind: "dynCall" }>["spreads"]>, readonly unknown[]>[number]>, object>["arg"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynCall" }>, "kind" | "callee" | "receiver" | "calleeName" | "calleeNameValue" | "args" | "spreads" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynCheck" }>, "kind" | "value" | "preserveRefs" | "adopt" | "exact" | "inferredSite" | "origin" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynCheck" }>["preserveRefs"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynCheck" }>["adopt"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynCheck" }>["exact"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynCheck" }>["origin"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynDestrCheck" }>, "kind" | "value" | "spelling" | "firstProp" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynFrom" }>, "kind" | "value" | "fnName" | "fnLength" | "liveRef" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynFrom" }>["fnLength"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynFrom" }>["liveRef"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynHasKey" }>, "kind" | "key" | "negated" | "value" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynHasKey" }>["negated"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynInvoke" }>, "kind" | "recv" | "method" | "calleeName" | "calleeNameValue" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynIterN" }>, "kind" | "value" | "count" | "notIterableMessage" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynIterN" }>["count"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynKeyGet" }>, "kind" | "key" | "optional" | "value" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynKeyGet" }>["optional"]>>,
|
||||
Unread<FieldsNotRead<Extract<NonNullable<Extract<NonNullable<Extract<IrExpr, { kind: "dynObjLit" }>["fields"]>, readonly unknown[]>[number]>, object>, "key" | "value">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynObjLit" }>, "kind" | "fields" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynScalarEq" }>, "kind" | "left" | "right" | "negated" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynScalarEq" }>["negated"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "dynTest" }>, "kind" | "test" | "bytesElem" | "negated" | "value" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "dynTest" }>["negated"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "ffiCall" }>, "kind" | "import" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "fieldAbsent" }>, "kind" | "unionId" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "fieldGet" }>, "kind" | "obj" | "className" | "field" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "fieldIncDec" }>, "kind" | "op" | "prefix" | "obj" | "className" | "field" | "fieldDyn" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "fieldIncDec" }>["prefix"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "fieldIncDec" }>["fieldDyn"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "genResume" }>, "kind" | "mode" | "gen" | "arg" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "incDec" }>, "kind" | "op" | "prefix" | "localId" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "incDec" }>["prefix"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "instanceOf" }>, "kind" | "value" | "className" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "instanceOfValue" }>, "kind" | "value" | "classValue" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "intrinsic" }>, "kind" | "name" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "jsonStringify" }>, "kind" | "value" | "indent" | "circular" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "jsonStringify" }>["circular"]>>,
|
||||
Unread<FieldsNotRead<Extract<NonNullable<Extract<IrExpr, { kind: "libCall" }>["assertOptions"]>, object>, "diff" | "skipPrototype">>,
|
||||
Unread<ValuesNotRead<Extract<NonNullable<Extract<IrExpr, { kind: "libCall" }>["assertOptions"]>, object>["skipPrototype"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "libCall" }>, "kind" | "fn" | "args" | "type" | "loc" | "prototypeIdentityOnly" | "realTimer" | "assertOptions">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "libCall" }>["prototypeIdentityOnly"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "libCall" }>["realTimer"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "logical" }>, "kind" | "op" | "left" | "right" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "mapIntrinsic" }>, "kind" | "method" | "receiver" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<NonNullable<Extract<NonNullable<Extract<IrExpr, { kind: "mapNew" }>["seed"]>, readonly unknown[]>[number]>, object>, "key" | "value">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "mapNew" }>, "kind" | "seed" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "moduleNsRef" }>, "kind" | "moduleId" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "new" }>, "kind" | "className" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "newPromise" }>, "kind" | "executor" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "newValue" }>, "kind" | "callee" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "nullish" }>, "kind" | "left" | "right" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "numLit" }>, "kind" | "value" | "spelling" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "numLit" }>["value"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "optChain" }>, "kind" | "id" | "receiver" | "body" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "orDefault" }>, "kind" | "left" | "right" | "retag" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "promiseVoidWiden" }>, "kind" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "promiseWithResolvers" }>, "kind" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<NonNullable<Extract<NonNullable<Extract<IrExpr, { kind: "recordClone" }>["overrides"]>, readonly unknown[]>[number]>, object>, "name" | "value">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "recordClone" }>, "kind" | "source" | "overrides" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "recordGet" }>, "kind" | "obj" | "shapeId" | "field" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "recordHas" }>, "kind" | "obj" | "shapeId" | "field" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "recordKeyGet" }>, "kind" | "obj" | "shapeId" | "key" | "overflowOnly" | "missNaN" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "recordKeyGet" }>["overflowOnly"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "recordKeyGet" }>["missNaN"]>>,
|
||||
Unread<FieldsNotRead<Extract<NonNullable<Extract<NonNullable<Extract<IrExpr, { kind: "recordLit" }>["fields"]>, readonly unknown[]>[number]>, object>, "name" | "value" | "overflow">>,
|
||||
Unread<ValuesNotRead<Extract<NonNullable<Extract<NonNullable<Extract<IrExpr, { kind: "recordLit" }>["fields"]>, readonly unknown[]>[number]>, object>["overflow"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "recordLit" }>, "kind" | "fields" | "nullProto" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "recordLit" }>["nullProto"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "recordNullProto" }>, "kind" | "obj" | "shapeId" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "recordOvfHas" }>, "kind" | "obj" | "shapeId" | "key" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "recordOvfKeys" }>, "kind" | "obj" | "shapeId" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "regexIntrinsic" }>, "kind" | "method" | "receiver" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "regexLit" }>, "kind" | "pattern" | "flags" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "selfRef" }>, "kind" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "seqExpr" }>, "kind" | "stmts" | "result" | "generatorDelegate" | "diverges" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "seqExpr" }>["generatorDelegate"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "seqExpr" }>["diverges"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "setIntrinsic" }>, "kind" | "method" | "receiver" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "setNew" }>, "kind" | "seed" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "strCmp" }>, "kind" | "op" | "left" | "right" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "strConcat" }>, "kind" | "left" | "right" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "strEq" }>, "kind" | "negated" | "left" | "right" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "strEq" }>["negated"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "strIntrinsic" }>, "kind" | "method" | "receiver" | "args" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "strLit" }>, "kind" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "templateStrings" }>, "kind" | "key" | "cooked" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "ternary" }>, "kind" | "cond" | "then" | "else_" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "toBool" }>, "kind" | "operand" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "toString" }>, "kind" | "operand" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unary" }>, "kind" | "op" | "operand" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unionDisc" }>, "kind" | "unionId" | "field" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unionEq" }>, "kind" | "unionId" | "negated" | "sameValue" | "left" | "right" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "unionEq" }>["negated"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "unionEq" }>["sameValue"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unionFuncEq" }>, "kind" | "unionId" | "tag" | "union" | "func" | "negated" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "unionFuncEq" }>["tag"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "unionFuncEq" }>["negated"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unionIsTag" }>, "kind" | "unionId" | "tag" | "negated" | "value" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "unionIsTag" }>["tag"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "unionIsTag" }>["negated"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unionKeyGet" }>, "kind" | "unionId" | "key" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unionNarrow" }>, "kind" | "unionId" | "tag" | "value" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "unionNarrow" }>["tag"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unionRetype" }>, "kind" | "fromId" | "unionId" | "tags" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unionWrap" }>, "kind" | "unionId" | "tag" | "value" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "unionWrap" }>["tag"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "unitLit" }>, "kind" | "unit" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "upcast" }>, "kind" | "value" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "varRef" }>, "kind" | "localId" | "type" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "virtualCall" }>, "kind" | "className" | "method" | "args" | "type" | "loc" | "targets">>,
|
||||
Unread<FieldsNotRead<Extract<NonNullable<Extract<IrExpr, { kind: "yieldExpr" }>["captureCompletion"]>, object>, "returnType">>,
|
||||
Unread<FieldsNotRead<Extract<IrExpr, { kind: "yieldExpr" }>, "kind" | "value" | "awaited" | "delegated" | "captureCompletion" | "type" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "yieldExpr" }>["awaited"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrExpr, { kind: "yieldExpr" }>["delegated"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "arrayDelete" }>, "kind" | "arr" | "index" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "arraySet" }>, "kind" | "arr" | "index" | "value" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "arraySetLength" }>, "kind" | "arr" | "length" | "loc" | "pop" | "reserve">>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "arraySetLength" }>["pop"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "arraySetLength" }>["reserve"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "arraySetUndefined" }>, "kind" | "arr" | "index" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "assign" }>, "kind" | "localId" | "value" | "initializes" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "assign" }>["initializes"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "block" }>, "kind" | "body" | "labels" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "break" }>, "kind" | "label" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "bytesSet" }>, "kind" | "arr" | "index" | "value" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "continue" }>, "kind" | "label" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "doWhile" }>, "kind" | "body" | "cond" | "labels" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "exprStmt" }>, "kind" | "expr" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "fieldSet" }>, "kind" | "obj" | "className" | "field" | "value" | "sloppy" | "define" | "attributesChecked" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "fieldSet" }>["sloppy"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "fieldSet" }>["define"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "fieldSet" }>["attributesChecked"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "for" }>, "kind" | "init" | "cond" | "update" | "body" | "labels" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "forOf" }>, "kind" | "localId" | "iterable" | "body" | "labels" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "if" }>, "kind" | "cond" | "then" | "else_" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "recordKeyDelete" }>, "kind" | "obj" | "shapeId" | "key" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "recordKeySet" }>, "kind" | "obj" | "shapeId" | "key" | "value" | "overflowOnly" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "recordKeySet" }>["overflowOnly"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "recordSet" }>, "kind" | "obj" | "shapeId" | "field" | "value" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "rethrow" }>, "kind" | "localId" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "return" }>, "kind" | "value" | "awaited" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "return" }>["awaited"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "runtimeFence" }>, "kind" | "code" | "message" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<NonNullable<Extract<NonNullable<Extract<IrStmt, { kind: "switch" }>["cases"]>, readonly unknown[]>[number]>, object>, "test" | "body">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "switch" }>, "kind" | "disc" | "cases" | "labels" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "throw" }>, "kind" | "value" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "tryCatch" }>, "kind" | "tryBody" | "catchBody" | "catchLocalId" | "finallyBody" | "suppressFinallyErrors" | "dropFinallyErrors" | "loc">>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "tryCatch" }>["suppressFinallyErrors"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrStmt, { kind: "tryCatch" }>["dropFinallyErrors"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "varDecl" }>, "kind" | "localId" | "init" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrStmt, { kind: "while" }>, "kind" | "cond" | "body" | "labels" | "loc">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "array" }>, "kind" | "elem" | "optionalElements">>,
|
||||
Unread<ValuesNotRead<Extract<IrType, { kind: "array" }>["optionalElements"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "bigint" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "bool" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "bytes" }>, "kind" | "elem">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "caught" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "child" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "childStream" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "childWriter" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "classval" }>, "kind" | "className">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "cryptoHash" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "cryptoHmac" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "date" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "dgramSocket" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "dir" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "dyn" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "f64" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "fileHandle" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "fsWatcher" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "func" }>, "kind" | "params" | "ret" | "rest" | "restAbi" | "argumentsAll">>,
|
||||
Unread<ValuesNotRead<Extract<IrType, { kind: "func" }>["rest"]>>,
|
||||
Unread<ValuesNotRead<Extract<IrType, { kind: "func" }>["argumentsAll"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "generator" }>, "kind" | "async" | "yieldT" | "retT" | "nextT">>,
|
||||
Unread<ValuesNotRead<Extract<IrType, { kind: "generator" }>["async"]>>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "http2Session" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "http2Stream" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "httpClientReq" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "httpReq" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "httpRes" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "jsval" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "map" }>, "kind" | "key" | "value">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "moduleNs" }>, "kind" | "moduleId">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "netServer" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "netSocket" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "nullT" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "object" }>, "kind" | "className">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "procStream" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "promise" }>, "kind" | "inner">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "record" }>, "kind" | "shapeId">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "regex" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "searchParams" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "secureCtx" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "set" }>, "kind" | "elem">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "spawnRes" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "stats" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "string" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "symbol" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "testCtx" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "undefinedT" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "union" }>, "kind" | "unionId">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "url" }>, "kind">>,
|
||||
Unread<FieldsNotRead<Extract<IrType, { kind: "void" }>, "kind">>,
|
||||
];
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8380-untyped-generator-loops.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8381-untyped-generator-stages.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -1518,7 +1518,11 @@ static void scr_switch(ScrCtx *from, ScrCtx *to, ScrFiber *to_fiber) {
|
||||
(void)to;
|
||||
scr_trap("scriptc: internal error: native stack switch reached on WASI\n");
|
||||
#elif defined(SCR_ASAN_FIBERS)
|
||||
void **save = from_fiber ? &from_fiber->fake_stack : &scr_main_fake_stack;
|
||||
/* A finished fiber leaves for the last time: a NULL slot has
|
||||
* AddressSanitizer free its fake stack, which otherwise stays mapped
|
||||
* until exit. */
|
||||
void **save = from_fiber ? (from_fiber->done ? NULL : &from_fiber->fake_stack)
|
||||
: &scr_main_fake_stack;
|
||||
const void *bottom = to_fiber ? to_fiber->stack : NULL;
|
||||
size_t size = to_fiber ? SCR_FIBER_STACK : 0;
|
||||
__sanitizer_start_switch_fiber(save, bottom, size);
|
||||
@@ -1527,10 +1531,13 @@ static void scr_switch(ScrCtx *from, ScrCtx *to, ScrFiber *to_fiber) {
|
||||
#else
|
||||
swapcontext(from, to);
|
||||
#endif
|
||||
/* Back in this frame: the context that saved into `save` above runs
|
||||
* again, whichever fiber `scr_current` names (a finished fiber returns
|
||||
* to its resumer with no fiber to switch to). */
|
||||
const void *old_bottom;
|
||||
size_t old_size;
|
||||
__sanitizer_finish_switch_fiber(
|
||||
scr_current ? scr_current->fake_stack : scr_main_fake_stack, &old_bottom, &old_size);
|
||||
__sanitizer_finish_switch_fiber(from_fiber ? from_fiber->fake_stack : scr_main_fake_stack,
|
||||
&old_bottom, &old_size);
|
||||
#elif defined(SCR_FAST_CTX)
|
||||
scr_ctx_swap(from, to);
|
||||
#else
|
||||
@@ -2228,6 +2235,7 @@ static void scr_prefix_trampoline(void) {
|
||||
scr_als_active = &f->als;
|
||||
entry(f, argpack);
|
||||
scr_current = carrier;
|
||||
carrier->done = true; /* its stack is freed once the spawner is back */
|
||||
scr_switch(&carrier->ctx, carrier->return_to, NULL);
|
||||
/* unreachable */
|
||||
}
|
||||
|
||||
@@ -706,31 +706,42 @@ static inline size_t scr_utf16_units_short(const char *data, size_t len) {
|
||||
}
|
||||
|
||||
/* A fresh concatenation (no head, the template-literal shape): numbers
|
||||
* format straight into the result, which reserves scr_f64_to_str's whole
|
||||
* 32-byte buffer for each. */
|
||||
* format into a stack buffer first, so the result is allocated at its exact
|
||||
* length. */
|
||||
#define SCR_F64_TEXT_MAX 32
|
||||
static __attribute__((noinline)) ScrStr *scr_str_concat_fresh(ScrStr *const *parts,
|
||||
const double *nums, size_t count) {
|
||||
size_t reserve = 0;
|
||||
char texts[SCR_STR_MIXED_MAX * SCR_F64_TEXT_MAX];
|
||||
size_t text_len[SCR_STR_MIXED_MAX];
|
||||
size_t len = 0, used = 0;
|
||||
bool ascii = true;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
size_t n = parts[i] ? parts[i]->len : SCR_F64_TEXT_MAX;
|
||||
if (n > SCR_STR_MAX_CAP - reserve) scr_oom();
|
||||
reserve += n;
|
||||
if (parts[i]) ascii &= parts[i]->ascii;
|
||||
size_t n;
|
||||
if (parts[i]) {
|
||||
n = parts[i]->len;
|
||||
ascii &= parts[i]->ascii;
|
||||
} else {
|
||||
n = scr_f64_to_str(nums[i], texts + used);
|
||||
text_len[i] = n;
|
||||
used += n;
|
||||
}
|
||||
if (n > SCR_STR_MAX_CAP - len) scr_oom();
|
||||
len += n;
|
||||
}
|
||||
ScrStr *out = scr_str_take_spare(reserve);
|
||||
if (!out) out = scr_str_alloc(reserve, reserve);
|
||||
ScrStr *out = scr_str_take_spare(len);
|
||||
if (!out) out = scr_str_alloc(len, len);
|
||||
char *w = out->data;
|
||||
const char *text = texts;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (parts[i]) {
|
||||
scr_copy_bytes(w, parts[i]->data, parts[i]->len);
|
||||
w += parts[i]->len;
|
||||
} else {
|
||||
w += scr_f64_to_str(nums[i], w);
|
||||
scr_copy_bytes(w, text, text_len[i]);
|
||||
w += text_len[i];
|
||||
text += text_len[i];
|
||||
}
|
||||
}
|
||||
size_t len = (size_t)(w - out->data);
|
||||
out->len = len;
|
||||
out->data[len] = '\0';
|
||||
out->ascii = ascii;
|
||||
|
||||
@@ -88,6 +88,13 @@ test("string methods match Node on committed oracle cases", async () => {
|
||||
expect(r.stderr.trim()).toMatch(/^(\d+)\/\1 cases passed$/);
|
||||
});
|
||||
|
||||
// A template literal of strings and numbers allocates exactly its length:
|
||||
// numbers format before the result is sized, not into a reserved maximum.
|
||||
test("template-literal concatenation allocates its exact length", async () => {
|
||||
const { stderr } = await execFileAsync(bin, ["--concat-capacity"]);
|
||||
expect(stderr.trim()).toBe("0 concat capacity failures");
|
||||
});
|
||||
|
||||
// repeat(count) with count < 0 or Infinity is Node's catchable RangeError
|
||||
// ("Invalid count value: -1"); the runtime leaves it pending.
|
||||
test.each([
|
||||
|
||||
@@ -982,7 +982,42 @@ static void ordering_asserts(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Template-literal concatenations (no head) of strings and numbers: the
|
||||
* result holds exactly its bytes, whatever the numbers format to. */
|
||||
static int check_concat_capacity(void) {
|
||||
ScrStr *a = scr_str_new("f", 1), *b = scr_str_new("_d", 2), *c = scr_str_new("", 0);
|
||||
struct {
|
||||
ScrStr *parts[4];
|
||||
double nums[4];
|
||||
size_t count;
|
||||
const char *expected;
|
||||
} cases[] = {
|
||||
{{a, NULL, b, NULL}, {0, 7, 0, 2230}, 4, "f7_d2230"},
|
||||
{{NULL, a}, {-1.5e-7, 0}, 2, "-1.5e-7f"},
|
||||
{{NULL, NULL, NULL}, {1.7976931348623157e308, 0.1, -0.0}, 3, "1.7976931348623157e+3080.10"},
|
||||
{{c, NULL, c}, {0, 42, 0}, 3, "42"},
|
||||
};
|
||||
int failed = 0;
|
||||
for (size_t i = 0; i < sizeof cases / sizeof cases[0]; i++) {
|
||||
ScrStr *r = scr_str_concat_mixed(NULL, cases[i].parts, cases[i].nums, cases[i].count);
|
||||
size_t want = strlen(cases[i].expected);
|
||||
if (r->len != want || memcmp(r->data, cases[i].expected, want) != 0 || r->data[want] != '\0' ||
|
||||
r->cap != r->len) {
|
||||
fprintf(stderr, "concat case %zu: got \"%.*s\" len %zu cap %zu, want \"%s\" len %zu\n", i,
|
||||
(int)r->len, r->data, r->len, (size_t)r->cap, cases[i].expected, want);
|
||||
failed++;
|
||||
}
|
||||
scr_str_release(r);
|
||||
}
|
||||
scr_str_release(a);
|
||||
scr_str_release(b);
|
||||
scr_str_release(c);
|
||||
fprintf(stderr, "%d concat capacity failures\n", failed);
|
||||
return failed != 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc > 1 && strcmp(argv[1], "--concat-capacity") == 0) return check_concat_capacity();
|
||||
if (argc > 1 && strncmp(argv[1], "--crash-repeat", 14) == 0) {
|
||||
ScrStr *s = scr_str_new("ab", 2);
|
||||
double count = strcmp(argv[1], "--crash-repeat-inf") == 0
|
||||
|
||||
+264
-183
@@ -14,202 +14,283 @@ const require = createRequire(join(root, "package.json"));
|
||||
const ts = require("typescript");
|
||||
const irPath = join(root, "packages/compiler/src/ir/ir.ts");
|
||||
const target = join(root, "packages/compiler/src/ir/fields.generated.ts");
|
||||
const samePath = (a, b) => a.replace(/\\/g, "/") === b.replace(/\\/g, "/");
|
||||
|
||||
const program = ts.createProgram([irPath], { strict: true, target: ts.ScriptTarget.ES2022 });
|
||||
const checker = program.getTypeChecker();
|
||||
const source = program.getSourceFile(irPath);
|
||||
const aliases = new Map();
|
||||
ts.forEachChild(source, (node) => {
|
||||
if (ts.isTypeAliasDeclaration(node)) aliases.set(node.name.text, node);
|
||||
});
|
||||
/** The text of fields.generated.ts for `irText`, the source of ir.ts. */
|
||||
export function generateIrFields(irText = readFileSync(irPath, "utf8")) {
|
||||
const options = { strict: true, target: ts.ScriptTarget.ES2022 };
|
||||
const host = ts.createCompilerHost(options);
|
||||
const readSourceFile = host.getSourceFile;
|
||||
// ir.ts is read from `irText`; the files it imports from disk.
|
||||
host.getSourceFile = (file, languageVersion, ...rest) =>
|
||||
samePath(file, irPath)
|
||||
? ts.createSourceFile(file, irText, languageVersion)
|
||||
: readSourceFile.call(host, file, languageVersion, ...rest);
|
||||
const program = ts.createProgram([irPath], options, host);
|
||||
const checker = program.getTypeChecker();
|
||||
const source = program.getSourceFile(irPath);
|
||||
const aliases = new Map();
|
||||
ts.forEachChild(source, (node) => {
|
||||
if (ts.isTypeAliasDeclaration(node)) aliases.set(node.name.text, node);
|
||||
});
|
||||
|
||||
/** Whether a value of `type` can be a string. */
|
||||
function holdsString(type) {
|
||||
if (type.isUnion()) return type.types.some(holdsString);
|
||||
return (type.flags & (ts.TypeFlags.StringLike | ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
|
||||
}
|
||||
|
||||
/** Whether every value of `type` is a string. */
|
||||
function onlyString(type) {
|
||||
if (type.isUnion()) return type.types.every(onlyString);
|
||||
return (type.flags & ts.TypeFlags.StringLike) !== 0;
|
||||
}
|
||||
|
||||
/** Each kind's string fields, in declaration order, merged over the unions:
|
||||
* a kind both unions declare lists the fields of both. */
|
||||
const kinds = new Map();
|
||||
for (const name of ["IrExpr", "IrStmt"]) {
|
||||
const alias = aliases.get(name);
|
||||
const union = checker.getTypeAtLocation(alias.name);
|
||||
for (const member of union.isUnion() ? union.types : [union]) {
|
||||
const kindSymbol = member.getProperty("kind");
|
||||
const kindType = checker.getTypeOfSymbolAtLocation(kindSymbol, alias);
|
||||
if (!kindType.isStringLiteral()) throw new Error(`${name} member without a literal kind`);
|
||||
const fields = kinds.get(kindType.value) ?? [];
|
||||
for (const property of member.getProperties()) {
|
||||
const type = checker.getTypeOfSymbolAtLocation(property, alias);
|
||||
if (!holdsString(type)) continue;
|
||||
const known = fields.find((field) => field.name === property.name);
|
||||
const always = onlyString(type) && (property.flags & ts.SymbolFlags.Optional) === 0;
|
||||
if (known === undefined) fields.push({ name: property.name, always });
|
||||
else known.always &&= always;
|
||||
}
|
||||
kinds.set(kindType.value, fields);
|
||||
/** Whether a value of `type` can be a string. */
|
||||
function holdsString(type) {
|
||||
if (type.isUnion()) return type.types.some(holdsString);
|
||||
return (type.flags & (ts.TypeFlags.StringLike | ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
|
||||
}
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"// Generated from packages/compiler/src/ir/ir.ts by scripts/generate-ir-fields.mjs.",
|
||||
"// Run `pnpm generate:ir-fields` after changing the fields of an IR node.",
|
||||
'import type { IrExpr, IrStmt, IrType } from "./ir.js";',
|
||||
"",
|
||||
"/** Calls `visit` with each string field of `node` (including `kind`) and its",
|
||||
" * name: the string values `for (const key in node)` finds on a node, read",
|
||||
" * by name in the order ir.ts declares them. */",
|
||||
"export function eachStringField(",
|
||||
" node: IrExpr | IrStmt,",
|
||||
" visit: (value: string, field: string) => void,",
|
||||
"): void {",
|
||||
" switch (node.kind) {",
|
||||
];
|
||||
for (const [kind, fields] of [...kinds].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
|
||||
lines.push(` case ${JSON.stringify(kind)}: {`);
|
||||
for (const { name, always } of fields) {
|
||||
const read = `node.${name}`;
|
||||
if (always) lines.push(` visit(${read}, ${JSON.stringify(name)});`);
|
||||
else
|
||||
lines.push(` if (typeof ${read} === "string") visit(${read}, ${JSON.stringify(name)});`);
|
||||
/** Whether every value of `type` is a string. */
|
||||
function onlyString(type) {
|
||||
if (type.isUnion()) return type.types.every(onlyString);
|
||||
return (type.flags & ts.TypeFlags.StringLike) !== 0;
|
||||
}
|
||||
lines.push(" return;", " }");
|
||||
}
|
||||
lines.push(" }", "}", "");
|
||||
|
||||
// The deep walkers: every field of every object under an IR node, as a
|
||||
// recursive key walk finds them, read by name.
|
||||
const members = new Map();
|
||||
for (const name of ["IrExpr", "IrStmt", "IrType"]) {
|
||||
const union = checker.getTypeAtLocation(aliases.get(name).name);
|
||||
members.set(name, new Set(union.isUnion() ? union.types : [union]));
|
||||
}
|
||||
const nullish = ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void;
|
||||
const scalar =
|
||||
ts.TypeFlags.NumberLike |
|
||||
ts.TypeFlags.BooleanLike |
|
||||
ts.TypeFlags.BigIntLike |
|
||||
ts.TypeFlags.ESSymbolLike;
|
||||
|
||||
/** The walker an IR union member belongs to, or null. */
|
||||
function irKind(type) {
|
||||
for (const [name, set] of members) if (set.has(type)) return name;
|
||||
return null;
|
||||
}
|
||||
|
||||
let temp = 0;
|
||||
/** Lines that walk `expr` of `type`: strings to `walker.string`, IR nodes to
|
||||
* their walkers, arrays by element and other objects by field. */
|
||||
function walkLines(expr, type, indent, depth) {
|
||||
if (depth > 12) throw new Error(`field type nests too deep at ${expr}`);
|
||||
const parts = type.isUnion() ? type.types : [type];
|
||||
const present = parts.filter((part) => (part.flags & nullish) === 0);
|
||||
const optional = present.length !== parts.length;
|
||||
const strings = present.filter((part) => (part.flags & ts.TypeFlags.StringLike) !== 0);
|
||||
const objects = present.filter((part) => (part.flags & (ts.TypeFlags.StringLike | scalar)) === 0);
|
||||
for (const part of present)
|
||||
if ((part.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)
|
||||
throw new Error(`untyped field ${expr}`);
|
||||
const body = [];
|
||||
if (objects.length > 0) {
|
||||
const kinds = new Set(objects.map(irKind));
|
||||
if (kinds.size === 1 && !kinds.has(null)) {
|
||||
const [kind] = kinds;
|
||||
body.push(`walk${kind.slice(2)}Fields(${expr}, walker, skipType);`);
|
||||
} else if (objects.every((part) => checker.isArrayType(part))) {
|
||||
if (objects.length !== 1) throw new Error(`several array types at ${expr}`);
|
||||
const item = `item${temp++}`;
|
||||
body.push(`for (const ${item} of ${expr}) {`);
|
||||
body.push(...walkLines(item, checker.getTypeArguments(objects[0])[0], " ", depth + 1));
|
||||
body.push("}");
|
||||
} else if (kinds.has(null) && objects.length === 1) {
|
||||
for (const property of checker.getPropertiesOfType(objects[0])) {
|
||||
if (property.name === "loc") continue;
|
||||
const fieldType = checker.getTypeOfSymbol(property);
|
||||
const field = `${expr}.${property.name}`;
|
||||
const lines = walkLines(field, fieldType, "", depth + 1);
|
||||
if (lines.length === 0) continue;
|
||||
if (property.name === "type")
|
||||
body.push("if (!skipType) {", ...lines.map((l) => ` ${l}`), "}");
|
||||
else body.push(...lines);
|
||||
/** Each kind's string fields, in declaration order, merged over the unions:
|
||||
* a kind both unions declare lists the fields of both. */
|
||||
const kinds = new Map();
|
||||
for (const name of ["IrExpr", "IrStmt"]) {
|
||||
const alias = aliases.get(name);
|
||||
const union = checker.getTypeAtLocation(alias.name);
|
||||
for (const member of union.isUnion() ? union.types : [union]) {
|
||||
const kindSymbol = member.getProperty("kind");
|
||||
const kindType = checker.getTypeOfSymbolAtLocation(kindSymbol, alias);
|
||||
if (!kindType.isStringLiteral()) throw new Error(`${name} member without a literal kind`);
|
||||
const fields = kinds.get(kindType.value) ?? [];
|
||||
for (const property of member.getProperties()) {
|
||||
const type = checker.getTypeOfSymbolAtLocation(property, alias);
|
||||
if (!holdsString(type)) continue;
|
||||
const known = fields.find((field) => field.name === property.name);
|
||||
const always = onlyString(type) && (property.flags & ts.SymbolFlags.Optional) === 0;
|
||||
if (known === undefined) fields.push({ name: property.name, always });
|
||||
else known.always &&= always;
|
||||
}
|
||||
} else throw new Error(`mixed object types at ${expr}: ${checker.typeToString(type)}`);
|
||||
kinds.set(kindType.value, fields);
|
||||
}
|
||||
}
|
||||
let lines = body;
|
||||
if (strings.length > 0 && objects.length > 0)
|
||||
lines = [
|
||||
`if (typeof ${expr} === "string") walker.string(${expr});`,
|
||||
...(body.length > 0 ? [`else {`, ...body.map((l) => ` ${l}`), "}"] : []),
|
||||
];
|
||||
else if (strings.length > 0) lines = [`walker.string(${expr});`];
|
||||
if (lines.length > 0 && optional && !(strings.length > 0 && objects.length > 0))
|
||||
lines = [
|
||||
`if (${expr} !== undefined && ${expr} !== null) {`,
|
||||
...lines.map((l) => ` ${l}`),
|
||||
"}",
|
||||
];
|
||||
else if (lines.length > 0 && optional)
|
||||
lines = [
|
||||
`if (${expr} !== undefined && ${expr} !== null) {`,
|
||||
...lines.map((l) => ` ${l}`),
|
||||
"}",
|
||||
];
|
||||
return lines.map((line) => indent + line);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
"/** What a field walk reports (walkExprFields): each expression and",
|
||||
" * statement node before its fields, and each string a field holds. */",
|
||||
"export interface IrFieldWalker {",
|
||||
" node(node: IrExpr | IrStmt): void;",
|
||||
" string(value: string): void;",
|
||||
"}",
|
||||
"",
|
||||
);
|
||||
for (const name of ["IrExpr", "IrStmt", "IrType"]) {
|
||||
const short = name.slice(2);
|
||||
const nodeCall = name === "IrType" ? [] : [" walker.node(node);"];
|
||||
lines.push(
|
||||
`/** Walks every field of \`node\` but \`loc\` (and \`type\` with \`skipType\`), and`,
|
||||
" * every field of each object under it: the strings and nodes a recursive",
|
||||
" * `for (const key in value)` walk with the same skips finds, read by name in",
|
||||
" * the order ir.ts declares the fields. */",
|
||||
`export function walk${short}Fields(node: ${name}, walker: IrFieldWalker, skipType: boolean): void {`,
|
||||
...nodeCall,
|
||||
const lines = [
|
||||
"// Generated from packages/compiler/src/ir/ir.ts by scripts/generate-ir-fields.mjs.",
|
||||
"// Run `pnpm generate:ir-fields` after changing the fields of an IR node.",
|
||||
'import type { IrExpr, IrStmt, IrType } from "./ir.js";',
|
||||
"",
|
||||
"/** Calls `visit` with each string field of `node` (including `kind`) and its",
|
||||
" * name: the string values `for (const key in node)` finds on a node, read",
|
||||
" * by name in the order ir.ts declares them. */",
|
||||
"export function eachStringField(",
|
||||
" node: IrExpr | IrStmt,",
|
||||
" visit: (value: string, field: string) => void,",
|
||||
"): void {",
|
||||
" switch (node.kind) {",
|
||||
);
|
||||
const byKind = new Map();
|
||||
for (const member of members.get(name)) {
|
||||
const kind = checker.getTypeOfSymbol(member.getProperty("kind")).value;
|
||||
if (byKind.has(kind)) throw new Error(`${name} kind ${kind} declared twice`);
|
||||
byKind.set(kind, member);
|
||||
}
|
||||
for (const [kind, member] of [...byKind].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
|
||||
];
|
||||
for (const [kind, fields] of [...kinds].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
|
||||
lines.push(` case ${JSON.stringify(kind)}: {`);
|
||||
for (const property of member.getProperties()) {
|
||||
if (property.name === "loc") continue;
|
||||
const fieldType = checker.getTypeOfSymbol(property);
|
||||
const field = `node.${property.name}`;
|
||||
const walk = walkLines(field, fieldType, "", 0);
|
||||
if (walk.length === 0) continue;
|
||||
if (property.name === "type")
|
||||
lines.push(" if (!skipType) {", ...walk.map((l) => ` ${l}`), " }");
|
||||
else lines.push(...walk.map((l) => ` ${l}`));
|
||||
for (const { name, always } of fields) {
|
||||
const read = `node.${name}`;
|
||||
if (always) lines.push(` visit(${read}, ${JSON.stringify(name)});`);
|
||||
else
|
||||
lines.push(
|
||||
` if (typeof ${read} === "string") visit(${read}, ${JSON.stringify(name)});`,
|
||||
);
|
||||
}
|
||||
lines.push(" return;", " }");
|
||||
}
|
||||
lines.push(" }", "}", "");
|
||||
}
|
||||
const text = lines.join("\n");
|
||||
|
||||
if (process.argv.includes("--check")) {
|
||||
if (readFileSync(target, "utf8") !== text)
|
||||
throw new Error("fields.generated.ts is stale; run node scripts/generate-ir-fields.mjs");
|
||||
} else writeFileSync(target, text);
|
||||
// The deep walkers: every field of every object under an IR node, as a
|
||||
// recursive key walk finds them, read by name.
|
||||
const members = new Map();
|
||||
for (const name of ["IrExpr", "IrStmt", "IrType"]) {
|
||||
const union = checker.getTypeAtLocation(aliases.get(name).name);
|
||||
members.set(name, new Set(union.isUnion() ? union.types : [union]));
|
||||
}
|
||||
const nullish = ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void;
|
||||
const scalar =
|
||||
ts.TypeFlags.NumberLike |
|
||||
ts.TypeFlags.BooleanLike |
|
||||
ts.TypeFlags.BigIntLike |
|
||||
ts.TypeFlags.ESSymbolLike;
|
||||
|
||||
/** Whether every value of `type` is a number, boolean, bigint, symbol, null
|
||||
* or undefined. A record of such fields walks to nothing too, but it is an
|
||||
* object: its own fields are checked instead. */
|
||||
function onlyScalar(type) {
|
||||
const parts = type.isUnion() ? type.types : [type];
|
||||
return parts.every((part) => (part.flags & (scalar | nullish)) !== 0);
|
||||
}
|
||||
|
||||
/** The walker an IR union member belongs to, or null. */
|
||||
function irKind(type) {
|
||||
for (const [name, set] of members) if (set.has(type)) return name;
|
||||
return null;
|
||||
}
|
||||
|
||||
let temp = 0;
|
||||
/** The fields each walked object type declares, for the type-level check at
|
||||
* the end of the file: `typeExpr` names the object type, `fields` every
|
||||
* field it had when the file was generated and `scalars` the fields that held
|
||||
* no string and no object then. */
|
||||
const checks = [];
|
||||
/** Records `property list` of `object` (named `typeExpr`) for the check. */
|
||||
function noteFields(typeExpr, properties, scalarNames) {
|
||||
checks.push({
|
||||
typeExpr,
|
||||
fields: properties.map((property) => property.name),
|
||||
scalars: scalarNames,
|
||||
});
|
||||
}
|
||||
/** Lines that walk `expr` of `type` (named `typeExpr` in the generated file):
|
||||
* strings to `walker.string`, IR nodes to their walkers, arrays by element and
|
||||
* other objects by field. */
|
||||
function walkLines(expr, type, typeExpr, indent, depth) {
|
||||
if (depth > 12) throw new Error(`field type nests too deep at ${expr}`);
|
||||
const parts = type.isUnion() ? type.types : [type];
|
||||
const present = parts.filter((part) => (part.flags & nullish) === 0);
|
||||
const optional = present.length !== parts.length;
|
||||
const strings = present.filter((part) => (part.flags & ts.TypeFlags.StringLike) !== 0);
|
||||
const objects = present.filter(
|
||||
(part) => (part.flags & (ts.TypeFlags.StringLike | scalar)) === 0,
|
||||
);
|
||||
for (const part of present)
|
||||
if ((part.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)
|
||||
throw new Error(`untyped field ${expr}`);
|
||||
const body = [];
|
||||
if (objects.length > 0) {
|
||||
const kinds = new Set(objects.map(irKind));
|
||||
if (kinds.size === 1 && !kinds.has(null)) {
|
||||
const [kind] = kinds;
|
||||
body.push(`walk${kind.slice(2)}Fields(${expr}, walker, skipType);`);
|
||||
} else if (objects.every((part) => checker.isArrayType(part))) {
|
||||
if (objects.length !== 1) throw new Error(`several array types at ${expr}`);
|
||||
const item = `item${temp++}`;
|
||||
body.push(`for (const ${item} of ${expr}) {`);
|
||||
const itemType = `Extract<NonNullable<${typeExpr}>, readonly unknown[]>[number]`;
|
||||
body.push(
|
||||
...walkLines(item, checker.getTypeArguments(objects[0])[0], itemType, " ", depth + 1),
|
||||
);
|
||||
body.push("}");
|
||||
} else if (kinds.has(null) && objects.length === 1) {
|
||||
const recordType = `Extract<NonNullable<${typeExpr}>, object>`;
|
||||
const properties = checker.getPropertiesOfType(objects[0]);
|
||||
const scalarNames = [];
|
||||
for (const property of properties) {
|
||||
if (property.name === "loc") continue;
|
||||
const fieldType = checker.getTypeOfSymbol(property);
|
||||
const field = `${expr}.${property.name}`;
|
||||
const fieldTypeExpr = `${recordType}[${JSON.stringify(property.name)}]`;
|
||||
const lines = walkLines(field, fieldType, fieldTypeExpr, "", depth + 1);
|
||||
if (onlyScalar(fieldType)) scalarNames.push(property.name);
|
||||
if (lines.length === 0) continue;
|
||||
if (property.name === "type")
|
||||
body.push("if (!skipType) {", ...lines.map((l) => ` ${l}`), "}");
|
||||
else body.push(...lines);
|
||||
}
|
||||
noteFields(recordType, properties, scalarNames);
|
||||
} else throw new Error(`mixed object types at ${expr}: ${checker.typeToString(type)}`);
|
||||
}
|
||||
let lines = body;
|
||||
if (strings.length > 0 && objects.length > 0)
|
||||
lines = [
|
||||
`if (typeof ${expr} === "string") walker.string(${expr});`,
|
||||
...(body.length > 0 ? [`else {`, ...body.map((l) => ` ${l}`), "}"] : []),
|
||||
];
|
||||
else if (strings.length > 0) lines = [`walker.string(${expr});`];
|
||||
if (lines.length > 0 && optional && !(strings.length > 0 && objects.length > 0))
|
||||
lines = [
|
||||
`if (${expr} !== undefined && ${expr} !== null) {`,
|
||||
...lines.map((l) => ` ${l}`),
|
||||
"}",
|
||||
];
|
||||
else if (lines.length > 0 && optional)
|
||||
lines = [
|
||||
`if (${expr} !== undefined && ${expr} !== null) {`,
|
||||
...lines.map((l) => ` ${l}`),
|
||||
"}",
|
||||
];
|
||||
return lines.map((line) => indent + line);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
"/** What a field walk reports (walkExprFields): each expression and",
|
||||
" * statement node before its fields, and each string a field holds. */",
|
||||
"export interface IrFieldWalker {",
|
||||
" node(node: IrExpr | IrStmt): void;",
|
||||
" string(value: string): void;",
|
||||
"}",
|
||||
"",
|
||||
);
|
||||
for (const name of ["IrExpr", "IrStmt", "IrType"]) {
|
||||
const short = name.slice(2);
|
||||
const nodeCall = name === "IrType" ? [] : [" walker.node(node);"];
|
||||
lines.push(
|
||||
`/** Walks every field of \`node\` but \`loc\` (and \`type\` with \`skipType\`), and`,
|
||||
" * every field of each object under it: the strings and nodes a recursive",
|
||||
" * `for (const key in value)` walk with the same skips finds, read by name in",
|
||||
" * the order ir.ts declares the fields. */",
|
||||
`export function walk${short}Fields(node: ${name}, walker: IrFieldWalker, skipType: boolean): void {`,
|
||||
...nodeCall,
|
||||
" switch (node.kind) {",
|
||||
);
|
||||
const byKind = new Map();
|
||||
for (const member of members.get(name)) {
|
||||
const kind = checker.getTypeOfSymbol(member.getProperty("kind")).value;
|
||||
if (byKind.has(kind)) throw new Error(`${name} kind ${kind} declared twice`);
|
||||
byKind.set(kind, member);
|
||||
}
|
||||
for (const [kind, member] of [...byKind].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
|
||||
lines.push(` case ${JSON.stringify(kind)}: {`);
|
||||
const memberType = `Extract<${name}, { kind: ${JSON.stringify(kind)} }>`;
|
||||
const scalarNames = [];
|
||||
for (const property of member.getProperties()) {
|
||||
if (property.name === "loc") continue;
|
||||
const fieldType = checker.getTypeOfSymbol(property);
|
||||
const field = `node.${property.name}`;
|
||||
const fieldTypeExpr = `${memberType}[${JSON.stringify(property.name)}]`;
|
||||
const walk = walkLines(field, fieldType, fieldTypeExpr, "", 0);
|
||||
if (onlyScalar(fieldType)) scalarNames.push(property.name);
|
||||
if (walk.length === 0) continue;
|
||||
if (property.name === "type")
|
||||
lines.push(" if (!skipType) {", ...walk.map((l) => ` ${l}`), " }");
|
||||
else lines.push(...walk.map((l) => ` ${l}`));
|
||||
}
|
||||
lines.push(" return;", " }");
|
||||
noteFields(memberType, member.getProperties(), scalarNames);
|
||||
}
|
||||
lines.push(" }", "}", "");
|
||||
}
|
||||
|
||||
// Type checking fails below when ir.ts gives a walked object a field this
|
||||
// file does not know, or lets a field that held no string and no object hold
|
||||
// one before this file is regenerated.
|
||||
const quoted = (names) => names.map((name) => JSON.stringify(name)).join(" | ");
|
||||
lines.push(
|
||||
"/** A field ir.ts declares that this file does not read. */",
|
||||
"type FieldsNotRead<T, Read extends PropertyKey> = Exclude<keyof T, Read>;",
|
||||
"/** The string or object values a field read as a scalar can hold. */",
|
||||
"type ValuesNotRead<T> = Extract<NonNullable<T>, string | object>;",
|
||||
"/** Fails type checking unless `T` is never: run `pnpm generate:ir-fields`. */",
|
||||
"type Unread<T extends never> = T;",
|
||||
"",
|
||||
"/** Every walked object type's fields as ir.ts declared them when this file",
|
||||
" * was generated. */",
|
||||
"export type IrFieldsCurrent = [",
|
||||
);
|
||||
for (const { typeExpr, fields, scalars } of checks) {
|
||||
lines.push(
|
||||
` Unread<FieldsNotRead<${typeExpr}, ${fields.length > 0 ? quoted(fields) : "never"}>>,`,
|
||||
);
|
||||
for (const scalarName of scalars)
|
||||
lines.push(` Unread<ValuesNotRead<${typeExpr}[${JSON.stringify(scalarName)}]>>,`);
|
||||
}
|
||||
lines.push("];", "");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
if (process.argv[1] !== undefined && samePath(process.argv[1], fileURLToPath(import.meta.url))) {
|
||||
const text = generateIrFields();
|
||||
|
||||
if (process.argv.includes("--check")) {
|
||||
if (readFileSync(target, "utf8") !== text)
|
||||
throw new Error("fields.generated.ts is stale; run node scripts/generate-ir-fields.mjs");
|
||||
} else writeFileSync(target, text);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// Untyped for-of and for await loops over generator objects: values,
|
||||
// completion values, early exits, errors, other iterables through the same
|
||||
// loop, and the microtask order of each step.
|
||||
function* numbers(n) {
|
||||
for (let i = 0; i < n; i++) yield i;
|
||||
return "done";
|
||||
}
|
||||
function* mixed() {
|
||||
yield "a";
|
||||
yield { k: 1 };
|
||||
yield undefined;
|
||||
yield [1, 2];
|
||||
yield Promise.resolve(7);
|
||||
return 99;
|
||||
}
|
||||
async function* asyncNumbers(n) {
|
||||
for (let i = 0; i < n; i++) yield i * 10;
|
||||
return "async done";
|
||||
}
|
||||
async function* asyncMixed() {
|
||||
yield "x";
|
||||
yield Promise.resolve(8);
|
||||
yield null;
|
||||
}
|
||||
|
||||
function collect(it) {
|
||||
const out = [];
|
||||
for (const x of it) out.push(x);
|
||||
return out;
|
||||
}
|
||||
async function collectAsync(it) {
|
||||
const out = [];
|
||||
for await (const x of it) out.push(x);
|
||||
return out;
|
||||
}
|
||||
const show = (v) => JSON.stringify(v, (k, x) => (x instanceof Promise ? "promise" : x === undefined ? "undef" : x));
|
||||
|
||||
async function main() {
|
||||
// The same loops over generators, arrays, Maps, strings and custom iterators.
|
||||
console.log("sync", show(collect(numbers(4))), show(collect(mixed())));
|
||||
console.log("sync other", show(collect([1, 2])), show(collect(new Map([[1, 2]]))), show(collect("hé")));
|
||||
const custom = {
|
||||
[Symbol.iterator]() {
|
||||
let i = 0;
|
||||
return { next: () => ({ value: i * i, done: i++ > 2 }) };
|
||||
},
|
||||
};
|
||||
console.log("sync custom", show(collect(custom)));
|
||||
console.log("async", show(await collectAsync(asyncNumbers(3))), show(await collectAsync(asyncMixed())));
|
||||
console.log("async over sync", show(await collectAsync(mixed())), show(await collectAsync(numbers(3))));
|
||||
console.log("async other", show(await collectAsync([Promise.resolve(1), 2])), show(await collectAsync(custom)));
|
||||
const asyncCustom = {
|
||||
[Symbol.asyncIterator]() {
|
||||
let i = 0;
|
||||
return { next: async () => ({ value: `c${i}`, done: i++ > 1 }) };
|
||||
},
|
||||
};
|
||||
console.log("async custom", show(await collectAsync(asyncCustom)));
|
||||
// Symbol.asyncIterator returning a sync generator: each result is awaited,
|
||||
// its value is not, so a yielded promise reaches the loop as a promise.
|
||||
const viaAsyncIterator = { [Symbol.asyncIterator]: () => mixed() };
|
||||
const seen = [];
|
||||
for await (const x of viaAsyncIterator) seen.push(x instanceof Promise ? `promise ${await x}` : x);
|
||||
console.log("async iterator sync", show(seen));
|
||||
|
||||
// A generator that has started keeps its position; one that finished stays done.
|
||||
const started = numbers(5);
|
||||
started.next();
|
||||
console.log("started", show(collect(started)), show(started.next()));
|
||||
|
||||
// Early exits close the generator: finally blocks run once.
|
||||
function* guarded(label) {
|
||||
try {
|
||||
yield 1;
|
||||
yield 2;
|
||||
} finally {
|
||||
console.log(label, "finally");
|
||||
}
|
||||
}
|
||||
const pick = (v) => v;
|
||||
for (const x of pick(guarded("break"))) {
|
||||
console.log("break at", x);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
for (const x of pick(guarded("throw"))) throw new Error(`body ${x}`);
|
||||
} catch (e) {
|
||||
console.log("caught", e.message);
|
||||
}
|
||||
const g = pick(guarded("after"));
|
||||
for (const x of g) {
|
||||
if (x === 1) break;
|
||||
}
|
||||
console.log("after break", show(g.next()));
|
||||
async function* aguarded(label) {
|
||||
try {
|
||||
yield 1;
|
||||
yield 2;
|
||||
} finally {
|
||||
console.log(label, "finally");
|
||||
}
|
||||
}
|
||||
for await (const x of pick(aguarded("abreak"))) {
|
||||
console.log("abreak at", x);
|
||||
break;
|
||||
}
|
||||
for await (const x of pick(guarded("sync abreak"))) {
|
||||
console.log("sync abreak at", x);
|
||||
break;
|
||||
}
|
||||
|
||||
// Errors from the generator body reach the loop.
|
||||
function* failing() {
|
||||
yield 1;
|
||||
throw new TypeError("generator failed");
|
||||
}
|
||||
async function* afailing() {
|
||||
yield 1;
|
||||
throw new RangeError("async generator failed");
|
||||
}
|
||||
try {
|
||||
for (const x of pick(failing())) console.log("failing got", x);
|
||||
} catch (e) {
|
||||
console.log("caught", e.name, e.message);
|
||||
}
|
||||
try {
|
||||
for await (const x of pick(afailing())) console.log("afailing got", x);
|
||||
} catch (e) {
|
||||
console.log("caught", e.name, e.message);
|
||||
}
|
||||
// Microtask order: each step against a promise chain ticking alongside.
|
||||
const ticker = (label, n) => {
|
||||
let p = Promise.resolve();
|
||||
for (let i = 0; i < n; i++) p = p.then(() => console.log(label, "tick", i));
|
||||
return p;
|
||||
};
|
||||
const consume = async (label, it) => {
|
||||
for await (const x of it) console.log(label, "value", x);
|
||||
console.log(label, "end");
|
||||
};
|
||||
await Promise.all([consume("sync", pick(numbers(3))), ticker("sync", 10)]);
|
||||
await Promise.all([consume("async", pick(asyncNumbers(3))), ticker("async", 10)]);
|
||||
await Promise.all([consume("promises", pick(mixed())), ticker("promises", 14)]);
|
||||
|
||||
// Stages chained through untyped parameters.
|
||||
async function* double(src) {
|
||||
for await (const x of src) yield x * 2;
|
||||
}
|
||||
async function* keepOdd(src) {
|
||||
for await (const x of src) if (x % 4 !== 0) yield x;
|
||||
}
|
||||
let sum = 0;
|
||||
for await (const x of keepOdd(double(asyncNumbers(50)))) sum += x;
|
||||
console.log("stages", sum);
|
||||
let syncSum = 0;
|
||||
function* inc(src) {
|
||||
for (const x of src) yield x + 1;
|
||||
}
|
||||
for (const x of inc(inc(numbers(100)))) syncSum += x;
|
||||
console.log("sync stages", syncSum);
|
||||
}
|
||||
main();
|
||||
@@ -0,0 +1,82 @@
|
||||
// Untyped stages chained through generator objects: a sync generator source
|
||||
// consumed with for await, async generator transforms, an async function
|
||||
// consumer, and the same stages over arrays and custom iterables.
|
||||
function run(source, transform, destination) {
|
||||
return destination(transform(source()));
|
||||
}
|
||||
const N = 2000;
|
||||
const total = await run(
|
||||
function* () {
|
||||
for (let i = 0; i < N; i++) yield i;
|
||||
},
|
||||
async function* (src) {
|
||||
for await (const n of src) yield n * 2;
|
||||
},
|
||||
async function (src) {
|
||||
let sum = 0;
|
||||
for await (const n of src) sum += n;
|
||||
return sum;
|
||||
},
|
||||
);
|
||||
console.log("total", total);
|
||||
|
||||
async function* map(it, f) {
|
||||
for await (const x of it) yield await f(x);
|
||||
}
|
||||
async function* take(it, n) {
|
||||
if (n <= 0) return;
|
||||
for await (const x of it) {
|
||||
yield x;
|
||||
if (--n === 0) return;
|
||||
}
|
||||
}
|
||||
async function* source(n) {
|
||||
try {
|
||||
for (let i = 0; i < n; i++) yield i;
|
||||
} finally {
|
||||
console.log("source closed");
|
||||
}
|
||||
}
|
||||
async function toArray(it) {
|
||||
const out = [];
|
||||
for await (const x of it) out.push(x);
|
||||
return out;
|
||||
}
|
||||
const half = async (x) => x / 2;
|
||||
console.log(await toArray(take(map(source(10), half), 3)));
|
||||
console.log(await toArray(map([1, 2, 3], half)));
|
||||
console.log(await toArray(map(new Set(["a", "b"]), async (s) => s.toUpperCase())));
|
||||
const words = {
|
||||
*[Symbol.iterator]() {
|
||||
yield "x";
|
||||
yield "y";
|
||||
},
|
||||
};
|
||||
console.log(await toArray(map(words, async (s) => s + s)));
|
||||
|
||||
// A stage that switches iterables between calls of the same loop.
|
||||
function* letters() {
|
||||
yield "p";
|
||||
yield "q";
|
||||
}
|
||||
async function* numbersAsync() {
|
||||
yield 1;
|
||||
yield 2;
|
||||
}
|
||||
const sources = [letters(), numbersAsync(), ["z"], letters()];
|
||||
for (const s of sources) console.log(await toArray(s));
|
||||
|
||||
// Sync stages: generator to generator through untyped parameters.
|
||||
function* inc(src) {
|
||||
for (const x of src) yield x + 1;
|
||||
}
|
||||
function* evens(src) {
|
||||
for (const x of src) if (x % 2 === 0) yield x;
|
||||
}
|
||||
function* range(n) {
|
||||
for (let i = 0; i < n; i++) yield i;
|
||||
return "range done";
|
||||
}
|
||||
let s = 0;
|
||||
for (const x of evens(inc(range(1000)))) s += x;
|
||||
console.log("sync", s, [...inc([10, 20])], Array.from(evens(range(7))));
|
||||
@@ -0,0 +1,60 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import ts from "typescript";
|
||||
import { expect, test } from "vitest";
|
||||
import { generateIrFields } from "../../scripts/generate-ir-fields.mjs";
|
||||
|
||||
const compilerDir = join(import.meta.dirname, "../../packages/compiler");
|
||||
const irPath = join(compilerDir, "src/ir/ir.ts");
|
||||
const generatedPath = join(compilerDir, "src/ir/fields.generated.ts");
|
||||
const irText = readFileSync(irPath, "utf8");
|
||||
const arrayGet = '{ kind: "arrayGet"; arr: IrExpr; index: IrExpr; type: IrType; loc: SrcLoc }';
|
||||
|
||||
/** ir.ts with `field` added to the arrayGet node. */
|
||||
function withArrayGetField(field: string): string {
|
||||
expect(irText).toContain(arrayGet);
|
||||
return irText.replace(arrayGet, arrayGet.replace("loc: SrcLoc }", `loc: SrcLoc; ${field} }`));
|
||||
}
|
||||
|
||||
/** The type errors of `generated` as fields.generated.ts, with `ir` as ir.ts,
|
||||
* under the compiler package's options. */
|
||||
function generatedErrors(ir: string, generated: string): string[] {
|
||||
const parsed = ts.getParsedCommandLineOfConfigFile(
|
||||
join(compilerDir, "tsconfig.json"),
|
||||
{ noEmit: true, incremental: false },
|
||||
{ ...ts.sys, onUnRecoverableConfigFileDiagnostic: () => {} },
|
||||
)!;
|
||||
const host = ts.createCompilerHost(parsed.options);
|
||||
const readSourceFile = host.getSourceFile;
|
||||
host.getSourceFile = (file, languageVersion, ...rest) =>
|
||||
file === irPath
|
||||
? ts.createSourceFile(file, ir, languageVersion)
|
||||
: file === generatedPath
|
||||
? ts.createSourceFile(file, generated, languageVersion)
|
||||
: readSourceFile.call(host, file, languageVersion, ...rest);
|
||||
const program = ts.createProgram([generatedPath], parsed.options, host);
|
||||
return ts
|
||||
.getPreEmitDiagnostics(program, program.getSourceFile(generatedPath))
|
||||
.map((d) => ts.flattenDiagnosticMessageText(d.messageText, "\n"));
|
||||
}
|
||||
|
||||
test("a nested record of scalar fields passes the checks once the readers are regenerated", () => {
|
||||
const ir = withArrayGetField("counts?: { hits: number; misses: number | undefined }");
|
||||
expect(generatedErrors(ir, generateIrFields(ir))).toEqual([]);
|
||||
});
|
||||
|
||||
test("the record's own fields are checked: an added field or a widened scalar fails with its name", () => {
|
||||
const generated = generateIrFields(
|
||||
withArrayGetField("counts?: { hits: number; misses: number | undefined }"),
|
||||
);
|
||||
const added = generatedErrors(
|
||||
withArrayGetField("counts?: { hits: number; misses: number | undefined; label: string }"),
|
||||
generated,
|
||||
);
|
||||
expect(added).toEqual([`Type '"label"' does not satisfy the constraint 'never'.`]);
|
||||
const widened = generatedErrors(
|
||||
withArrayGetField("counts?: { hits: number | string; misses: number | undefined }"),
|
||||
generated,
|
||||
);
|
||||
expect(widened).toEqual([`Type 'string' does not satisfy the constraint 'never'.`]);
|
||||
});
|
||||
@@ -59,7 +59,9 @@ test("the production layout and metadata stage lowers statically", async () => {
|
||||
});
|
||||
|
||||
for (const backend of ["llvm"] as const) {
|
||||
test(`native LLVM layout and metadata emission (${backend})`, async () => {
|
||||
// The sanitized dev build of the stage takes 170 to 300 s of CI time beside
|
||||
// its shard's other tests.
|
||||
test(`native LLVM layout and metadata emission (${backend})`, { timeout: 600_000 }, async () => {
|
||||
const dir = mkdtempSync(
|
||||
join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-native-emission-"),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user