feat(types): вложенные и рекурсивные см.-ссылки в выводе типов, hover и автокомплите#4196
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughNested ChangesNested См. reference resolution with lazy type decorations
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/SymbolTypeIndex.java`:
- Around line 315-322: The visited set is being shared across all recursion
branches, causing false-positive cycle detection when the same helper is
referenced multiple times in different contexts. Instead of adding localFunction
to the shared visited set and passing it to ResolutionContext, create a new
visited set copy that includes the current visited elements plus the current
localFunction, then pass this new scoped copy to the recursive resolveTypes
call. This ensures each recursion path maintains its own cycle detection state
without affecting sibling branches.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 38d68e0e-1bf0-401c-9782-7139cfe50d1f
⛔ Files ignored due to path filters (2)
src/test/resources/types/NestedSeeRef.bslis excluded by!src/test/resources/**src/test/resources/types/NestedSeeRefCyclic.bslis excluded by!src/test/resources/**
📒 Files selected for processing (6)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/MemberTypeFromCommentResolver.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/TypeService.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/SymbolTypeIndex.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/inferencer/ExpressionTypeInferencer.javasrc/test/java/com/github/_1c_syntax/bsl/languageserver/types/JsDocVariantsTest.javasrc/test/java/com/github/_1c_syntax/bsl/languageserver/types/NestedSeeRefInferenceTest.java
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/types/model/TypeSet.java`:
- Around line 65-67: The lazy doc-comment field path in TypeSet is dropping the
field description, so IDE hints lose the text after the reference while keeping
the lazy type. Update TypeSet to carry descriptions alongside lazy fields by
extending the lazy field storage and materialization logic, and make
SymbolTypeIndex.applyFields pass fieldDescription(field) into that path instead
of creating lazy fields with an empty description.
In
`@src/test/java/com/github/_1c_syntax/bsl/languageserver/types/RecursiveSeeRefInferenceTest.java`:
- Around line 83-84: The cursor helper in RecursiveSeeRefInferenceTest can
compute an invalid offset when the marker is missing because indexOf returns -1.
Add a fail-fast assertion or precondition in the helper that computes
markerStart and targetOffset so the test stops immediately if the marker is
absent, and keep the validation close to the marker lookup logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9e836e63-b949-4706-a6f4-36df81adcc0c
⛔ Files ignored due to path filters (1)
src/test/resources/types/RecursiveSeeRef.bslis excluded by!src/test/resources/**
📒 Files selected for processing (4)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/SymbolTypeIndex.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/model/LazyTypeSet.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/model/TypeSet.javasrc/test/java/com/github/_1c_syntax/bsl/languageserver/types/RecursiveSeeRefInferenceTest.java
✅ Files skipped from review due to trivial changes (1)
- src/main/java/com/github/_1c_syntax/bsl/languageserver/types/model/LazyTypeSet.java
…олях структур Ссылки вида `см. Метод` в JsDoc разворачивались только на верхнем уровне. Во вложенных позициях (тип элемента коллекции `Массив из см. X`, тип поля структуры `* Поле - см. X`) они терялись, т.к. в рекурсивный резолв SymbolTypeIndex не прокидывался контекст документа. То же касалось и описаний типов параметров. Изменения: - прокинут ResolutionContext (владелец + язык + множество посещённых функций) через resolveTypes/resolveCollection/applyFields; HYPERLINK разворачивается единообразно на любом уровне через resolveSeeReference; - getDeclaredParameterTypes теперь принимает owner и разворачивает см.-ссылки в типах параметров (в т.ч. вложенные); - удалены обходные проходы, существовавшие лишь из-за отсутствия контекста: resolveReturnedValueHyperlinks (SymbolTypeIndex) и parameterHyperlinkTypes (ExpressionTypeInferencer); удалён ставший мёртвым публичный resolveDescribedTypes; - защита от закольцованных см.-ссылок через множество visited. Тесты: NestedSeeRefInferenceTest (элемент коллекции, поле структуры, параметр, закольцованная ссылка) + фикстуры NestedSeeRef.bsl/NestedSeeRefCyclic.bsl. Closes #4194 Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
…т вложенных типов) Самоссылающиеся структуры (узел дерева: `* Потомки - Массив из см. Узел`) образуют бесконечный тип — eager-материализация его не вмещает, поэтому раньше вложенность обрывалась на первом уровне. Решение — ленивый разворот, управляемый курсором. Авто-комплит разыменовывает выражение конечной глубины, поэтому материализовывать бесконечный тип не нужно: - новый LazyTypeSet — неизменяемая, немемоизирующая ссылка на возвращаемый тип локальной функции; реальный тип берётся из кэша на момент чтения; - TypeSet получил декорации lazyElements/lazyFields; геттеры getElementTypes/getLocalFields/getFieldTypes/getAllFieldNames форсят их на чтении, поэтому потребители (инференсер/автокомплит/hover) не меняются; - equals/hashCode остаются конечными и по значению (ключ ленивой ссылки — символ функции, не результат форса); - SymbolTypeIndex: вложенная см.-ссылка на локальную функцию навешивается лениво (withLazyElement/withLazyField), верхнеуровневые ссылки и квалифицированные/типовые — по-прежнему eager. Глубина рекурсии ограничивается выражением под курсором; хранимый граф конечен. Тесты: RecursiveSeeRefInferenceTest (глубокая навигация Корень.Потомки[0]. Потомки[0].Значение -> Строка; промежуточный уровень раскрывает поля). Refs #4194 Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
176af23 to
49e5055
Compare
- visited скоупится на текущий путь обхода (try/finally remove) — соседняя нециклическая см.-ссылка на ту же функцию больше не считается циклом; - ленивые поля сохраняют текстовое описание doc-комментария (новый LazyField: ленивый тип + описание), оно доходит до подсказок; - fail-fast проверка наличия маркера в курсор-хелпере теста. Тесты: юнит-тесты LazyTypeSet/LazyField и ленивых декораций TypeSet (элемент/поле, union, add, форс на чтении, имена без форса); регрессии — описание поля через см.-ссылку и верхнеуровневая закольцованная см.-цепочка. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
|
/buildJar |
|
✅ Собраны JAR-файлы для этого PR по команде Артефакт: 7851216217 Файлы внутри:
|
- TypeSet: накопление нескольких ленивых полей, union ленивых декораций на новых ref'ах, игнор несовпадающего по имени ленивого поля; - См.-ссылка на имя типа (не локальная функция) разрешается через TypeRegistry. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
Массив из см. Модуль.Метод — квалифицированная ссылка не лениво и, не разрешившись, не даёт тип элемента (ветки resolveCollection/localFunctionSeeRef). Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
…урсии Отчёт alisher-nil к #4194 выявил два дефекта поверх первичной реализации: 1. Автокомплит после `a.СмПоле.` забывал содержимое структуры: при разыменовании члена локальное поле сводилось к головному TypeRef, теряя вложенные поля. DereferenceMemberMatcher.collectLocalFieldMembers теперь несёт полный тип поля (с декорациями), поэтому `Контекст.ДанныеТокена.` снова показывает поля вложенной структуры. 2. Взаимно-рекурсивные структуры (Контейнер↔Коробка через см.-поля/параметры) при наведении уводили VariableSymbolMarkupContentBuilder.collectFieldBullets в бесконечную рекурсию (StackOverflow): ленивые поля раскрываются на чтении. Добавлено ограничение глубины разворота (MAX_FIELD_NESTING). Тесты: ReporterScenariosSeeRefTest воспроизводит сценарии репортёра в инференсе, hover и автокомплите (+ фикстуры Chained*/MutualRecursion*). Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
…есто лимита глубины Замена ограничения глубины (MAX_FIELD_NESTING) на точное обнаружение цикла по функции-источнику ленивой см.-ссылки. collectFieldBullets ведёт набор уже развёрнутых на пути источников; поле, разворот которого снова приводит к одному из них, не углубляется, а рендерится как «См. Функция» (то самое ссылочное представление, которого ожидал репортёр). Глубокие нерекурсивные структуры по-прежнему разворачиваются полностью; взаимная рекурсия Контейнер↔Коробка больше не уводит hover в StackOverflow. Тест mutualRecursionHoverTerminates усилен проверкой `См. (Контейнер|Коробка)`. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
… не на голову Отчёт alisher-nil к #4194: `Соответствие из КлючИЗначение: * Ключ - Строка * Значение - Число` — тип переменной считался верно, но при обходе `Для Каждого` тип элемента терялся. Причина: поля COLLECTION-описания (`* Ключ`/`* Значение`) применялись к голове коллекции (Соответствие), а не к её элементу (КлючИЗначение). resolveCollection теперь навешивает их на тип элемента; resolveTypes не применяет applyFields к COLLECTION повторно. У простых типов (`Структура: * Поле`) поведение прежнее. Тесты: MapElementFieldsInferenceTest (элемент несёт Ключ/Значение; Элемент.Ключ → Строка; Элемент.Значение → Число) + фикстура MapElementFields.bsl. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
|
/buildJar |
|
✅ Собраны JAR-файлы для этого PR по команде Артефакт: 7858809395 Файлы внутри:
|
По замечанию ревьюера: пакет types.model помечен @NullMarked, поэтому явные Objects.requireNonNull на параметрах избыточны. Убраны из новых TypeSet.withLazyElement/withLazyField и конструктора LazyTypeSet; удалён ставший бессмысленным тест constructorRejectsNulls. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
…консистентности Продолжение замечания про @NullMarked: уже существовавшие TypeSet.withElement/withField тоже несли избыточные Objects.requireNonNull — после чистки ленивых методов это смотрелось непоследовательно. Убраны, импорт java.util.Objects больше не нужен. ParameterDescriptor.requireNonNullElse оставлен: это нормализация null→дефолт, иное по смыслу, чем защитный requireNonNull. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
- VariableSymbolMarkupContentBuilder: вынесен renderInferredField (снижена когнитивная/цикломатическая сложность collectFieldBullets); явные скобки в seeReferenceLabel; - SymbolTypeIndex: localFunctionSeeRef сделан static, убрана всегда-ложная проверка name == null (@NullMarked), ветка локальной функции вынесена в resolveLocalFunctionTypes (число return'ов в resolveSeeReference ≤ 5); - TypeSet: нормализация декорационных мап вынесена в immutableCopy/ immutableNestedCopy (снижена сложность канонического конструктора); - убраны неиспользуемые импорты (MemberTypeFromCommentResolver, NestedSeeRefInferenceTest); - LazyTypeSetTest: объединённая цепочка ассертов. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
…r S1200) Единственное использование Arrays.copyOfRange заменено на List.of(parts).subList, что убирает класс из зависимостей SymbolTypeIndex (21 → 20) и закрывает последнее замечание SonarCloud по PR. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
- hover по рекурсивному дереву (Массив из см. ДеревоУзел) — обрыв элемент-цикла ссылкой `См. ДеревоУзел` (покрывает ветку ленивых элементов в lazySourceKeys); - hover взаимной рекурсии в EN-локали — `See Функция` (ветка локали в seeReferenceLabel). Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01QcYuuctkrfWkmGqXaUmx5S
|
/buildJar |
|
✅ Собраны JAR-файлы для этого PR по команде Артефакт: 7859960413 Файлы внутри:
|
|



Closes #4194
см. Метод-ссылки в JsDoc разворачивались только на верхнем уровне описания типа.PR делает их вывод единообразным на любой глубине, добавляет поддержку
рекурсивных (само- и взаимно-ссылочных) структур и чинит сопутствующие дефекты в
hover и автокомплите, найденные репортёром.
1. Вложенные
см.-ссылки (fix)Во вложенных позициях (тип элемента коллекции
Массив из см. X, тип поляструктуры
* Поле - см. X, типы параметров) ссылки терялись, т.к. в рекурсивныйрезолв
SymbolTypeIndexне прокидывался контекст документа.ResolutionContext(владелец + язык + множество посещённых функций)через
resolveTypes/resolveCollection/applyFields;HYPERLINKразворачивается единообразно на любом уровне через
resolveSeeReference;getDeclaredParameterTypesпринимаетownerи разворачиваетсм.-ссылки втипах параметров (в т.ч. вложенные);
(
resolveReturnedValueHyperlinks,parameterHyperlinkTypes, мёртвыйresolveDescribedTypes);см.-ссылок через множествоvisited(скоупится на путь обхода).
2. Рекурсивный автокомплит (feat)
Самоссылочные структуры (узел дерева:
* Потомки - Массив из см. Узел)образуют бесконечный тип — eager-материализация его не вмещает. Решение — ленивый
разворот, управляемый курсором: автокомплит разыменовывает выражение конечной
глубины, поэтому каждый шаг форсит ровно один уровень из кэша функции-источника.
LazyTypeSet— неизменяемая, немемоизирующая ссылка на возвращаемый типлокальной функции; реальный тип берётся из кэша на момент чтения;
TypeSetполучил декорацииlazyElements/lazyFields(+LazyFieldсописанием); геттеры форсят их на чтении, поэтому потребители не меняются;
equals/hashCodeостаются конечными и по значению (ключ ленивой ссылки —символ функции, не результат форса);
Корень.Потомки[0].Потомки[0].Значениеразрешается доСтрокана любую глубину;закольцованные ссылки
A↔Bне зацикливаются.3. Сценарии из отчёта в issue (@alisher-nil)
Контекст.ДанныеТокена.забывал содержимоеструктуры: при разыменовании члена локальное поле сводилось к головному
TypeRef.DereferenceMemberMatcherтеперь несёт полный тип поля.Контейнер↔Коробкачерез см.-поля/параметрыуводили построитель hover в
StackOverflow. Вместо лимита глубины — точноеобнаружение цикла по функции-источнику: повторный вход рендерится как
См. Функция(то самое ссылочное представление, которого ожидал репортёр), аглубокие нерекурсивные структуры по-прежнему разворачиваются полностью.
терял тип элемента приДля Каждого: поля COLLECTION-описания навешивались на голову коллекции, а не на элемент. Исправлено вresolveCollection`.4. Правки по ревью
visitedскоупится на путь обхода (try/finally); ленивые полясохраняют описание doc-комментария; fail-fast guard маркера в тесте.
@NullMarked: убраны избыточныеObjects.requireNonNullвtypes.model(
TypeSet.with*, конструкторLazyTypeSet).Тесты
NestedSeeRefInferenceTest,RecursiveSeeRefInferenceTest,ReporterScenariosSeeRefTest(инференс/hover/автокомплит),MapElementFieldsInferenceTest;LazyTypeSet/LazyFieldи ленивых декорацийTypeSet;NestedSeeRef*,RecursiveSeeRef,Chained*,MutualRecursion*,MapElementFields.Полный прогон зелёный; coverage на новом коде ≥ 95% (line).
Производительность (репозиторий cpm, 13 090 файлов)
Замер ядра ленивой реализации (
developvs ветка) через analyze (JFR + GC +jmap) иCompletionTypingProfileTest:TypeSet≈ +3.8 MBЛатентность не изменилась (разброс прогонов больше разницы веток), аллокации
идентичны;
LazyTypeSetинстанцируется только для рекурсивных типов (в cpm — 73шт.). Последующие UX-правки (обнаружение цикла в hover, чейнинг в автокомплите,
поля элемента) работают на уже посчитанных типах и в горячий путь не попадают.
🤖 Generated with Claude Code