perf(types): кэш инференса + memo getMembers + индекс callStatement#3997
Conversation
inferVariable пересчитывал тип ресивера с нуля на каждый member-доступ: getReferencesTo + переинференс присваиваний, а для Структура/ТаблицаЗначений — полный скан AST модуля. На полном проходе семантических токенов по 47k-модулю один и тот же ресивер выводился сотни раз (~190с на сапплаер). Добавлен InferredVariableTypeIndex (types.index, workspace-scoped): ленивый кэш Map<URI, Map<VariableSymbol, TypeSet>>, разрезанный по URI. Пишется из inferVariable только для «чистого корня» инференса (visited ≤ 1) — вложенные, потенциально усечённые цикл-гардом выводы не кэшируются. Инвалидация per-URI на DocumentContextContentChangedEvent / ServerContextDocumentClosedEvent / ServerContextDocumentRemovedEvent. Наполнение только ленивое: populateContext шлёт content-changed на все документы, но обработчик лишь чистит URI — на старте кэш не разбухает. Замер (УправлениеДоступомСлужебный, SSL, 47k строк): property 195985→22375ms, method 184385→40524ms; токены и конфликты не изменились. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
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:
📝 WalkthroughWalkthroughAdds InferredVariableTypeIndex and CallStatementByReceiverIndex with per-document caches and event-driven invalidation, integrates them into ExpressionTypeInferencer (cache lookup/write and receiver-scoped call scans), and adds epoch-based memoization to TypeRegistry.getMembers; tests cover both indices' behavior. ChangesInferred Variable Type Caching, Call-Receiver Index & TypeRegistry Memoization
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Suggested labels
Suggested reviewers
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)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the 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 |
getMembers пересобирал список членов (а для config/generic-типов — переспециализировал) на КАЖДЫЙ вызов; на горячем пути (семантические токены, completion) это повторялось тысячи раз для одного типа. Добавлена мемоизация Map<(ref,fileType), (epoch,members)>. Инвалидация — через AtomicLong-epoch: бампается при любой мутации memberSources (register/unregister) И на DocumentContextContentChangedEvent — часть member-source'ов лениво читает символьное дерево/OScript-библиотеку и меняет вывод без ре-регистрации (см. упавший OScriptLibraryIndexTest, теперь зелёный). В steady-state (во время запроса, без правок) memo стабилен. Кэшируется неизменяемый список (все потребители только итерируют). Замер (УправлениеДоступомСлужебный, SSL) поверх кэша инференса: property 22375→13740ms, method 40524→24880ms; от базы — property 195985→13740 (~14×), method 184385→24880 (~7.4×). Поведение не изменилось, регрессия зелёная. Co-Authored-By: Claude Opus 4.8 <[email protected]>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java (1)
354-373:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSynchronize iteration over
memberSourcesvalues (Collections.synchronizedList) to meet the iteration contract.
registerMemberSource(...)storesmemberSourcesentries asCollections.synchronizedList(...), butcomputeMembers(...)iteratessourceswithoutsynchronized (sources), which violates the JDK contract for thread-safe iteration and can lead to inconsistent results orConcurrentModificationException.Suggested fix
private List<MemberDescriptor> computeMembers(TypeRef ref, FileType fileType) { var sources = memberSources.get(ref); if ((sources == null || sources.isEmpty()) && ref != null) { var canonical = aliasIndex.get(ref.qualifiedName().toLowerCase(Locale.ROOT)); if (canonical != null && !canonical.equals(ref)) { sources = memberSources.get(canonical); } } if (sources == null || sources.isEmpty()) { return List.of(); } + List<ScopedMemberSource> snapshot; + synchronized (sources) { + snapshot = List.copyOf(sources); + } var byName = new LinkedHashMap<String, MemberDescriptor>(); - for (var scoped : sources) { + for (var scoped : snapshot) { if (fileType != null && scoped.scope() != null && !scoped.scope().matches(fileType)) { continue; } for (var member : scoped.source().getMembers()) { byName.putIfAbsent(member.name().toLowerCase(Locale.ROOT), member); } } // Неизменяемый список: память шарится между вызовами, случайная мутация // упадёт сразу (все потребители только итерируют). return List.copyOf(byName.values()); }🤖 Prompt for 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. In `@src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java` around lines 354 - 373, The computeMembers method iterates over lists pulled from memberSources (assigned in registerMemberSource as Collections.synchronizedList) without synchronizing on the list, violating the synchronizedList iteration contract; update computeMembers to wrap iterations over the local variable sources (and any nested iterations over scoped.source().getMembers() if that returns a shared list) in synchronized(sources) blocks so all iterations over the synchronizedList are performed inside a synchronized(sources) section; keep existing logic and variable names (computeMembers, memberSources, sources, registerMemberSource, scoped, member) and only add the minimal synchronized(sources) scope around the loops that walk the list to avoid ConcurrentModificationException and ensure thread-safe iteration.
🤖 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.
Outside diff comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java`:
- Around line 354-373: The computeMembers method iterates over lists pulled from
memberSources (assigned in registerMemberSource as Collections.synchronizedList)
without synchronizing on the list, violating the synchronizedList iteration
contract; update computeMembers to wrap iterations over the local variable
sources (and any nested iterations over scoped.source().getMembers() if that
returns a shared list) in synchronized(sources) blocks so all iterations over
the synchronizedList are performed inside a synchronized(sources) section; keep
existing logic and variable names (computeMembers, memberSources, sources,
registerMemberSource, scoped, member) and only add the minimal
synchronized(sources) scope around the loops that walk the list to avoid
ConcurrentModificationException and ensure thread-safe iteration.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 667a7255-4058-4827-8eb1-46b727ce4c83
📒 Files selected for processing (1)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/registry/TypeRegistry.java
Аккумуляторы полей структур (X.Вставить) и колонок ТЗ (X.Колонки.Добавить) делали полный скан AST модуля (findAllRuleNodes(RULE_callStatement)) на каждую коллекционную переменную, чтобы найти её mutation-вызовы. Добавлен CallStatementByReceiverIndex (types.index, workspace-scoped): строит один раз на документ карту «базовый идентификатор → callStatement'ы», поиск становится hash-lookup'ом по имени переменной. Инвалидация per-URI на content-change / close / remove (индекс держит AST-узлы). Экстракторы возвращают базовый идентификатор цепочки, поэтому индексация по нему корректна; паттерн (Вставить / Колонки.Добавить) по-прежнему фильтруется в аккумуляторе. Замер (УправлениеДоступомСлужебный, SSL) поверх кэша+memo: property 13740→9803ms, method 24880→14331ms. От базы: property 195985→9803 (~20×), method 184385→14331 (~13×). Результаты инференса не изменились, регрессия зелёная, покрытие 100%. Co-Authored-By: Claude Opus 4.8 <[email protected]>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/test/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndexTest.java (2)
64-71: ⚡ Quick winVerify all receivers after each lifecycle event for comprehensive coverage.
The test currently re-asserts only
"ТЗ"after each event (lines 65, 68, 71), but does not re-verify"СТР"and"Нет". To ensure the entire index is correctly rebuilt after clearing, consider asserting all three cases after each event:eventPublisher.publishEvent(new DocumentContextContentChangedEvent(documentContext)); assertThat(index.byReceiver(uri, ast, "ТЗ")).hasSize(1); assertThat(index.byReceiver(uri, ast, "СТР")).hasSize(1); assertThat(index.byReceiver(uri, ast, "Нет")).isEmpty();This pattern would apply to all three event publications (lines 64, 67, 70).
Based on learnings: CR expects comprehensive tests covering edge cases for test files.
🤖 Prompt for 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. In `@src/test/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndexTest.java` around lines 64 - 71, The test only re-checks index.byReceiver(uri, ast, "ТЗ") after each lifecycle event; update the assertions after each published event (DocumentContextContentChangedEvent, ServerContextDocumentClosedEvent, ServerContextDocumentRemovedEvent) to verify all receivers by calling index.byReceiver for "ТЗ", "СТР", and "Нет" (expect sizes 1, 1, and 0 respectively) so the index rebuild/clear behavior is fully covered; locate these checks around the eventPublisher.publishEvent calls and add the two missing assertions after each event.
46-60: ⚡ Quick winConsider adding a test case for multiple call statements with the same receiver.
The current test verifies that a single call statement is grouped correctly, but does not cover the scenario where multiple call statements share the same receiver (e.g., two
ТЗ.Колонки.Добавить(...)calls). Adding such a case would confirm that the index correctly aggregates multiple calls.Based on learnings: CR expects comprehensive tests covering edge cases for test files.
🤖 Prompt for 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. In `@src/test/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndexTest.java` around lines 46 - 60, Update the CallStatementByReceiverIndexTest to include a second call with the same receiver and assert the index aggregates both: in the TestUtils.getDocumentContext string add another call like a second "ТЗ.Колонки.Добавить(...)" (or another call on receiver "ТЗ"), then use index.byReceiver(uri, ast, "ТЗ") to assert size 2; keep the existing case-insensitive check (index.byReceiver(uri, ast, "СТР")) and the empty check for "Нет" unchanged.
🤖 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.
Nitpick comments:
In
`@src/test/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndexTest.java`:
- Around line 64-71: The test only re-checks index.byReceiver(uri, ast, "ТЗ")
after each lifecycle event; update the assertions after each published event
(DocumentContextContentChangedEvent, ServerContextDocumentClosedEvent,
ServerContextDocumentRemovedEvent) to verify all receivers by calling
index.byReceiver for "ТЗ", "СТР", and "Нет" (expect sizes 1, 1, and 0
respectively) so the index rebuild/clear behavior is fully covered; locate these
checks around the eventPublisher.publishEvent calls and add the two missing
assertions after each event.
- Around line 46-60: Update the CallStatementByReceiverIndexTest to include a
second call with the same receiver and assert the index aggregates both: in the
TestUtils.getDocumentContext string add another call like a second
"ТЗ.Колонки.Добавить(...)" (or another call on receiver "ТЗ"), then use
index.byReceiver(uri, ast, "ТЗ") to assert size 2; keep the existing
case-insensitive check (index.byReceiver(uri, ast, "СТР")) and the empty check
for "Нет" unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 722398d8-9e0b-4bbf-b60b-1cc7e9c2033b
📒 Files selected for processing (3)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndex.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/inferencer/ExpressionTypeInferencer.javasrc/test/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndexTest.java
|
@claude review |
|
Claude finished @nixel2007's task in 2m 55s —— View job Code Review
Отличная работа с задокументированными результатами (~20x на горячем пути). Архитектура в целом корректная. Нашёл одну критическую проблему с thread-safety (её же нашёл CodeRabbit), несколько средних и минорных. 🔴 Critical:
|
memberSources хранит источники как Collections.synchronizedList, контракт которого требует ручной синхронизации при обходе через итератор. computeMembers обходил список расширенным for без synchronized — при конкурентной регистрации членов возможны ConcurrentModificationException или несогласованное чтение. Снимаем согласованный снимок под synchronized(sources) и итерируем его. Найдено ревью CodeRabbit и claude[bot] на PR #3997. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…умента В batch-анализе (AnalyzeCommand) и populateContext документ освобождается через ServerContext.tryClearDocument -> DocumentContext.clearSecondaryData, который НЕ публиковал ни одного события. Поэтому per-URI кэши инференса (InferredVariableTypeIndex, CallStatementByReceiverIndex) не сбрасывались после обработки файла и накапливали записи по всем файлам прогона. Особенно опасен CallStatementByReceiverIndex: он держит AST-узлы callStatement'ов, чьи parent-указатели дотягиваются до корня FileContext, — то есть удерживал бы разобранные деревья всех файлов, прямо отменяя смысл tryClearDocument. Публикуем DocumentContextDataClearedEvent на clearSecondaryData (аналогично DocumentContextContentChangedEvent на rebuild) и сбрасываем оба индекса по URI. Событие — «только инвалидация», без пересчёта, поэтому не наполняет кэши. Заодно ревью-правки PR #3997: - комментарий о ключевании кэша inferVariable по VariableSymbol без fileType (fileType детерминирован владельцем переменной); - задокументирована осознанная гонка clear<->computeIfAbsent в byReceiver; - тесты проверяют все ресиверы после каждого события жизненного цикла и покрывают сброс по DocumentContextDataClearedEvent. Co-Authored-By: Claude Opus 4.8 <[email protected]>
40d963a to
94beee1
Compare
В batch-анализе (AnalyzeCommand) и populateContext документ освобождается через ServerContext.tryClearDocument, который НЕ публиковал событий. Поэтому per-URI кэши инференса (InferredVariableTypeIndex, CallStatementByReceiverIndex) не сбрасывались после обработки файла и накапливали записи по всем файлам прогона. Особенно опасен CallStatementByReceiverIndex: он держит AST-узлы callStatement'ов, чьи parent-указатели дотягиваются до корня FileContext, — то есть удерживал бы разобранные деревья всех файлов, прямо отменяя смысл tryClearDocument. tryClearDocument теперь возвращает boolean (реально ли освободил данные), а EventPublisherAspect публикует новый ServerContextDocumentClearedEvent только при true. Точка выбрана осознанно: tryClearDocument не лежит на пути набора текста (там openDocument/rebuild по открытому документу), поэтому флуда событий при печати нет; при стартапе — одно событие на файл, тот же порядок, что и существующий ContentChangedEvent. Семантика отдельная от ClosedEvent: документ не закрывается, лишь освобождает резидентные данные. Оба индекса слушают новое событие и чистятся per-URI. Ревью-правки PR #3997: - комментарий о ключевании кэша inferVariable по VariableSymbol без fileType; - задокументирована осознанная гонка clear<->computeIfAbsent в byReceiver; - тесты проверяют все ресиверы после каждого события жизненного цикла, end-to-end покрывают реальный tryClearDocument через AOP и no-op на открытом документе. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
@claude re-review |
|
Claude finished @nixel2007's task in 2m 39s —— View job Code Review (Re-review)
Changes since the previous review✅ Critical thread-safety fix — // TypeRegistry.java:368–371
List<ScopedMemberSource> snapshot;
synchronized (sources) {
snapshot = List.copyOf(sources);
}
for (var scoped : snapshot) { ... }The fix is correct and the comment explaining why is a good addition. ✅ ✅ Remaining issues🔴 SonarQube Quality Gate fails: duplication > 3% on new codeThe four event-listener methods are verbatim identical in both // Identical 4-method block in BOTH files:
@EventListener
public void handleContentChanged(DocumentContextContentChangedEvent event) {
clear(event.getSource().getUri());
}
@EventListener
public void handleDataCleared(ServerContextDocumentClearedEvent event) {
clear(event.getDocumentContext().getUri());
}
@EventListener
public void handleDocumentClosed(ServerContextDocumentClosedEvent event) {
clear(event.getDocumentContext().getUri());
}
@EventListener
public void handleDocumentRemoved(ServerContextDocumentRemovedEvent event) {
clear(event.getUri());
}~20 duplicated lines × 2 occurrences ≈ 6% of the 668 added lines, which matches the reported 5.3% and B maintainability rating. This blocks the Quality Gate ( Fix: Extract to an abstract base class in the same package: // types/index/AbstractDocumentLifecycleClearableIndex.java
abstract class AbstractDocumentLifecycleClearableIndex {
public abstract void clear(URI uri);
@EventListener
public void handleContentChanged(DocumentContextContentChangedEvent event) {
clear(event.getSource().getUri());
}
@EventListener
public void handleDataCleared(ServerContextDocumentClearedEvent event) {
clear(event.getDocumentContext().getUri());
}
@EventListener
public void handleDocumentClosed(ServerContextDocumentClosedEvent event) {
clear(event.getDocumentContext().getUri());
}
@EventListener
public void handleDocumentRemoved(ServerContextDocumentRemovedEvent event) {
clear(event.getUri());
}
}Spring's 🟢 Minor: dead
|
SonarQube Quality Gate падал по дублированию (>3% на новом коде): четыре @EventListener-метода дословно совпадали в InferredVariableTypeIndex и CallStatementByReceiverIndex (~20 строк × 2). Вынес их в package-private AbstractDocumentLifecycleClearableIndex с abstract clear(URI); Spring наследует @eventlistener в конкретных подклассах-бинах, поэтому индексы просто его расширяют и реализуют clear(URI). Дубль убран. Заодно тест на агрегацию: byReceiver возвращает оба callStatement'а для одного ресивера (hasSize(2)) — ревью-замечание CodeRabbit. Co-Authored-By: Claude Opus 4.8 <[email protected]>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndex.java (1)
72-80:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winProtect cache invariants by returning immutable collections.
byReceivercurrently exposes mutable lists stored in cache; an accidental caller-side mutation will corrupt future lookups for that URI.♻️ Proposed fix
private static Map<String, List<BSLParser.CallStatementContext>> build(BSLParser.FileContext ast) { var index = new HashMap<String, List<BSLParser.CallStatementContext>>(); for (var call : Trees.<BSLParser.CallStatementContext>findAllRuleNodes(ast, BSLParser.RULE_callStatement)) { var identifier = call.IDENTIFIER(); if (identifier != null) { index.computeIfAbsent(identifier.getText().toLowerCase(Locale.ROOT), k -> new ArrayList<>()).add(call); } } - return index; + index.replaceAll((k, v) -> List.copyOf(v)); + return Map.copyOf(index); }Also applies to: 82-91
🤖 Prompt for 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. In `@src/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndex.java` around lines 72 - 80, byReceiver currently returns the mutable lists that are stored in the cached index (via byUri.computeIfAbsent(...) and the map produced by build), so caller-side mutation can corrupt the cache; update byReceiver to return an immutable copy (e.g., wrap the result with List.copyOf(...) or Collections.unmodifiableList(...)) and ensure any other similar accessors (the other method at ~82-91) also return immutable lists rather than exposing the cached mutable collections; keep the cache storage unchanged or, if feasible, make build() populate immutable lists so the cache invariants are preserved.
🤖 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.
Outside diff comments:
In
`@src/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndex.java`:
- Around line 72-80: byReceiver currently returns the mutable lists that are
stored in the cached index (via byUri.computeIfAbsent(...) and the map produced
by build), so caller-side mutation can corrupt the cache; update byReceiver to
return an immutable copy (e.g., wrap the result with List.copyOf(...) or
Collections.unmodifiableList(...)) and ensure any other similar accessors (the
other method at ~82-91) also return immutable lists rather than exposing the
cached mutable collections; keep the cache storage unchanged or, if feasible,
make build() populate immutable lists so the cache invariants are preserved.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 844a8bd5-e41a-490e-812a-c76a19d57ad5
📒 Files selected for processing (4)
src/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/AbstractDocumentLifecycleClearableIndex.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndex.javasrc/main/java/com/github/_1c_syntax/bsl/languageserver/types/index/InferredVariableTypeIndex.javasrc/test/java/com/github/_1c_syntax/bsl/languageserver/types/index/CallStatementByReceiverIndexTest.java
InferredVariableTypeIndex держит Map<VariableSymbol, …>, и Sonar (S6411) требует Comparable-ключ. Делаем VariableSymbol Comparable<VariableSymbol> с естественным порядком, согласованным с equals: имя -> URI владельца -> позиция имени. Чтобы порядок не аллоцировал, добавлены дешёвые int-аксессоры позиции имени (getVariableNameLine/StartCharacter/EndCharacter) — в отличие от getVariableNameRange(), который создаёт Range/Position. Порядок задан общим NATURAL_ORDER на интерфейсе; compareTo обязаны реализовать наследники (IntBased/ShortBased одной строкой через NATURAL_ORDER). Co-Authored-By: Claude Opus 4.8 <[email protected]>
… S1541) - MembersKey (ключ memo getMembers) реализует Comparable (S6411). Порядок null-safe: fileType штатно бывает null (getMembers(ref) зовёт getMembers(ref, null)), ref допускает null защитно. - computeMembers разбит на resolveMemberSources (alias-fallback) и snapshotOf (синхронизированный снимок) — цикломатическая сложность падает ниже порога (S1541), синхронный снимок synchronizedList сохранён. Co-Authored-By: Claude Opus 4.8 <[email protected]>
accumulateStructureInsertFields/accumulateValueTableColumnFields имели по >1 continue в цикле (S135). Тело каждого вынесено в helper (insertedStructureField / addedColumn) с ранними return; общий guard-префикс — в mutationCallParams, чтобы не плодить дублирование. Циклы стали одиночным if. Поведение прежнее (тесты инференса структур/ТЗ зелёные). Плюс S1612: лямбда -> method reference TypeRef::qualifiedName в тесте. Co-Authored-By: Claude Opus 4.8 <[email protected]>
В контракте интерфейса не место деталям реализации/зависимостям: из javadoc NATURAL_ORDER и int-аксессоров позиции убраны упоминания getVariableNameRange()/Range/Position и «без аллокации». Контрактное описание методов сохранено (summary + @return). Co-Authored-By: Claude Opus 4.8 <[email protected]>
b8ff397 to
6dea25b
Compare
Вынос снимка synchronizedList в snapshotOf(List) синхронизировался на параметре метода — Sonar S2445 (BUG, уронил Reliability до C). Возвращаем снимок под synchronized внутрь computeMembers на локальной переменной; resolveMemberSources (fallback по псевдониму) оставлен отдельно. Поведение и потокобезопасность те же. Co-Authored-By: Claude Opus 4.8 <[email protected]>
da8b1eb to
e4c3b16
Compare
Идентичные compareTo и геттеры позиции в IntBased/ShortBased давали дублирование на новом коде (>3%). compareTo вынесен в VariableSymbol как default (аксессоры позиции уже на интерфейсе). Геттеры позиции в IntBased теперь генерирует Lombok @value (сняты @Getter(NONE) с полей имени); в ShortBased оставлены вручную, т.к. поля short, а контракт требует int (Lombok расширение не делает) — единственная копия в исходниках, дубля нет. Co-Authored-By: Claude Opus 4.8 <[email protected]>
e4c3b16 to
4232531
Compare
|



Три связанные оптимизации горячего пути резолва членов (
memberAt→ инференс ресивера →getMembers), из-за которого полный проход семантических токенов по большому модулю занимал минуты.1. Ленивый кэш выведенных типов переменных (
InferredVariableTypeIndex)inferVariableпересчитывал тип ресивера с нуля на каждый member-доступ. КэшMap<URI, Map<VariableSymbol, TypeSet>>, разрезан по URI, ленивый, пишется только для «чистого корня» инференса (ctx.visited.size() <= 1). Инвалидация per-URI: content-change / close / remove. На стартеpopulateContextлишь чистит URI (не наполняет).2. Мемоизация
TypeRegistry.getMembers(epoch)getMembersпереспециализировал config/generic-члены на каждый вызов. MemoMap<(ref,fileType),(epoch,members)>сAtomicLong-epoch: бамп на мутациюmemberSourcesи наDocumentContextContentChangedEvent(часть source'ов лениво читает символьное дерево/OScript-библиотеку — менялись без ре-регистрации). Кэшируется неизменяемый список.3. Индекс callStatement по ресиверу (
CallStatementByReceiverIndex)Аккумуляторы полей структур (
X.Вставить) и колонок ТЗ (X.Колонки.Добавить) сканировали весь AST модуля на каждую коллекционную переменную. Индексбазовый идентификатор → callStatement'ыстроится раз на документ; поиск — hash-lookup. Инвалидация per-URI (держит AST-узлы): content-change / close / remove.Эффект (УправлениеДоступомСлужебный, SSL, 47k строк)
Результаты инференса / число токенов / конфликтов не изменились; полная регрессия (
types, completion, signature, hover, OScript, config) зелёная. Покрытие нового кода 100%.🤖 Generated with Claude Code
Summary by CodeRabbit