From f5d9a0efe5a6aaf94e99a49a77a746b0a51958c7 Mon Sep 17 00:00:00 2001 From: Olaf Monien Date: Thu, 12 Feb 2026 00:28:37 +0100 Subject: [PATCH] perf: Speed up ref resolution for large Pascal codebases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two optimizations that eliminate the O(n²) bottleneck: 1. Add Pascal built-in filtering to isBuiltInOrExternal() — skips resolution attempts for System.*, Vcl.*, Fmx.* and ~60 common RTL identifiers (WriteLn, Create, Free, TObject, etc.) 2. Cache getNodesByKind() results during warm cache phase — matchFuzzy() was calling the database 3 times per unresolved reference instead of using the in-memory cache that warmCaches() already builds. Co-Authored-By: Claude Opus 4.6 --- src/resolution/index.ts | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/resolution/index.ts b/src/resolution/index.ts index d3056a6..a051574 100644 --- a/src/resolution/index.ts +++ b/src/resolution/index.ts @@ -38,6 +38,7 @@ export class ReferenceResolver { private fileCache: Map = new Map(); private nameCache: Map = new Map(); private qualifiedNameCache: Map = new Map(); + private kindCache: Map = new Map(); private nodeByIdCache: Map = new Map(); private cachesWarmed = false; @@ -80,6 +81,14 @@ export class ReferenceResolver { this.qualifiedNameCache.set(node.qualifiedName, [node]); } + // Index by kind + const byKind = this.kindCache.get(node.kind); + if (byKind) { + byKind.push(node); + } else { + this.kindCache.set(node.kind, [node]); + } + // Index by ID this.nodeByIdCache.set(node.id, node); } @@ -95,6 +104,7 @@ export class ReferenceResolver { this.fileCache.clear(); this.nameCache.clear(); this.qualifiedNameCache.clear(); + this.kindCache.clear(); this.nodeByIdCache.clear(); this.cachesWarmed = false; } @@ -131,6 +141,9 @@ export class ReferenceResolver { }, getNodesByKind: (kind: Node['kind']) => { + if (this.cachesWarmed) { + return this.kindCache.get(kind) ?? []; + } return this.queries.getNodesByKind(kind); }, @@ -363,6 +376,44 @@ export class ReferenceResolver { return true; } + // Pascal/Delphi built-ins and standard library units + if (ref.language === 'pascal') { + // Standard RTL/VCL/FMX unit prefixes — these are external dependencies + const pascalUnitPrefixes = [ + 'System.', 'Winapi.', 'Vcl.', 'Fmx.', 'Data.', 'Datasnap.', + 'Soap.', 'Xml.', 'Web.', 'REST.', 'FireDAC.', 'IBX.', + 'IdHTTP', 'IdTCP', 'IdSSL', 'Id', + ]; + if (pascalUnitPrefixes.some((p) => name.startsWith(p))) { + return true; + } + + // Common standalone RTL units and built-in identifiers + const pascalBuiltIns = [ + 'System', 'SysUtils', 'Classes', 'Types', 'Variants', 'StrUtils', + 'Math', 'DateUtils', 'IOUtils', 'Generics.Collections', 'Generics.Defaults', + 'Rtti', 'TypInfo', 'SyncObjs', 'RegularExpressions', + 'SysInit', 'Windows', 'Messages', 'Graphics', 'Controls', 'Forms', + 'Dialogs', 'StdCtrls', 'ExtCtrls', 'ComCtrls', 'Menus', 'ActnList', + 'WriteLn', 'Write', 'ReadLn', 'Read', 'Inc', 'Dec', 'Ord', 'Chr', + 'Length', 'SetLength', 'High', 'Low', 'Assigned', 'FreeAndNil', + 'Format', 'IntToStr', 'StrToInt', 'FloatToStr', 'StrToFloat', + 'Trim', 'UpperCase', 'LowerCase', 'Pos', 'Copy', 'Delete', 'Insert', + 'Now', 'Date', 'Time', 'DateToStr', 'StrToDate', + 'Raise', 'Exit', 'Break', 'Continue', 'Abort', + 'True', 'False', 'nil', 'Self', 'Result', + 'Create', 'Destroy', 'Free', + 'TObject', 'TComponent', 'TPersistent', 'TInterfacedObject', + 'TList', 'TStringList', 'TStrings', 'TStream', 'TMemoryStream', 'TFileStream', + 'Exception', 'EAbort', 'EConvertError', 'EAccessViolation', + 'IInterface', 'IUnknown', + ]; + + if (pascalBuiltIns.includes(name)) { + return true; + } + } + return false; }