diff --git a/graph/bundle/build.ts b/graph/bundle/build.ts index b2a2a60c..7e4eae26 100644 --- a/graph/bundle/build.ts +++ b/graph/bundle/build.ts @@ -199,6 +199,15 @@ export async function buildCore(inp: BuildInputs): Promise { wantMethods.add(r[1] as string); if (r[2] !== null) wantFields.add(r[2] as string); } + // A relation may NAME a field without being a field access, and such a row is exactly a + // statement that the field matters. Counting only field_access left those ids resolving + // to nothing in `fields` (#890). Which relations do this is declared by the adapter. + for (const fr of inp.adapter.raw.fieldRefs ?? []) { + for await (const r of readRaw(path.join(inp.rawDir, fr.file))) { + const id = r[fr.column]; + if (id && id !== NONE) wantFields.add(id); + } + } // ── 3. client entities (all of them — the client is the subject) ────────── const methods = new Map(); diff --git a/graph/bundle/languages.ts b/graph/bundle/languages.ts index 4750c9b6..3899c1ed 100644 --- a/graph/bundle/languages.ts +++ b/graph/bundle/languages.ts @@ -95,6 +95,17 @@ export interface LanguageAdapter { fieldAccess?: RawSource; /** (ref, owner, ownerKind, enclType, enclMethod, type, prov, context, depth, tier) — #663 */ typeUse?: RawSource; + /** + * Relations that NAME a field without being a field access. `fields` lists every client + * field, but a LIBRARY field only when something references it, and that something was + * `fieldAccess` alone. A relation like a config binding says the field matters and then + * could not resolve it, so its rows pointed at ids the table does not hold (#890). + * + * Each entry is a raw relation plus the column holding the field id. Declared per + * language because which relations name a field is a language's own business, and + * build.ts stays neutral. + */ + fieldRefs?: { file: string; column: number }[]; }; ir: { methods: MethodsIR; @@ -126,6 +137,10 @@ const JAVA: LanguageAdapter = { fieldAccess: { file: 'field-access.csv', columns: [0, 1, 2, 3, 4, 5] }, // ref, type, context, depth, ownerKind, owner, enclMethod, enclType, typeProv, tier typeUse: { file: 'type-use.csv', columns: [0, 5, 7, 8, 2, 1, 4, 3, 6, 9] }, + // config_binding(Key, Mechanism, TargetKind, Target, Owner): column 3 is the bound + // field when TargetKind is "field". A row naming a library field is why that field + // has to be listed at all. + fieldRefs: [{ file: 'config-binding.csv', column: 3 }], }, ir: { methods: {