diff --git a/packages/compiler/src/ir/fields.generated.ts b/packages/compiler/src/ir/fields.generated.ts index e9f5efaa3..86c6a15e5 100644 --- a/packages/compiler/src/ir/fields.generated.ts +++ b/packages/compiler/src/ir/fields.generated.ts @@ -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 = Exclude; +/** The string or object values a field read as a scalar can hold. */ +type ValuesNotRead = Extract, string | object>; +/** Fails type checking unless `T` is never: run `pnpm generate:ir-fields`. */ +type Unread = T; + +/** Every walked object type's fields as ir.ts declared them when this file + * was generated. */ +export type IrFieldsCurrent = [ + Unread, "kind" | "method" | "receiver" | "args" | "type" | "loc">>, + Unread, "kind" | "arr" | "index" | "type" | "loc">>, + Unread, "kind" | "arr" | "index" | "type" | "loc">>, + Unread, "kind" | "elems" | "spreads" | "capacity" | "type" | "loc">>, + Unread["capacity"]>>, + Unread, "kind" | "length" | "type" | "loc">>, + Unread, "kind" | "arr" | "index" | "type" | "loc">>, + Unread, "kind" | "localId" | "value" | "type" | "loc">>, + Unread, "kind" | "value" | "type" | "adopt" | "loc">>, + Unread["adopt"]>>, + Unread, "kind" | "value" | "promiseTag" | "type" | "loc">>, + Unread["promiseTag"]>>, + Unread, "kind" | "op" | "left" | "right" | "type" | "loc">>, + Unread, "kind" | "value" | "type" | "loc">>, + Unread["value"]>>, + Unread, "kind" | "method" | "receiver" | "args" | "type" | "loc" | "invalidNaN">>, + Unread["invalidNaN"]>>, + Unread, "kind" | "source" | "from" | "type" | "loc">>, + Unread["from"]>>, + Unread, "kind" | "callee" | "args" | "type" | "loc">>, + Unread, "kind" | "callee" | "receiver" | "args" | "type" | "loc">>, + Unread, "kind" | "value" | "className" | "type" | "loc">>, + Unread, "kind" | "value" | "type" | "loc">>, + Unread, "kind" | "value" | "test" | "className" | "negated" | "type" | "loc">>, + Unread["negated"]>>, + Unread, "kind" | "value" | "type" | "loc">>, + Unread, "kind" | "id" | "type" | "loc">>, + Unread, "kind" | "className" | "captures" | "type" | "loc">>, + Unread, "kind" | "fnName" | "captures" | "adapts" | "type" | "loc">>, + Unread["adapts"]>>, + Unread, "kind" | "value" | "type" | "loc">>, + Unread, "kind" | "elems" | "type" | "loc">>, + Unread["spreads"]>, readonly unknown[]>[number]>, object>, "arg" | "what">>, + Unread["spreads"]>, readonly unknown[]>[number]>, object>["arg"]>>, + Unread, "kind" | "callee" | "receiver" | "calleeName" | "calleeNameValue" | "args" | "spreads" | "type" | "loc">>, + Unread, "kind" | "value" | "preserveRefs" | "adopt" | "exact" | "inferredSite" | "origin" | "type" | "loc">>, + Unread["preserveRefs"]>>, + Unread["adopt"]>>, + Unread["exact"]>>, + Unread["origin"]>>, + Unread, "kind" | "value" | "spelling" | "firstProp" | "type" | "loc">>, + Unread, "kind" | "value" | "fnName" | "fnLength" | "liveRef" | "type" | "loc">>, + Unread["fnLength"]>>, + Unread["liveRef"]>>, + Unread, "kind" | "key" | "negated" | "value" | "type" | "loc">>, + Unread["negated"]>>, + Unread, "kind" | "recv" | "method" | "calleeName" | "calleeNameValue" | "args" | "type" | "loc">>, + Unread, "kind" | "value" | "count" | "notIterableMessage" | "type" | "loc">>, + Unread["count"]>>, + Unread, "kind" | "key" | "optional" | "value" | "type" | "loc">>, + Unread["optional"]>>, + Unread["fields"]>, readonly unknown[]>[number]>, object>, "key" | "value">>, + Unread, "kind" | "fields" | "type" | "loc">>, + Unread, "kind" | "left" | "right" | "negated" | "type" | "loc">>, + Unread["negated"]>>, + Unread, "kind" | "test" | "bytesElem" | "negated" | "value" | "type" | "loc">>, + Unread["negated"]>>, + Unread, "kind" | "import" | "args" | "type" | "loc">>, + Unread, "kind" | "unionId" | "type" | "loc">>, + Unread, "kind" | "obj" | "className" | "field" | "type" | "loc">>, + Unread, "kind" | "op" | "prefix" | "obj" | "className" | "field" | "fieldDyn" | "type" | "loc">>, + Unread["prefix"]>>, + Unread["fieldDyn"]>>, + Unread, "kind" | "mode" | "gen" | "arg" | "type" | "loc">>, + Unread, "kind" | "op" | "prefix" | "localId" | "type" | "loc">>, + Unread["prefix"]>>, + Unread, "kind" | "value" | "className" | "type" | "loc">>, + Unread, "kind" | "value" | "classValue" | "type" | "loc">>, + Unread, "kind" | "name" | "args" | "type" | "loc">>, + Unread, "kind" | "value" | "indent" | "circular" | "type" | "loc">>, + Unread["circular"]>>, + Unread["assertOptions"]>, object>, "diff" | "skipPrototype">>, + Unread["assertOptions"]>, object>["skipPrototype"]>>, + Unread, "kind" | "fn" | "args" | "type" | "loc" | "prototypeIdentityOnly" | "realTimer" | "assertOptions">>, + Unread["prototypeIdentityOnly"]>>, + Unread["realTimer"]>>, + Unread, "kind" | "op" | "left" | "right" | "type" | "loc">>, + Unread, "kind" | "method" | "receiver" | "args" | "type" | "loc">>, + Unread["seed"]>, readonly unknown[]>[number]>, object>, "key" | "value">>, + Unread, "kind" | "seed" | "type" | "loc">>, + Unread, "kind" | "moduleId" | "type" | "loc">>, + Unread, "kind" | "className" | "args" | "type" | "loc">>, + Unread, "kind" | "executor" | "type" | "loc">>, + Unread, "kind" | "callee" | "args" | "type" | "loc">>, + Unread, "kind" | "left" | "right" | "type" | "loc">>, + Unread, "kind" | "value" | "spelling" | "type" | "loc">>, + Unread["value"]>>, + Unread, "kind" | "id" | "receiver" | "body" | "type" | "loc">>, + Unread, "kind" | "left" | "right" | "retag" | "type" | "loc">>, + Unread, "kind" | "value" | "type" | "loc">>, + Unread, "kind" | "type" | "loc">>, + Unread["overrides"]>, readonly unknown[]>[number]>, object>, "name" | "value">>, + Unread, "kind" | "source" | "overrides" | "type" | "loc">>, + Unread, "kind" | "obj" | "shapeId" | "field" | "type" | "loc">>, + Unread, "kind" | "obj" | "shapeId" | "field" | "type" | "loc">>, + Unread, "kind" | "obj" | "shapeId" | "key" | "overflowOnly" | "missNaN" | "type" | "loc">>, + Unread["overflowOnly"]>>, + Unread["missNaN"]>>, + Unread["fields"]>, readonly unknown[]>[number]>, object>, "name" | "value" | "overflow">>, + Unread["fields"]>, readonly unknown[]>[number]>, object>["overflow"]>>, + Unread, "kind" | "fields" | "nullProto" | "type" | "loc">>, + Unread["nullProto"]>>, + Unread, "kind" | "obj" | "shapeId" | "type" | "loc">>, + Unread, "kind" | "obj" | "shapeId" | "key" | "type" | "loc">>, + Unread, "kind" | "obj" | "shapeId" | "type" | "loc">>, + Unread, "kind" | "method" | "receiver" | "args" | "type" | "loc">>, + Unread, "kind" | "pattern" | "flags" | "type" | "loc">>, + Unread, "kind" | "type" | "loc">>, + Unread, "kind" | "stmts" | "result" | "generatorDelegate" | "diverges" | "type" | "loc">>, + Unread["generatorDelegate"]>>, + Unread["diverges"]>>, + Unread, "kind" | "method" | "receiver" | "args" | "type" | "loc">>, + Unread, "kind" | "seed" | "type" | "loc">>, + Unread, "kind" | "op" | "left" | "right" | "type" | "loc">>, + Unread, "kind" | "left" | "right" | "type" | "loc">>, + Unread, "kind" | "negated" | "left" | "right" | "type" | "loc">>, + Unread["negated"]>>, + Unread, "kind" | "method" | "receiver" | "args" | "type" | "loc">>, + Unread, "kind" | "value" | "type" | "loc">>, + Unread, "kind" | "key" | "cooked" | "type" | "loc">>, + Unread, "kind" | "cond" | "then" | "else_" | "type" | "loc">>, + Unread, "kind" | "operand" | "type" | "loc">>, + Unread, "kind" | "operand" | "type" | "loc">>, + Unread, "kind" | "op" | "operand" | "type" | "loc">>, + Unread, "kind" | "unionId" | "field" | "value" | "type" | "loc">>, + Unread, "kind" | "unionId" | "negated" | "sameValue" | "left" | "right" | "type" | "loc">>, + Unread["negated"]>>, + Unread["sameValue"]>>, + Unread, "kind" | "unionId" | "tag" | "union" | "func" | "negated" | "type" | "loc">>, + Unread["tag"]>>, + Unread["negated"]>>, + Unread, "kind" | "unionId" | "tag" | "negated" | "value" | "type" | "loc">>, + Unread["tag"]>>, + Unread["negated"]>>, + Unread, "kind" | "unionId" | "key" | "value" | "type" | "loc">>, + Unread, "kind" | "unionId" | "tag" | "value" | "type" | "loc">>, + Unread["tag"]>>, + Unread, "kind" | "fromId" | "unionId" | "tags" | "value" | "type" | "loc">>, + Unread, "kind" | "unionId" | "tag" | "value" | "type" | "loc">>, + Unread["tag"]>>, + Unread, "kind" | "unit" | "type" | "loc">>, + Unread, "kind" | "value" | "type" | "loc">>, + Unread, "kind" | "localId" | "type" | "loc">>, + Unread, "kind" | "className" | "method" | "args" | "type" | "loc" | "targets">>, + Unread["captureCompletion"]>, object>, "returnType">>, + Unread, "kind" | "value" | "awaited" | "delegated" | "captureCompletion" | "type" | "loc">>, + Unread["awaited"]>>, + Unread["delegated"]>>, + Unread, "kind" | "arr" | "index" | "loc">>, + Unread, "kind" | "arr" | "index" | "value" | "loc">>, + Unread, "kind" | "arr" | "length" | "loc" | "pop" | "reserve">>, + Unread["pop"]>>, + Unread["reserve"]>>, + Unread, "kind" | "arr" | "index" | "loc">>, + Unread, "kind" | "localId" | "value" | "initializes" | "loc">>, + Unread["initializes"]>>, + Unread, "kind" | "body" | "labels" | "loc">>, + Unread, "kind" | "label" | "loc">>, + Unread, "kind" | "arr" | "index" | "value" | "loc">>, + Unread, "kind" | "label" | "loc">>, + Unread, "kind" | "body" | "cond" | "labels" | "loc">>, + Unread, "kind" | "expr" | "loc">>, + Unread, "kind" | "obj" | "className" | "field" | "value" | "sloppy" | "define" | "attributesChecked" | "loc">>, + Unread["sloppy"]>>, + Unread["define"]>>, + Unread["attributesChecked"]>>, + Unread, "kind" | "init" | "cond" | "update" | "body" | "labels" | "loc">>, + Unread, "kind" | "localId" | "iterable" | "body" | "labels" | "loc">>, + Unread, "kind" | "cond" | "then" | "else_" | "loc">>, + Unread, "kind" | "obj" | "shapeId" | "key" | "loc">>, + Unread, "kind" | "obj" | "shapeId" | "key" | "value" | "overflowOnly" | "loc">>, + Unread["overflowOnly"]>>, + Unread, "kind" | "obj" | "shapeId" | "field" | "value" | "loc">>, + Unread, "kind" | "localId" | "loc">>, + Unread, "kind" | "value" | "awaited" | "loc">>, + Unread["awaited"]>>, + Unread, "kind" | "code" | "message" | "loc">>, + Unread["cases"]>, readonly unknown[]>[number]>, object>, "test" | "body">>, + Unread, "kind" | "disc" | "cases" | "labels" | "loc">>, + Unread, "kind" | "value" | "loc">>, + Unread, "kind" | "tryBody" | "catchBody" | "catchLocalId" | "finallyBody" | "suppressFinallyErrors" | "dropFinallyErrors" | "loc">>, + Unread["suppressFinallyErrors"]>>, + Unread["dropFinallyErrors"]>>, + Unread, "kind" | "localId" | "init" | "loc">>, + Unread, "kind" | "cond" | "body" | "labels" | "loc">>, + Unread, "kind" | "elem" | "optionalElements">>, + Unread["optionalElements"]>>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind" | "elem">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind" | "className">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind" | "params" | "ret" | "rest" | "restAbi" | "argumentsAll">>, + Unread["rest"]>>, + Unread["argumentsAll"]>>, + Unread, "kind" | "async" | "yieldT" | "retT" | "nextT">>, + Unread["async"]>>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind" | "key" | "value">>, + Unread, "kind" | "moduleId">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind" | "className">>, + Unread, "kind">>, + Unread, "kind" | "inner">>, + Unread, "kind" | "shapeId">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind" | "elem">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind">>, + Unread, "kind" | "unionId">>, + Unread, "kind">>, + Unread, "kind">>, +]; diff --git a/scripts/generate-ir-fields.mjs b/scripts/generate-ir-fields.mjs index 5b5ad011a..037cb615d 100644 --- a/scripts/generate-ir-fields.mjs +++ b/scripts/generate-ir-fields.mjs @@ -105,9 +105,23 @@ function irKind(type) { } 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) { +/** 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); @@ -127,19 +141,28 @@ function walkLines(expr, type, indent, depth) { 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)); + const itemType = `Extract, 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) { - for (const property of checker.getPropertiesOfType(objects[0])) { + const recordType = `Extract, 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 lines = walkLines(field, fieldType, "", depth + 1); + const fieldTypeExpr = `${recordType}[${JSON.stringify(property.name)}]`; + const lines = walkLines(field, fieldType, fieldTypeExpr, "", depth + 1); + if (lines.length === 0) 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; @@ -193,20 +216,50 @@ for (const name of ["IrExpr", "IrStmt", "IrType"]) { } 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 walk = walkLines(field, fieldType, "", 0); + const fieldTypeExpr = `${memberType}[${JSON.stringify(property.name)}]`; + const walk = walkLines(field, fieldType, fieldTypeExpr, "", 0); + if (walk.length === 0) 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 = Exclude;", + "/** The string or object values a field read as a scalar can hold. */", + "type ValuesNotRead = Extract, string | object>;", + "/** Fails type checking unless `T` is never: run `pnpm generate:ir-fields`. */", + "type Unread = 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 0 ? quoted(fields) : "never"}>>,`, + ); + for (const scalarName of scalars) + lines.push(` Unread>,`); +} +lines.push("];", ""); const text = lines.join("\n"); if (process.argv.includes("--check")) {