Fail type checking when the generated IR field readers miss a field

fields.generated.ts reads IR nodes' fields by name, so a field ir.ts gains
after the last `pnpm generate:ir-fields` is skipped by the walkers until
someone regenerates the file; `--check` in CI reports it, but only on the
run that looks.

The generator now ends the file with type-level checks, one per node kind
and per nested record the walkers enumerate: a field outside the list the
file was generated from fails type checking with the field's name, and so
does a field read as a scalar (no string, no object) that can now hold a
string or an object. Fields the file reads already fail type checking when
they are removed or change type. The checks are types only and emit no
code.
This commit is contained in:
Chris Tate
2026-10-26 21:06:07 -05:00
parent 807f5f6712
commit 07f159accd
2 changed files with 309 additions and 7 deletions
@@ -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">>,
];
+60 -7
View File
@@ -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<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) {
for (const property of checker.getPropertiesOfType(objects[0])) {
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 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<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("];", "");
const text = lines.join("\n");
if (process.argv.includes("--check")) {