mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-12 08:41:34 +00:00
fix(symbol-table): store all overloads in fileIndex instead of last-write-wins
The fileIndex Map stored SymbolDefinition per name, silently dropping earlier overloads via Map.set(). Changed to SymbolDefinition[] so all same-name methods (e.g., Java overloads) survive in same-file resolution. Added lookupExactAll() for resolution-context to pass all same-file candidates through to candidate filtering.
This commit is contained in:
parent
00c5126b24
commit
3e29f4e4b9
3 changed files with 46 additions and 17 deletions
|
|
@ -81,10 +81,10 @@ export const createResolutionContext = (): ResolutionContext => {
|
|||
// --- Core resolution (single implementation of tier logic) ---
|
||||
|
||||
const resolveUncached = (name: string, fromFile: string): TieredCandidates | null => {
|
||||
// Tier 1: Same file — authoritative match
|
||||
const localDef = symbols.lookupExactFull(fromFile, name);
|
||||
if (localDef) {
|
||||
return { candidates: [localDef], tier: 'same-file' };
|
||||
// Tier 1: Same file — authoritative match (returns all overloads)
|
||||
const localDefs = symbols.lookupExactAll(fromFile, name);
|
||||
if (localDefs.length > 0) {
|
||||
return { candidates: localDefs, tier: 'same-file' };
|
||||
}
|
||||
|
||||
// Get all global definitions for subsequent tiers
|
||||
|
|
|
|||
|
|
@ -34,9 +34,17 @@ export interface SymbolTable {
|
|||
/**
|
||||
* High Confidence: Look for a symbol in a specific file, returning full definition.
|
||||
* Includes type information needed for heritage resolution (Class vs Interface).
|
||||
* Returns first matching definition — use lookupExactAll for overloaded methods.
|
||||
*/
|
||||
lookupExactFull: (filePath: string, name: string) => SymbolDefinition | undefined;
|
||||
|
||||
/**
|
||||
* High Confidence: Look for ALL symbols with this name in a specific file.
|
||||
* Returns all definitions, including overloaded methods with the same name.
|
||||
* Used by resolution-context to pass all same-file overloads to candidate filtering.
|
||||
*/
|
||||
lookupExactAll: (filePath: string, name: string) => SymbolDefinition[];
|
||||
|
||||
/**
|
||||
* Low Confidence: Look for a symbol anywhere in the project
|
||||
* Used when imports are missing or for framework magic
|
||||
|
|
@ -69,9 +77,10 @@ export interface SymbolTable {
|
|||
}
|
||||
|
||||
export const createSymbolTable = (): SymbolTable => {
|
||||
// 1. File-Specific Index — stores full SymbolDefinition for O(1) lookupExactFull
|
||||
// Structure: FilePath -> (SymbolName -> SymbolDefinition)
|
||||
const fileIndex = new Map<string, Map<string, SymbolDefinition>>();
|
||||
// 1. File-Specific Index — stores full SymbolDefinition(s) for O(1) lookup.
|
||||
// Structure: FilePath -> (SymbolName -> SymbolDefinition[])
|
||||
// Array allows overloaded methods (same name, different signatures) to coexist.
|
||||
const fileIndex = new Map<string, Map<string, SymbolDefinition[]>>();
|
||||
|
||||
// 2. Global Reverse Index (The "Backup")
|
||||
// Structure: SymbolName -> [List of Definitions]
|
||||
|
|
@ -109,7 +118,12 @@ export const createSymbolTable = (): SymbolTable => {
|
|||
if (!fileIndex.has(filePath)) {
|
||||
fileIndex.set(filePath, new Map());
|
||||
}
|
||||
fileIndex.get(filePath)!.set(name, def);
|
||||
const fileMap = fileIndex.get(filePath)!;
|
||||
if (!fileMap.has(name)) {
|
||||
fileMap.set(name, [def]);
|
||||
} else {
|
||||
fileMap.get(name)!.push(def);
|
||||
}
|
||||
|
||||
// B. Properties go to fieldByOwner index only — skip globalIndex to prevent
|
||||
// namespace pollution for common names like 'id', 'name', 'type'.
|
||||
|
|
@ -134,11 +148,17 @@ export const createSymbolTable = (): SymbolTable => {
|
|||
};
|
||||
|
||||
const lookupExact = (filePath: string, name: string): string | undefined => {
|
||||
return fileIndex.get(filePath)?.get(name)?.nodeId;
|
||||
const defs = fileIndex.get(filePath)?.get(name);
|
||||
return defs?.[0]?.nodeId;
|
||||
};
|
||||
|
||||
const lookupExactFull = (filePath: string, name: string): SymbolDefinition | undefined => {
|
||||
return fileIndex.get(filePath)?.get(name);
|
||||
const defs = fileIndex.get(filePath)?.get(name);
|
||||
return defs?.[0];
|
||||
};
|
||||
|
||||
const lookupExactAll = (filePath: string, name: string): SymbolDefinition[] => {
|
||||
return fileIndex.get(filePath)?.get(name) ?? [];
|
||||
};
|
||||
|
||||
const lookupFuzzy = (name: string): SymbolDefinition[] => {
|
||||
|
|
@ -173,5 +193,5 @@ export const createSymbolTable = (): SymbolTable => {
|
|||
fieldByOwner.clear();
|
||||
};
|
||||
|
||||
return { add, lookupExact, lookupExactFull, lookupFuzzy, lookupFuzzyCallable, lookupFieldByOwner, getStats, clear };
|
||||
return { add, lookupExact, lookupExactFull, lookupExactAll, lookupFuzzy, lookupFuzzyCallable, lookupFieldByOwner, getStats, clear };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,11 +30,13 @@ describe('SymbolTable', () => {
|
|||
expect(table.getStats().globalSymbolCount).toBe(1);
|
||||
});
|
||||
|
||||
it('allows duplicate adds for same file and name', () => {
|
||||
it('allows duplicate adds for same file and name (overloads preserved)', () => {
|
||||
table.add('src/a.ts', 'foo', 'func:foo:1', 'Function');
|
||||
table.add('src/a.ts', 'foo', 'func:foo:2', 'Function');
|
||||
// File index overwrites: last wins
|
||||
expect(table.lookupExact('src/a.ts', 'foo')).toBe('func:foo:2');
|
||||
// File index stores both overloads; lookupExact returns first
|
||||
expect(table.lookupExact('src/a.ts', 'foo')).toBe('func:foo:1');
|
||||
// lookupExactAll returns all overloads
|
||||
expect(table.lookupExactAll('src/a.ts', 'foo')).toHaveLength(2);
|
||||
// Global index appends
|
||||
expect(table.lookupFuzzy('foo')).toHaveLength(2);
|
||||
});
|
||||
|
|
@ -495,13 +497,20 @@ describe('SymbolTable', () => {
|
|||
expect(def!.ownerId).toBe('class:User');
|
||||
});
|
||||
|
||||
it('last-write-wins when same file and name are added twice', () => {
|
||||
it('returns first definition when same file and name are added twice (overloads preserved)', () => {
|
||||
table.add('src/a.ts', 'foo', 'func:foo:v1', 'Function', { returnType: 'void' });
|
||||
table.add('src/a.ts', 'foo', 'func:foo:v2', 'Function', { returnType: 'string' });
|
||||
// lookupExactFull returns first match
|
||||
const def = table.lookupExactFull('src/a.ts', 'foo');
|
||||
expect(def).toBeDefined();
|
||||
expect(def!.nodeId).toBe('func:foo:v2');
|
||||
expect(def!.returnType).toBe('string');
|
||||
expect(def!.nodeId).toBe('func:foo:v1');
|
||||
expect(def!.returnType).toBe('void');
|
||||
// lookupExactAll returns all overloads
|
||||
const all = table.lookupExactAll('src/a.ts', 'foo');
|
||||
expect(all).toHaveLength(2);
|
||||
expect(all[0].nodeId).toBe('func:foo:v1');
|
||||
expect(all[1].nodeId).toBe('func:foo:v2');
|
||||
expect(all[1].returnType).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue