mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-15 04:18:05 +00:00
feat(r): resolve the S4 VIRTUAL heritage sentinel
setClass(x, contains = "VIRTUAL", ...) names a base no fixture ever declares, so the generic scope-based heritage lookup (scope-model-only, never consults graph nodes) can't find a binding for it. Add emitRVirtualHeritageEdges, mirroring Ruby's emitRubyMixinEdges shape: it mints the placeholder VIRTUAL Class node once and an EXTENDS edge from every S4 class whose contains= names it, scoped narrowly to that one literal fixture string. All 39/39 r.test.ts scenarios now pass.
This commit is contained in:
parent
658e5ce840
commit
d2be5fd3d5
1 changed files with 80 additions and 5 deletions
|
|
@ -6,22 +6,94 @@
|
|||
* (`import-resolvers/r.ts`), the adapter step 2 of this plan added over the
|
||||
* existing `resolveRImportInternal`, with the repo's local R packages
|
||||
* threaded through `loadRPackageConfig`. Heritage (R6 `inherit=`, S4
|
||||
* `contains=`) rides the generic `@reference.inherits` mechanism — R6/S4
|
||||
* base names are ordinary scope-bound class names, unlike Ruby's
|
||||
* `include`/`extend`, which are method calls and need a custom
|
||||
* `emitHeritageEdges` hook.
|
||||
* `contains=`) mostly rides the generic `@reference.inherits` mechanism —
|
||||
* R6/S4 base names are ordinary scope-bound class names, unlike Ruby's
|
||||
* `include`/`extend`, which are method calls. The one exception is S4's
|
||||
* `contains = "VIRTUAL"` sentinel (`setClass(x, contains = "VIRTUAL", ...)`,
|
||||
* R's spelling for an abstract base): no fixture ever declares
|
||||
* `setClass("VIRTUAL", ...)`, so the generic scope-based lookup
|
||||
* (`resolveInheritanceBaseInScope` / `findClassBindingInScope`) can never
|
||||
* find a binding for it — that lookup is scope-model-only and never
|
||||
* consults graph nodes directly. `emitRVirtualHeritageEdges` below mints
|
||||
* the placeholder `VIRTUAL` graph node and its EXTENDS edge directly,
|
||||
* mirroring Ruby's `emitRubyMixinEdges` shape for the same reason: a
|
||||
* heritage target this plan's fixtures name but never declare.
|
||||
*/
|
||||
|
||||
import type { ParsedFile, ScopeId } from 'gitnexus-shared';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { buildMro, defaultLinearize } from '../../scope-resolution/passes/mro.js';
|
||||
import { populateClassOwnedMembers } from '../../scope-resolution/scope/walkers.js';
|
||||
import { findEnclosingClassDef, populateClassOwnedMembers } from '../../scope-resolution/scope/walkers.js';
|
||||
import type { ScopeResolver } from '../../scope-resolution/contract/scope-resolver.js';
|
||||
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
||||
import { resolveDefGraphId } from '../../scope-resolution/graph-bridge/ids.js';
|
||||
import type { GraphNodeLookup } from '../../scope-resolution/graph-bridge/node-lookup.js';
|
||||
import type { KnowledgeGraph } from '../../../graph/types.js';
|
||||
import { generateId } from '../../../../lib/utils.js';
|
||||
import { loadRPackageConfig } from '../../language-config.js';
|
||||
import { resolveRImportTarget } from '../../import-resolvers/r.js';
|
||||
import { rProvider } from '../r.js';
|
||||
import { rArityCompatibility, rMergeBindings } from './simple-hooks.js';
|
||||
|
||||
/** Literal string `"VIRTUAL"` graph id — one node shared by every S4 class
|
||||
* that names it as a base, minted lazily on first use. */
|
||||
const VIRTUAL_CLASS_NAME = 'VIRTUAL';
|
||||
|
||||
/**
|
||||
* Mint the `VIRTUAL` placeholder Class node (once) and an EXTENDS edge from
|
||||
* every S4 class whose `contains=` names it, exactly the literal fixture
|
||||
* string `"VIRTUAL"` (§12 Q1) — not a general policy for arbitrary S4
|
||||
* base-type sentinels (`"numeric"`, `"list"`, …), which is a distinct,
|
||||
* out-of-scope design question (kaybenleroll/GitNexus, Issues To File #2).
|
||||
*
|
||||
* Idempotent: re-seeds its dedup set from any `EXTENDS` edge already
|
||||
* targeting a node named `VIRTUAL` before emitting, and re-uses the graph's
|
||||
* existing `VIRTUAL` node instead of minting a second one when the
|
||||
* orchestrator re-runs resolution.
|
||||
*/
|
||||
function emitRVirtualHeritageEdges(
|
||||
graph: KnowledgeGraph,
|
||||
_parsedFiles: readonly ParsedFile[],
|
||||
nodeLookup: GraphNodeLookup,
|
||||
scopes?: ScopeResolutionIndexes,
|
||||
): void {
|
||||
if (scopes === undefined) return;
|
||||
|
||||
const virtualGraphId = generateId('Class', VIRTUAL_CLASS_NAME);
|
||||
const emitted = new Set<string>();
|
||||
for (const rel of graph.iterRelationshipsByType('EXTENDS')) {
|
||||
if (rel.targetId === virtualGraphId) emitted.add(rel.sourceId);
|
||||
}
|
||||
|
||||
let virtualNodeMinted = graph.getNode(virtualGraphId) !== undefined;
|
||||
|
||||
for (const site of scopes.referenceSites) {
|
||||
if (site.kind !== 'inherits' || site.name !== VIRTUAL_CLASS_NAME) continue;
|
||||
const callerClass = findEnclosingClassDef(site.inScope, scopes);
|
||||
if (callerClass === undefined) continue;
|
||||
const callerGraphId = resolveDefGraphId(callerClass.filePath, callerClass, nodeLookup);
|
||||
if (callerGraphId === undefined || emitted.has(callerGraphId)) continue;
|
||||
|
||||
if (!virtualNodeMinted) {
|
||||
graph.addNode({
|
||||
id: virtualGraphId,
|
||||
label: 'Class',
|
||||
properties: { name: VIRTUAL_CLASS_NAME, filePath: callerClass.filePath },
|
||||
});
|
||||
virtualNodeMinted = true;
|
||||
}
|
||||
emitted.add(callerGraphId);
|
||||
graph.addRelationship({
|
||||
id: generateId('EXTENDS', `${callerGraphId}->${virtualGraphId}`),
|
||||
sourceId: callerGraphId,
|
||||
targetId: virtualGraphId,
|
||||
type: 'EXTENDS',
|
||||
confidence: 0.85,
|
||||
reason: 'scope-resolution: inherits (virtual)',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all top-level names exported from a target module scope's file.
|
||||
* `library()`/`require()` are R's wildcard imports — every top-level name of
|
||||
|
|
@ -69,6 +141,9 @@ export const rScopeResolver: ScopeResolver = {
|
|||
|
||||
populateOwners: (parsed: ParsedFile) => populateClassOwnedMembers(parsed),
|
||||
|
||||
emitHeritageEdges: (graph, parsedFiles, nodeLookup, scopes) =>
|
||||
emitRVirtualHeritageEdges(graph, parsedFiles, nodeLookup, scopes),
|
||||
|
||||
// R has no `super`.
|
||||
isSuperReceiver: () => false,
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue