Skip to content

fix(types): названный в описании тип не уносит с собой посчитанные поля - #4447

Merged
nixel2007 merged 6 commits into
developfrom
fix/declared-type-keeps-fields
Aug 11, 2026
Merged

fix(types): названный в описании тип не уносит с собой посчитанные поля#4447
nixel2007 merged 6 commits into
developfrom
fix/declared-type-keeps-fields

Conversation

@nixel2007

@nixel2007 nixel2007 commented Aug 11, 2026

Copy link
Copy Markdown
Member

Проблема

Значение метода складывается из двух источников: типа, названного в описании (// Возвращаемое значение: …), и типов, выведенных по телу. По общим типам верим описанию — у Массив из Число из комментария состав элементов точнее собранного по телу. Ради этого названный в описании тип вычитался из выведенного:

var extra = inferred;
for (var ref : declared.refs()) {
  extra = extra.without(ref);   // убирает ссылку вместе со ВСЕМИ её декорациями
}
return declared.union(extra);

Вычитание убирает ссылку целиком — вместе с полями. У функции, чьё описание гласит Возвращаемое значение: Структура, состав ключей, собранный из Вставить, пропадал полностью.

Замер на живом коде БСП (ИнтеграцияПодсистемБСП.СобытияБСП, у которой два с лишним десятка Вставить): 111 полей в момент сохранения и ноль при чтении.

Дальше это оборачивалось ложными «У типа "Структура" нет метода или свойства …» — самой массовой жалобой диагностики на реальной конфигурации: 9660 срабатываний из 30 587, почти треть всей выдачи.

Что сделано

Состав элементов по-прежнему остаётся за описанием — правило и тесты на Массив из Число не тронуты. А поля возвращаются: после вычитания к результату добавляются поля выведенного значения по тем же ссылкам.

Замеры на ssl_3_1

Пакетный анализ, репортер SARIF, mode: only с UnknownMember + EventHandlerInvalidSignature. Правка мерилась в составе большей ветки, где она — единственная, влияющая на состав полей: замечаний 30 401 → 28 859, то есть примерно полторы тысячи ложных срабатываний уходит.

Отдельного прогона «develop против develop плюс только эта правка» я не делал: вынесу числа комментарием, если понадобится точная атрибуция.

Тесты

DeclaredStructureFieldsTest: у функции в описании назван тип Структура, а состав ключей известен из тела; после правки ключи видны. До правки набор полей пуст.

Весь пакет types.* (1706 тестов) и *UnknownMember* — зелёные, включая тесты Массив из Число, ради которых вычитание и существовало.

Связанные задачи

Часть работы по #4429.

Summary by CodeRabbit

  • Bug Fixes

    • Preserved fields inferred from method bodies when declared return types are structures.
    • Retained detailed element, field, and lazy type information when combining declared and inferred types.
    • Improved consistency after return-type updates or removals.
    • Prevented recursive type analysis from losing locally inferred fields and element types.
  • Tests

    • Added coverage for inferred structure fields, cache updates, and recursive lazy-field resolution.

Значение метода складывается из описания и вывода по телу. По общим типам верим
описанию: у «Массив из Число» из комментария состав элементов точнее собранного
по телу, — и ради этого названный в описании тип вычитался из выведенного. Но
вычитание убирало ссылку вместе со ВСЕМИ её декорациями, включая поля.

У функции, чьё описание гласит «Возвращаемое значение: Структура», состав ключей,
честно собранный из Вставить, пропадал целиком: замер на ssl_3_1 показывает 111
полей в момент сохранения и ноль при чтении. Дальше это оборачивалось ложными
«У типа "Структура" нет метода или свойства …» — самой массовой жалобой
диагностики на реальной конфигурации.

Состав элементов по-прежнему остаётся за описанием, а поля возвращаются.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cu7S3zYn7n6GMsdYf5v1q
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

SymbolTypeIndex caches combined declared and inferred return types and invalidates stale entries. recursiveKnot avoids forcing recursive lazy values. Tests cover declared structure fields, cache replacement, and recursive lazy fields.

Changes

Return type inference

Layer / File(s) Summary
Combine and invalidate return types
src/main/java/.../SymbolTypeIndex.java, src/test/.../DeclaredStructureFieldsTest.java
SymbolTypeIndex caches combined return types, invalidates entries after updates or document clearing, and restores inferred fields during merging. Tests cover declared structure fields and replacement after method body changes.
Avoid recursive lazy forcing
src/main/java/.../ExpressionTypeInferencer.java, src/test/.../DeclaredStructureFieldsTest.java
recursiveKnot reads underlying element and field maps and creates lazy fields without forcing recursive accessors. Tests verify recursive lazy field resolution.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: preserving computed fields for types named in return-value descriptions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/declared-type-keeps-fields

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Test Results

 4 080 files  + 6   4 080 suites  +6   56m 7s ⏱️ + 1m 54s
 4 233 tests + 3   4 162 ✅ + 3   71 💤 ±0  0 ❌ ±0 
25 398 runs  +18  24 968 ✅ +18  430 💤 ±0  0 ❌ ±0 

Results for commit af0c79b. ± Comparison against base commit 603b9c6.

♻️ This comment has been updated with latest results.

…нивое

Первая версия правки ставила соединение описания с выводом на путь чтения,
который зовётся при каждом разрешении вызова, и вдобавок читала поля через
getLocalFields — а тот склеивает жадные поля с ленивыми, то есть форсирует их.
Ленивое поле у рекурсивных и `см.`-типов ведёт обратно в getReturnTypes того же
метода, и разворот шёл вглубь на каждом чтении: в прогоне по ssl_3_1 — 212
переполнений стека, проглоченных обработчиком, и катастрофическое время.

Теперь соединение считается один раз и снимается записью любого из источников,
а сырые карты полей читаются без склейки — ленивое перекладывается ленивым.
Резолверы узла рекурсии тоже перестали форсировать: и по полям, и по элементам
они читают жадный состав, разрешая ровно один уровень, ради чего узел и заведён.

Прогон по ssl_3_1: переполнений стека ноль, время 60 и 71 секунда против 63 и 62
у develop, замечаний 29 220 и 29 024 против 30 235 и 30 334.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cu7S3zYn7n6GMsdYf5v1q
@nixel2007

Copy link
Copy Markdown
Member Author

Просадку нашёл и переработал в — вы были правы, вливать первую версию было нельзя.

Что я сделал не так. Соединение описания с выводом встало на путь чтения: getReturnTypes зовётся при каждом разрешении вызова, а я туда положил копию карты полей. Хуже того, поля читались через getLocalFields, а он склеивает жадные с ленивыми — то есть форсирует ленивое. У рекурсивных и см.-типов ленивое поле ведёт обратно в getReturnTypes того же метода, и разворот шёл вглубь на каждом чтении. В прогоне по ssl_3_1 это давало 212 переполнений стека, молча проглоченных обработчиком в инференсере, — и то самое катастрофическое время.

Что изменено:

  • соединение считается один раз и снимается записью любого из источников (putReturnTypes, clear, reindexDeclared — все четыре точки записи проверены по коду);
  • сырые карты localFields()/lazyFields() читаются раздельно, ленивое перекладывается ленивым;
  • резолверы узла рекурсии тоже перестали форсировать — и по полям, и по элементам они читают жадный состав. Узел разрешает ровно один уровень, ради чего он и заведён; форс замыкал его на себя.

Замеры на ssl_3_1 (прогоны вперемежку, машина на сети, сканер молчит):

замечаний время
develop 30 235 / 30 334 63 / 62 с
ветка 29 220 / 29 024 60 / 71 с

Переполнений стека — ноль. Разница по времени в пределах разброса этой машины; окончательное слово за бенчмарком CI.

Отдельно отмечу находку, которая шире этого PR: обработчик catch (StackOverflowError | RuntimeException) в инференсере скрывает такие ошибки — расчёт молча отдаёт пустой тип, и внешне это выглядит как «типы не выводятся», а не как поломка. Ровно поэтому первая версия прошла все тесты.

@nixel2007

Copy link
Copy Markdown
Member Author

Просадку нашёл и переработал в ab7a071 — вы были правы, вливать первую версию было нельзя. (Поправка к предыдущему комментарию: там номер коммита съела оболочка.)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/index/SymbolTypeIndex.java`:
- Line 189: Fix concurrent cache publication in reindexDeclared and
putReturnTypes so an outdated reader cannot repopulate combinedReturnTypes after
source updates; use version validation against declared and inferred snapshots
or the same per-method synchronization for source publication and cache
computation. Ensure both interleavings are covered by a barrier-based
concurrency test, preserving correct return types for subsequent reads.

In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/types/inferencer/ExpressionTypeInferencer.java`:
- Around line 829-843: Update the field and element guards in the surrounding
inferencer method to read raw maps directly from known.localFields() and
known.elementTypes(), rather than calling getLocalFields(ref) or other accessors
that force lazy values. Preserve the existing one-level LazyTypeSet creation via
withLazyField and withLazyElement, so self-referential fields and elements are
installed before any lazy evaluation occurs.
🪄 Autofix

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 Plus

Run ID: 83f93d6c-85a3-43e0-b255-c0d45c505bd1

📥 Commits

Reviewing files that changed from the base of the PR and between 2bbe4a1 and ab7a071.

📒 Files selected for processing (2)
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/SymbolTypeIndex.java
  • src/main/java/com/github/_1c_syntax/bsl/languageserver/types/inferencer/ExpressionTypeInferencer.java

nixel2007 and others added 4 commits August 11, 2026 18:57
Снятие запомненного при записи ничего не гарантировало: читатель, взявший
источники до снятия, укладывал результат уже после него, и устаревшее значение
оставалось жить — новых поводов снять его не возникало.

Теперь запомненное хранится вместе с источниками, из которых посчитано, и
годится, только если оба совпали по ссылке. Наборы неизменяемы, запись кладёт
другой объект — этого достаточно, а гонки между записью и чтением нет вовсе.
В очистке документа запись снимается по-прежнему, но уже только ради памяти.

Заодно перечисление в узле рекурсии перестало форсировать ленивое: имена полей
берутся из сырых карт, ленивые поля перекладываются как есть, а состав элементов
проверяется по жадной карте. Прежде форс мог развернуть самоссылку ещё до того,
как узел будет установлен.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cu7S3zYn7n6GMsdYf5v1q
Сонар показал 69 % на новом коде: непокрытыми оставались перенос ленивых полей
при соединении, перекладывание их в узле и тело его резолвера. Три теста бьют
именно туда: правка тела обесценивает запомненное, ленивое поле рекурсивной
функции переживает соединение с описанием и разрешается на чтении.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…о уровня

Обе ветки, переносившие ленивые поля, не исполняются ни в одном тесте, и это не
пробел покрытия, а свойство системы: у сохранённого значения метода ленивых полей
верхнего уровня не бывает — ленивость узла живёт уровнем ниже, внутри значения
поля. Я написал их на случай, которого не возникает.

Сырое чтение карт остаётся: оно и было нужно, чтобы не форсировать ленивое.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Имена вложенных полей узел знает без разворота, поэтому прежняя проверка резолвер
не запускала вовсе. Теперь тест читает тип вложенного поля — то есть ссылка
обязана разрешиться в посчитанное значение, а не в пустоту.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nixel2007
nixel2007 merged commit 0b0a484 into develop Aug 11, 2026
32 of 33 checks passed
@nixel2007
nixel2007 deleted the fix/declared-type-keeps-fields branch August 11, 2026 19:37
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant