mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
Merge branch 'main' into fix/fts-non-fatal-in-analyze
This commit is contained in:
commit
7b75cfb585
1 changed files with 96 additions and 2 deletions
|
|
@ -1,6 +1,10 @@
|
|||
import { SupportedLanguages, type ParsedFile } from 'gitnexus-shared';
|
||||
import type { KnowledgeGraph } from '../../../graph/types.js';
|
||||
import { buildMro, defaultLinearize } from '../../scope-resolution/passes/mro.js';
|
||||
import type { ScopeResolver } from '../../scope-resolution/contract/scope-resolver.js';
|
||||
import { resolveDefGraphId } from '../../scope-resolution/graph-bridge/ids.js';
|
||||
import type { GraphNodeLookup } from '../../scope-resolution/graph-bridge/node-lookup.js';
|
||||
import { isClassLike } from '../../scope-resolution/scope/walkers.js';
|
||||
import { kotlinProvider } from '../kotlin.js';
|
||||
import {
|
||||
kotlinArityCompatibility,
|
||||
|
|
@ -42,8 +46,7 @@ export const kotlinScopeResolver: ScopeResolver = {
|
|||
|
||||
arityCompatibility: (callsite, def) => kotlinArityCompatibility(def, callsite),
|
||||
|
||||
buildMro: (graph, parsedFiles, nodeLookup) =>
|
||||
buildMro(graph, parsedFiles, nodeLookup, defaultLinearize),
|
||||
buildMro: (graph, parsedFiles, nodeLookup) => buildKotlinMro(graph, parsedFiles, nodeLookup),
|
||||
|
||||
populateOwners: (parsed: ParsedFile) => populateKotlinOwners(parsed),
|
||||
|
||||
|
|
@ -54,3 +57,94 @@ export const kotlinScopeResolver: ScopeResolver = {
|
|||
collapseMemberCallsByCallerTarget: false,
|
||||
hoistTypeBindingsToModule: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Kotlin MRO builder — extends `defaultLinearize` (EXTENDS-only) with
|
||||
* interface ancestors discovered via `IMPLEMENTS` edges. Interface
|
||||
* default methods (`interface Validator { fun validate(): Boolean = true }`)
|
||||
* are inherited by implementing classes without an explicit override;
|
||||
* the generic MRO would not surface them because the implementor has
|
||||
* no `EXTENDS` link to the interface (#1763).
|
||||
*
|
||||
* Interfaces are appended after the EXTENDS chain (Kotlin resolves
|
||||
* conflicts by requiring an explicit override, so first-seen-in-MRO
|
||||
* ordering is a reasonable approximation for method lookup). Transitive
|
||||
* interface inheritance (`interface A : B`) is closed via BFS.
|
||||
*/
|
||||
function buildKotlinMro(
|
||||
graph: KnowledgeGraph,
|
||||
parsedFiles: readonly ParsedFile[],
|
||||
nodeLookup: GraphNodeLookup,
|
||||
): Map<string, string[]> {
|
||||
const mro = buildMro(graph, parsedFiles, nodeLookup, defaultLinearize);
|
||||
|
||||
const defIdByGraphId = new Map<string, string>();
|
||||
for (const parsed of parsedFiles) {
|
||||
for (const def of parsed.localDefs) {
|
||||
if (!isClassLike(def.type)) continue;
|
||||
const graphId = resolveDefGraphId(parsed.filePath, def, nodeLookup);
|
||||
if (graphId !== undefined) defIdByGraphId.set(graphId, def.nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
// Direct IMPLEMENTS targets per class-like def.
|
||||
const directImpls = new Map<string, string[]>();
|
||||
for (const rel of graph.iterRelationshipsByType('IMPLEMENTS')) {
|
||||
const source = defIdByGraphId.get(rel.sourceId);
|
||||
const target = defIdByGraphId.get(rel.targetId);
|
||||
if (source === undefined || target === undefined) continue;
|
||||
let list = directImpls.get(source);
|
||||
if (list === undefined) {
|
||||
list = [];
|
||||
directImpls.set(source, list);
|
||||
}
|
||||
if (!list.includes(target)) list.push(target);
|
||||
}
|
||||
|
||||
// For each class, append the transitive closure of interfaces reachable
|
||||
// through its own + ancestor classes' IMPLEMENTS edges. Walking
|
||||
// ancestors picks up interfaces inherited via the EXTENDS chain
|
||||
// (e.g. `class C : B; class B : A; interface A` — C inherits A's
|
||||
// interface methods through B).
|
||||
for (const [classDefId, extendsMro] of mro) {
|
||||
const ancestorChain = [classDefId, ...extendsMro];
|
||||
const seeds: string[] = [];
|
||||
for (const ancestorId of ancestorChain) {
|
||||
for (const ifaceId of directImpls.get(ancestorId) ?? []) {
|
||||
seeds.push(ifaceId);
|
||||
}
|
||||
}
|
||||
if (seeds.length === 0) continue;
|
||||
const interfaces = closeInterfaces(seeds, directImpls);
|
||||
mro.set(classDefId, [...extendsMro, ...interfaces.filter((i) => !extendsMro.includes(i))]);
|
||||
}
|
||||
|
||||
// Classes with no EXTENDS still need an MRO entry when they implement
|
||||
// interfaces (e.g. `class User : Validator` — no `mro` entry from the
|
||||
// EXTENDS-only pass because no EXTENDS edges exist).
|
||||
for (const [classDefId, ifaces] of directImpls) {
|
||||
if (mro.has(classDefId)) continue;
|
||||
mro.set(classDefId, closeInterfaces([...ifaces], directImpls));
|
||||
}
|
||||
|
||||
return mro;
|
||||
}
|
||||
|
||||
function closeInterfaces(
|
||||
seeds: readonly string[],
|
||||
directImpls: ReadonlyMap<string, readonly string[]>,
|
||||
): string[] {
|
||||
const out: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
const queue: string[] = [...seeds];
|
||||
while (queue.length > 0) {
|
||||
const cur = queue.shift()!;
|
||||
if (seen.has(cur)) continue;
|
||||
seen.add(cur);
|
||||
out.push(cur);
|
||||
for (const next of directImpls.get(cur) ?? []) {
|
||||
if (!seen.has(next)) queue.push(next);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue