Enhance Symbol Table for Performance and IDE Features
Labels: arch-review, symbol-table, performance, medium
Priority: P2
Severity: MEDIUM
Epic: Architectural Improvements Q2 2026
Problem Summary
The symbol table implementation is a simple Dictionary<Symbol, ISymbolTableEntry> with no support for efficient scope-based queries, hierarchical lookups, or the rich queries needed for IDE features and advanced type checking.
Current Issues
Limited Functionality
- Symbol table is basic dictionary (SymbolTable.cs: 32 lines)
- Linear search for name-based lookup (
ResolveByName()) - No scope hierarchy traversal support
- No "find all references" capability
- No "find symbols in scope" query
- Symbol table stored per-scope but no global index
Code Evidence:
// src/ast-model/Symbols/SymbolTable.cs
public class SymbolTable : Dictionary<Symbol, ISymbolTableEntry>, ISymbolTable
{
public ISymbolTableEntry ResolveByName(string symbolName)
{
// O(n) linear search!
foreach (var k in Keys)
{
if (k.Name == symbolName)
return this[k];
}
return null;
}
}
Performance Problems
- O(n) lookup for symbol resolution
- No indexing for fast queries
- Cannot efficiently answer "what's in scope?" queries
- Scales poorly with large codebases
IDE Features Blocked
- "Find all references" requires full AST scan
- "Find symbols" completion has no index
- "Rename symbol" cannot find all uses efficiently
- Hover info requires re-resolution
- No support for workspace-wide symbol search
Requirements
Enhanced Symbol Table
public class SymbolTable : ISymbolTable
{
// Fast lookups via multiple indexes
private readonly Dictionary<string, List<ISymbolTableEntry>> _nameIndex = new();
private readonly Dictionary<Symbol, ISymbolTableEntry> _symbolIndex = new();
private readonly Dictionary<SymbolKind, List<ISymbolTableEntry>> _kindIndex = new();
// Scope hierarchy
private readonly SymbolTable? _parent;
private readonly List<SymbolTable> _children = new();
private readonly IScope _scope;
// O(1) lookup by name
public IEnumerable<ISymbolTableEntry> ResolveByName(string name)
{
// Check current scope
if (_nameIndex.TryGetValue(name, out var entries))
return entries;
// Walk up scope chain
return _parent?.ResolveByName(name)
?? Enumerable.Empty<ISymbolTableEntry>();
}
// Get all visible symbols at location
public IEnumerable<ISymbolTableEntry> GetVisibleSymbols(SourceLocation location)
{
// Return all symbols visible at location
// Includes current scope + parent scopes
var symbols = _symbolIndex.Values.ToList();
if (_parent != null)
symbols.AddRange(_parent.GetVisibleSymbols(location));
return symbols;
}
// O(1) lookup by symbol kind
public IEnumerable<ISymbolTableEntry> FindByKind(SymbolKind kind)
{
return _kindIndex.TryGetValue(kind, out var entries)
? entries
: Enumerable.Empty<ISymbolTableEntry>();
}
// Qualified name resolution
public ISymbolTableEntry? ResolveQualified(string[] nameParts)
{
// Handle paths like "System.Collections.List"
var current = this;
ISymbolTableEntry? result = null;
foreach (var part in nameParts)
{
result = current.ResolveByName(part).FirstOrDefault();
if (result == null)
return null;
// Navigate into nested scope if available
if (result.NestedScope != null)
current = result.NestedScope.SymbolTable;
}
return result;
}
}
Global Symbol Index
public class GlobalSymbolIndex
{
// Fast global queries for IDE features
private readonly Dictionary<string, List<SymbolDefinition>> _definitions = new();
private readonly Dictionary<Symbol, List<SourceLocation>> _references = new();
private readonly Dictionary<string, List<SymbolDefinition>> _fuzzyIndex = new();
public void IndexAssembly(AssemblyDef assembly)
{
// Build indices from AST
var visitor = new SymbolIndexingVisitor(this);
visitor.Visit(assembly);
}
public IEnumerable<SourceLocation> FindReferences(Symbol symbol)
{
return _references.TryGetValue(symbol, out var locs)
? locs
: Enumerable.Empty<SourceLocation>();
}
public IEnumerable<SymbolDefinition> FindDefinitions(string name)
{
return _definitions.TryGetValue(name, out var defs)
? defs
: Enumerable.Empty<SymbolDefinition>();
}
public IEnumerable<SymbolDefinition> FuzzySearch(string prefix)
{
// For code completion - find symbols starting with prefix
return _definitions
.Where(kvp => kvp.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
.SelectMany(kvp => kvp.Value);
}
}
Scope-Aware Resolution
public class ScopeResolver
{
private readonly GlobalSymbolIndex _index;
public ResolvedSymbol? Resolve(string name, IScope scope)
{
// Try local scope first
var local = scope.SymbolTable.ResolveByName(name);
if (local.Any())
return new ResolvedSymbol(local.First(), ResolutionKind.Local);
// Try parent scopes
var parent = scope.EnclosingScope;
while (parent != null)
{
var parentResult = parent.SymbolTable.ResolveByName(name);
if (parentResult.Any())
return new ResolvedSymbol(parentResult.First(), ResolutionKind.Outer);
parent = parent.EnclosingScope;
}
// Try imported modules
foreach (var import in scope.Imports)
{
var imported = _index.FindDefinitions($"{import}.{name}");
if (imported.Any())
return new ResolvedSymbol(imported.First(), ResolutionKind.Imported);
}
return null;
}
}
Implementation Plan
Phase 1: Enhanced Symbol Table (Weeks 1-2)
- Design indexed symbol table structure
- Implement multiple index types (name, kind, location)
- Add scope hierarchy support
- Test performance improvements
Phase 2: Global Symbol Index (Weeks 3-4)
- Design global index structure
- Implement symbol indexing visitor
- Build definition and reference indices
- Add fuzzy search support
Phase 3: Scope-Aware Resolution (Weeks 5-6)
- Implement qualified name resolution
- Add import resolution
- Create scope resolver with fallback chain
- Test resolution correctness
Phase 4: IDE Integration (Weeks 7-8)
- Add "find references" API
- Add "find symbols in scope" API
- Integrate with LSP (if available)
- Performance benchmarking
Acceptance Criteria
- [ ] Symbol lookup is O(1) instead of O(n)
- [ ] Can find all references to a symbol
- [ ] Can find all symbols in scope
- [ ] Can search symbols by kind (types, functions, variables)
- [ ] Qualified name resolution works
- [ ] Global index handles cross-file symbols
- [ ] Performance tests show >10x speedup
- [ ] Integration tests verify correctness
Performance Goals
| Operation | Current | Target | Improvement |
|---|---|---|---|
| Resolve by name (100 symbols) | O(n) ~100μs | O(1) ~1μs | 100x |
| Find all references | Full AST scan | Indexed | >100x |
| Symbols in scope | Walk AST | Indexed | >50x |
| Fuzzy search | N/A | <10ms | N/A |
Benefits
Performance
- O(1) symbol lookups (instead of O(n))
- Fast "find all references" (indexed)
- Efficient scope queries
- Scales to large codebases
IDE Features
- Real-time "find references"
- Fast code completion
- Workspace-wide symbol search
- Efficient "rename symbol"
- Quick "go to definition"
Type Checking
- Efficient overload resolution
- Fast generic type resolution
- Trait/interface resolution
Example Usage
// Create enhanced symbol table
var symbolTable = new SymbolTable(parentScope);
// Add symbol (automatically indexed)
symbolTable.Add(new Symbol("myVar"), new VariableEntry(...));
// Fast O(1) lookup
var results = symbolTable.ResolveByName("myVar");
// Find all variables in scope
var variables = symbolTable.FindByKind(SymbolKind.Variable);
// Global index for "find references"
var globalIndex = new GlobalSymbolIndex();
globalIndex.IndexAssembly(assembly);
var references = globalIndex.FindReferences(symbol);
Console.WriteLine($"Found {references.Count()} references");
// Fuzzy search for code completion
var completions = globalIndex.FuzzySearch("my");
// Returns: myVar, myFunction, myClass, etc.
References
- Architectural Review:
docs/architectural-review-2025.md- Finding #6 - Roslyn symbol tables: https://github.com/dotnet/roslyn
- rust-analyzer symbol indexing
- Related Issues: Enables #ISSUE-002 (LSP navigation), improves #ISSUE-003 (Incremental Compilation)
Estimated Effort
8 weeks (2 months) - Weeks 1-2: Enhanced symbol table - Weeks 3-4: Global symbol index - Weeks 5-6: Scope-aware resolution - Weeks 7-8: IDE integration
Dependencies
- Nice to have: #ISSUE-002 LSP (for integration)
- Nice to have: #ISSUE-004 Diagnostics (for source locations)
Enables
- Issue #002: LSP navigation features
- Issue #003: Incremental compilation (symbol caching)
- Better type checking performance
- Workspace-wide refactoring
Success Metrics
- 100x faster symbol lookups
- "Find references" <100ms for typical project
- Code completion responds instantly
- Zero performance regressions
- Scales to 10,000+ symbols