perf(references): terminal-aware резолюция ссылок вместо повторного спуска по AST#4253
Conversation
Добавлен terminal-aware overload в SPI резолюции ссылок: ReferenceFinder.findReference(uri, TerminalNode) (default делегирует в позиционный — обратная совместимость) и ReferenceResolver.findReference( uri, TerminalNode) с тем же @order и семантикой первого совпадения. New/Keyword/PlatformMember finder'ы переопределяют его на подъём от терминала вместо спуска findTerminalNodeContainsPosition от корня AST. Горячие вызывающие переведены на терминал: ExpressionTypeInferencer (inferMethodCall, inferIdentifier) и TypeService.isUnknownGlobalAt. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01BSiRGLm633B4EmvG3vkk4V
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- убрать неиспользуемый resolveReferenceAt(Position) в ExpressionTypeInferencer (единственный вызывающий переведён на терминальный overload) — S1144; - NewExpression terminal-override: single-expression lambda вместо блочной, short-circuit до fetch документа — S2211; - KeywordReferenceFinder: позиционный путь делегирует в терминальный, без DocumentContext-типизированного хелпера (и импорта) — coupling 21→20, S1200. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01BSiRGLm633B4EmvG3vkk4V
|



Описание
Резолюция ссылок под курсором (
ReferenceResolver→ цепочкаReferenceFinder'ов) принимала толькоPosition, поэтому вызывающие, у которыхTerminalNodeидентификатора уже под рукой (инференсер типов, диагностики), теряли его — и каждый finder заново спускался от корня AST черезfindTerminalNodeContainsPosition/аналоги (O(размер дерева)).Что стало — terminal-aware overload в SPI:
ReferenceFinder.findReference(uri, TerminalNode)— default делегирует вfindReference(uri, Position)по стартовой позиции терминала (полная обратная совместимость: finder'ы, которым спуск не нужен, ничего не переопределяют);ReferenceResolver.findReference(uri, TerminalNode)— тот же@Orderи семантика первого совпадения, что и у позиционного варианта.Три finder'а с дорогим спуском переопределяют терминальный метод на подъём от терминала:
KeywordReferenceFinder— строит keyword-символ прямо от терминала;NewExpressionReferenceFinder— подъём до объемлющегоnewExpression(ancestorOrSelf → typeName → newExpression) вместо полногоfindInnermostNewExpression-спуска;PlatformMemberReferenceFinder—TypeService.memberAt(document, terminal).Горячие вызывающие переведены на терминал:
ExpressionTypeInferencer.inferMethodCall/inferIdentifier(терминал имени вызова / идентификатора уже есть) иTypeService.isUnknownGlobalAt(terminal).Зачем
По CPU-профилю на большом реальном модуле (после line-индексов серии) остаток резолюции — это
findTerminalNodeContainsPositionи однотипные спуски от корня, ~65% inclusive времениUnknownMember, разложенные по finder'ам (NewExpression~22%,Keyword~17%,PlatformMember~16%). Все они запускаются изReferenceResolver.findReference(uri, position)при инференсе ресивера — на каждый резолв идентификатора, хотя терминал у инференсера уже есть. ЦепочкаfindFirstвдобавок прогоняет спуск каждого finder'а до первого совпадения (напр.NewExpression@150спускается и возвращает пусто на не-new позициях — чистые накладные расходы).Терминальный путь заменяет спуск от корня на подъём O(глубина); негативные ветки (терминал не является new-выражением/keyword'ом) отвечают мгновенно, не обходя дерево.
Замеры
Нагрузка —
UnknownMemberпо общему модулюУправлениеДоступомСлужебныйиз SSL (~48k строк); A/B по best-итерации в одной JVM. База — все PR серии (терминальный резолв типов + мемоизация инференса + оба line-индекса), т.е. срез уже полностью оптимизирован — измеряется чистый вклад этой правки:Связанные задачи
Closes
Чеклист
Общие
claude/perf-terminal-based-type-resolution— см. «Дополнительно»)ReferenceResolverTest: +2 теста терминальной диспетчеризации — default-делегирование в позиционный и приоритет override; паритиUnknownMember/Deprecated/AssignToReadOnly/Hover/Definition/ExpressionTypeInferencer)gradlew precommit) — прогнаны затронутые тесты references/инференса/провайдеров; полныйprecommitза мейнтейнерамиДополнительно
Зависит от #4250 (использует терминальные overload'ы
TypeService.memberAt(terminal)/isUnknownGlobalAt(terminal)иExpressionAtPosition). Поэтому PR стекнут на веткуclaude/perf-terminal-based-type-resolution; после мерджа #4250 базу нужно переключить наdevelop(или ребейзнуть). Финальный шаг серии перф-правок по цепочке CPU-профилей одной диагностики: терминальный резолв типов (#4250) → мемоизация инференса (#4251) → line-индекс объявлений (#4252) → line-индекс вхождений (#4249) → terminal-aware резолюция ссылок (этот PR).🤖 Generated with Claude Code
https://claude.ai/code/session_01BSiRGLm633B4EmvG3vkk4V
Generated by Claude Code