From ff8ce7613cee88dcd2f647b27fb539ed98186b76 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Thu, 7 May 2026 01:59:19 +0200 Subject: [PATCH] Let drill-in work on external classes that have local instances MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit drillInto used to gate on classById.has(n.id()) and silently fall through to highlightOnly when the double-clicked class wasn't locally defined. That's the wrong call when the class is external but instances of it *are* in our dumps (orphan-style, or because the package defining the instances was extracted while the package defining the class wasn't) — there's still a useful instance view to render. Two changes: * drillInto now drills in whenever the class is locally defined OR has at least one entry in instancesByClass. When neither is true (truly nothing to show), it console.warns with the qid so you can diagnose why your double-click did nothing. * buildInstanceView synthesises a minimal ClassInfo from the first matching instance's iiClass when classById misses, so the rest of the build pipeline (which needs ciSuperclasses, ciAssocTypes, ciTyVars …) keeps working with empty defaults. The focused-class node ends up as an external-styled stub but the per-instance rendering — contexts, superclass requirements, fam-instances — is all there. Co-Authored-By: Claude Opus 4.7 (1M context) --- data/viewer.js | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/data/viewer.js b/data/viewer.js index 4208694..31adcd0 100644 --- a/data/viewer.js +++ b/data/viewer.js @@ -526,9 +526,29 @@ } function buildInstanceView(classId) { - const cls = classById.get(classId); - if (!cls) return null; + let cls = classById.get(classId); const all = instancesByClass.get(classId) || []; + if (!cls) { + // External class — its defining package wasn't extracted, but + // we may still have instances of it (orphan-style, or because + // the package that *defines instances* was extracted while the + // package that *defines the class* wasn't). Synthesise a + // minimal ClassInfo from the first instance's iiClass so the + // rest of the code path keeps working. Superclasses/methods/ + // associated families are unknown; we leave them empty. + if (all.length === 0) return null; + const first = all[0]; + cls = { + ciName: first.iiClass, + ciTyVars: [], + ciSuperclasses: [], + ciAssocTypes: [], + ciMethods: [], + ciSrc: null, + ciDoc: null, + _externalShim: true, + }; + } const hidden = getHidden(classId); const insts = all.filter(i => !hidden.has(i._idx)); @@ -2214,10 +2234,23 @@ pinClass(n.id()); return; } - // Class node → drill into its instance view. - if (data.kind === 'class' && !data.focused && classById.has(n.id())) { - switchToInstance(n.id()); - return; + // Class node → drill into its instance view. We *don't* gate on + // classById.has(n.id()) here — buildInstanceView falls back to a + // synthesised ClassInfo when the class is external but local + // instances exist, so external classes that only show up via + // someone else's superclass theta still drill in usefully. + if (data.kind === 'class' && !data.focused) { + const haveLocalDef = classById.has(n.id()); + const localInstCount = (instancesByClass.get(n.id()) || []).length; + if (haveLocalDef || localInstCount > 0) { + switchToInstance(n.id()); + return; + } + // No instances and no local definition either — nothing to show. + // Warn so the user can diagnose why their double-click did + // nothing instead of being silently confused. + console.warn('classgraph: nothing to drill into for', n.id(), + '— class is external and no instances of it appear in the dumps.'); } // Family node → drill into its family view. if (data.kind === 'family' && !data.focused && familyById.has(n.id())) { -- 2.54.0