Skip to content

feat: implement index system with LSP query handlers - #382

Merged
16bit-ykiko merged 12 commits into
mainfrom
feature/index-system
Apr 1, 2026
Merged

16bit-ykiko merged 12 commits into
mainfrom
feature/index-system

Conversation

@16bit-ykiko

@16bit-ykiko 16bit-ykiko commented Mar 31, 2026 •

Copy link
Copy Markdown
Member

Summary

Implement the complete index system for cross-file LSP features. This adds persistent two-tier indexing (ProjectIndex + per-file MergedIndex shards), background indexing triggered on idle, and index-based query handlers for major LSP requests.

Index Data Layer (src/index/)

  • TUIndex: Add binary serialization/deserialization via FlatBuffers, enabling IPC between stateless worker and master server
  • ProjectIndex: Add symbol name/kind storage, PathPool path normalization (backslash -> forward slash), and binary persistence
  • MergedIndex: Add content field to store file content for reliable offset<->position mapping; add removed bitmap for garbage collection of deleted entries; filter removed IDs in lookup() queries
  • schema.fbs: Add TUIndex tables, Symbol.name field, MergedIndex.removed bitmap and MergedIndex.content string

Server (src/server/)

  • Background indexing: Idle-triggered coroutine dequeues files from CDB, dispatches IndexParams to stateless workers, merges returned TUIndex into ProjectIndex/MergedIndex, and persists to .clice/index/
  • Index persistence: save_index() / load_index() for startup restoration; only rewrites shards flagged need_rewrite()
  • LSP handlers:
    • textDocument/definition -- index-first lookup with stateful worker fallback
    • textDocument/references -- cross-file reference query via index
    • callHierarchy/prepare, incomingCalls, outgoingCalls -- Caller/Callee relation traversal
    • typeHierarchy/prepare, supertypes, subtypes -- Base/Derived relation traversal
    • workspace/symbol -- case-insensitive substring search over ProjectIndex symbols
  • Stateless worker: Add Index request handler that builds TUIndex from compiled AST and returns serialized data
  • Config: Add enable_indexing (default true) and idle_timeout_ms (default 3000ms)

Fixes and Cross-platform

  • ElaboratedType handling in decl_of() for correct Base/Derived relation emission
  • Windows path normalization in PathPool::intern() and ProjectIndex::from() (backslash -> forward slash)
  • .gitattributes: Force LF in tests/data/** to prevent CRLF byte-offset mismatches on Windows CI
  • Test fixture: Clean .clice/ before each test for hermetic index state

Tests

  • 370-line index_query_tests.cpp: unit tests for occurrence lookup, relation queries, content retrieval, removed bitmap filtering
  • 282-line test_index.py: E2E integration tests for GoToDefinition, FindReferences, CallHierarchy (prepare/incoming/outgoing), TypeHierarchy (prepare/supertypes/subtypes), WorkspaceSymbol
  • Updated existing MergedIndex and ProjectIndex tests for new schema fields

Test plan

  • 414 C++ unit tests pass (including new IndexQuery, MergedIndex, ProjectIndex tests)
  • 69 Python integration tests pass (including 10 new index feature tests)
  • CI green on Linux, macOS, Windows
  • Manual smoke test with VSCode extension

@coderabbitai

coderabbitai Bot commented Mar 31, 2026 •

Copy link
Copy Markdown

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

Adds persistent TU/project indexing and PCH caching: FlatBuffers TUIndex persistence and schema extensions (symbol names, removed bitmap), merged-index removal tracking and filtering, ProjectIndex path normalization and name round-trips, MasterServer background indexing and PCH reuse, index-driven LSP handlers, and tests for indexing and PCH behavior.

Changes

Cohort / File(s) Summary
Merged & Project Index
src/index/merged_index.cpp, src/index/project_index.cpp, src/index/project_index.h
Persist and load removed bitmap; filter cached occurrences/relations by removed; clear occurrences cache after merges; refactor remove(path_id) to decrement refs for header vs compilation contexts; populate/serialize Symbol.name; normalize path separators when interning/looking up paths.
FlatBuffers Schema
src/index/schema.fbs
Added Symbol.name : string and MergedIndex.removed : [ubyte]; introduced TUFileRelationsEntry, TUFileIndexEntry, and TUIndex tables for TU-level index persistence.
TUIndex (serialize/from)
src/index/tu_index.h, src/index/tu_index.cpp
Added TUIndex::serialize(llvm::raw_ostream&) and static TUIndex::from(const void*); serialize/deserialize paths, locations, symbols (with refs bitmaps), per-file occurrences/relations and a main file index; added path_file_indices for deserialized path-keyed indices.
MasterServer & Workers & Protocol
src/server/master_server.cpp, src/server/master_server.h, src/server/protocol.h, src/server/stateless_worker.cpp
Added PCH preamble bound/hash cache and ensure_pch; extended BuildPCHParams/result with preamble_bound/pch_path; worker returns serialized TUIndex and pch_path; added background indexing pipeline (queue, scheduler, merge, persistence) and index-driven LSP handlers (definition, references, call/type hierarchy, workspace symbols); PCH invalidation on close/save.
PathPool / Path normalization
src/support/path_pool.h
Normalize backslashes to forward slashes in PathPool::intern (and added SmallString usage) to canonicalize paths on input before interning.
IncludeGraph const-fix
src/index/include_graph.h
Made path_id(clang::FileID) a const member function.
AST Utility
src/semantic/ast_utility.cpp
Unwrap clang::ElaboratedType at start of decl_of(QualType) so downstream resolution operates on the named type.
TUIndex Builder fix
src/index/tu_index.cpp
Builder relation construction now sets Relation.kind from provided kind parameter instead of hardcoding.
Server Config
src/server/config.h, src/server/config.cpp
Added CliceConfig::index_dir with default <cache_dir>/index when unset; ${workspace} substitution applied to index_dir.
Tests: Integration & Workspaces
tests/data/index_features/*, tests/data/pch_test/*, tests/integration/test_index.py, tests/integration/test_pch.py
Added index_features and pch_test workspaces and integration tests exercising index-driven LSP features and PCH behaviors (diagnostics, recompilation on body edit, hover, completion, no-PCH case).
Tests: Unit
tests/unit/compile/compilation_tests.cpp, tests/unit/index/*, tests/unit/server/*
Added preamble hash tests; MergedIndex removal and cache-invalidation tests; ProjectIndex name round-trip test; comprehensive IndexQuery suite for multi-shard queries; PCH worker tests; updated stateless worker PCH assertions.
Misc tests changes
tests/unit/server/stateless_worker_tests.cpp, tests/unit/server/pch_worker_tests.cpp
Updated tests to assert pch_path presence and success flags; added build-and-compile PCH integration tests.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant MasterServer
    participant PCHCache
    participant Worker
    participant Disk

    Note over Client,Disk: PCH Build & Reuse Flow

    Client->>MasterServer: didOpen/didChange (text)
    MasterServer->>MasterServer: compute_preamble_bound(text)
    MasterServer->>PCHCache: lookup by preamble_hash

    alt cached and hash matches
        PCHCache-->>MasterServer: cached pch_path
    else build new
        MasterServer->>Worker: BuildPCHParams(args, text, preamble_bound)
        Worker->>Worker: build PCH
        Worker->>Disk: write PCH file
        Worker-->>MasterServer: BuildPCHResult(pch_path)
        MasterServer->>PCHCache: store pch_path,preamble_bound,pch_hash
    end

    MasterServer->>Worker: CompileParams(..., pch={pch_path,preamble_bound})
    Worker->>Worker: compile with PCH
    Worker-->>MasterServer: CompileResult
    MasterServer-->>Client: diagnostics
Loading
sequenceDiagram
    actor User
    participant MasterServer
    participant IndexQueue
    participant Worker
    participant ProjectIndex
    participant MergedIndices
    participant Disk

    Note over User,Disk: Background Indexing Pipeline

    User->>MasterServer: initialize workspace
    MasterServer->>Disk: load_index()
    Disk-->>MasterServer: persisted ProjectIndex & MergedIndices
    MasterServer->>IndexQueue: populate from CDB
    MasterServer->>MasterServer: schedule_indexing()

    loop idle indexing
        MasterServer->>IndexQueue: dequeue file_id
        MasterServer->>Worker: Index request (stateless)
        Worker->>Worker: build TUIndex & serialize
        Worker-->>MasterServer: serialized TUIndex
        MasterServer->>ProjectIndex: merge symbols/refs
        MasterServer->>MergedIndices: merge occurrences/relations
    end

    MasterServer->>Disk: save_index()
    Disk->>Disk: persist
Loading
sequenceDiagram
    actor Client
    participant MasterServer
    participant ProjectIndex
    participant MergedIndices
    participant AST

    Note over Client,AST: Index-Based LSP Query Flow

    Client->>MasterServer: textDocument/definition `@pos`
    MasterServer->>MasterServer: lookup_symbol_at_position()
    MasterServer->>ProjectIndex: find symbol
    MasterServer->>MasterServer: query_index_relations(symbol, Definition)

    alt index hit
        MasterServer->>MergedIndices: query shards for relation
        MergedIndices-->>MasterServer: locations
        MasterServer-->>Client: definition locations
    else fallback
        MasterServer->>AST: stateful lookup
        AST-->>MasterServer: definition location
        MasterServer-->>Client: definition location
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hopped through bytes and preambles in the night,

I cached a PCH and kept the index light.
FlatBuffers cradled symbols safe and bright,
Background indexing hummed until first light.
Hop — the rabbit’s proud: search now takes flight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: implement index system with LSP query handlers' directly and accurately describes the main changes—implementing a complete index system with corresponding LSP query handlers.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feature/index-system

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/index/merged_index.cpp (2)

415-436: ⚠️ Potential issue | 🟠 Major

Missing removed bitmap filtering in buffer-based lookup path.

When self.impl is null but self.buffer exists, the lookup iterates over serialized occurrences without checking the removed bitmap. This creates an inconsistency: in-memory lookups (lines 393-403) filter removed entries, but buffer-based lookups return stale data.

Consider either:

  1. Loading the removed bitmap from the buffer and filtering, or
  2. Calling load_in_memory() at the start of lookup to ensure consistent behavior
Potential fix approach
     } else if(self.buffer) {
         auto index = fbs::GetRoot<binary::MergedIndex>(self.buffer->getBufferStart());
         auto& occurrences = *index->occurrences();
+        
+        // Deserialize removed bitmap for filtering
+        roaring::Roaring removed_bitmap;
+        if(index->removed() && index->removed()->size() > 0) {
+            removed_bitmap = read_bitmap(index->removed());
+        }

         auto it = std::ranges::lower_bound(occurrences, offset, {}, [](auto o) {
             return o->occurrence()->range().end();
         });

         while(it != occurrences.end()) {
             auto o = safe_cast<Occurrence>(it->occurrence());
             if(o->range.contains(offset)) {
+                // Skip if all canonical_ids are removed
+                if(!removed_bitmap.isEmpty()) {
+                    auto context_bitmap = read_bitmap(it->context());
+                    auto remaining = context_bitmap - removed_bitmap;
+                    if(remaining.isEmpty()) {
+                        it++;
+                        continue;
+                    }
+                }
                 if(!callback(*o)) {
                     break;
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index/merged_index.cpp` around lines 415 - 436, The buffer-based lookup
path in the lookup routine currently iterates serialized occurrences from
self.buffer (using fbs::GetRoot<binary::MergedIndex> and occurrences) without
applying the removed bitmap check, causing stale entries to be returned when
self.impl is null; to fix, either load and consult the removed bitmap from the
on-disk buffer before iterating (extract the removed bitmap field from the
MergedIndex in the buffer and skip occurrences whose ordinal is marked removed)
or simply call load_in_memory() at the start of lookup when self.impl is null so
the existing in-memory removed-bit filtering logic is reused; update the branch
that handles self.buffer to perform one of these two actions and ensure the
callback(*o) is only invoked for non-removed occurrences.

465-482: ⚠️ Potential issue | 🟠 Major

Same issue: missing removed filtering in buffer-based relation lookup.

The relation lookup from buffer (lines 465-482) also doesn't filter by the removed bitmap, creating the same inconsistency as the occurrence lookup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index/merged_index.cpp` around lines 465 - 482, When iterating relations
from the buffer-based lookup in merged_index.cpp (the branch using self.buffer,
fbs::GetRoot<binary::MergedIndex>, entries and it->relations()), add the same
removed-bitset filtering used by the occurrence lookup: obtain the removed
bitmap from the flatbuffer index and skip any relation whose id/index is marked
removed before checking r->kind and calling callback. In practice, retrieve the
removed bitmap from index (the same field used elsewhere), extract each
relation's identifier (from entry->relation() or the Relation object), test it
against the removed bitmap, and continue/skip if removed; only then apply the
kind check and invoke callback.
🧹 Nitpick comments (2)
tests/integration/test_pch.py (1)

76-104: Test may pass without meaningful completion verification.

The completion test only asserts result is not None, which passes even if completion returns an empty list. Consider asserting that completion items actually contain "Point" (from common.h):

Suggested improvement
     result = await client.text_document_completion_async(
         CompletionParams(
             text_document=_doc(uri),
             position=Position(line=last_line, character=3),
         )
     )
-    # Completion should return results.
-    assert result is not None
+    # Completion should return results including Point from common.h
+    assert result is not None
+    items = result.items if hasattr(result, 'items') else result
+    labels = [item.label for item in items] if items else []
+    assert "Point" in labels, f"Expected 'Point' in completions, got: {labels}"
     client.text_document_did_close(DidCloseTextDocumentParams(text_document=_doc(uri)))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/test_pch.py` around lines 76 - 104, The
test_completion_with_pch currently only checks that result is not None, which
can pass for empty completions; update the assertion to verify that completion
items include the expected symbol from the PCH (e.g., "Point" from common.h).
Locate the test_completion_with_pch function and change the post-request
assertions on result (returned by text_document_completion_async /
CompletionParams) to assert that result.items (or the result list) is non-empty
and contains an entry whose label equals "Point" (or contains "Point"), and keep
the existing cleanup (text_document_did_close) intact.
src/index/schema.fbs (1)

20-25: Consider conventional FlatBuffers field formatting.

The field declarations use an unconventional multi-line format:

sha256:
    string;

While valid, the conventional single-line format is more readable:

sha256: string;

This appears throughout the file. Consider reformatting for consistency with FlatBuffers conventions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index/schema.fbs` around lines 20 - 25, The FlatBuffers schema uses
multi-line field declarations (e.g., in table CacheEntry for sha256 and
canonical_id) which is unconventional; update those fields to the conventional
single-line format (e.g., "sha256: string;" and "canonical_id: uint;")
throughout schema.fbs, ensuring every table/field follows the single-line "name:
type;" style for consistency and readability (search for occurrences of
"sha256", "canonical_id" and similar multi-line declarations and collapse them
into single-line declarations).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/index/project_index.cpp`:
- Around line 90-94: ProjectIndex::from() currently dereferences
entry->symbol()->name() without checking for null; update the loop that
populates index.symbols (the block using entry->symbol()->name(), SymbolKind,
and read_bitmap) to guard against a missing name field (as loaded by
MasterServer::load_index()). Specifically, check whether entry->symbol() and
entry->symbol()->name() are non-null before calling ->str(); if null, set
symbol.name to a safe default (e.g., empty string or a placeholder) so
deserialization of older persisted indices does not crash. Ensure the rest of
the assignments (symbol.kind = SymbolKind(...), symbol.reference_files =
read_bitmap(...)) remain unchanged.

In `@src/index/tu_index.cpp`:
- Around line 259-260: Change TUIndex::from(const void* data) to accept a size
parameter (e.g., TUIndex::from(const void* data, size_t size)) and update
callers (notably merge_index_result()) to pass the byte count; inside from() use
flatbuffers::Verifier on the reinterpret_cast<const uint8_t*>(data) and the
supplied size and call binary::VerifyTUIndexBuffer(verifier) (or the generated
verify function) and return a recoverable failure (e.g., std::nullopt or an
error) if verification fails instead of calling
fbs::GetRoot<binary::TUIndex>(data); alternatively ensure flatc is invoked with
--gen-verify in CMakeLists so VerifyTUIndexBuffer exists.

In `@src/server/master_server.cpp`:
- Around line 231-232: Before calling schedule_indexing(), re-populate
index_queue with any files that were edited/changed since workspace load so the
background indexer processes them; implement a helper (e.g.,
requeue_edited_files or merge_edited_into_index_queue) that takes the
edited/changed file set used by the compile step and pushes those paths into
index_queue (avoiding duplicates) and call that helper from the same
post-compile sites that now call schedule_indexing() (the code that calls
schedule_indexing() and uses index_queue should be updated to invoke the requeue
helper first).
- Around line 607-619: The scheduling logic allows multiple background indexing
coroutines to be queued because indexing_active only flips true after the idle
timer; introduce a scheduling guard (e.g., a new bool member like
indexing_scheduled) and use it in MasterServer::schedule_indexing() to prevent
re-queuing: check indexing_scheduled at the top, set it to true before
creating/starting index_idle_timer and calling
loop.schedule(run_background_indexing()), and then clear indexing_scheduled
inside the run_background_indexing() coroutine when it actually begins or
finishes so future idle windows can schedule again; apply the same guard to the
other similar scheduling path that currently queues run_background_indexing() so
only one idle pass drains index_queue_pos at a time (reference symbols:
schedule_indexing(), indexing_active, index_idle_timer,
run_background_indexing(), index_queue_pos).
- Around line 1031-1044: The resolve_hierarchy_item function currently ignores
the stored info.hash in TypeHierarchyItem::data and calls
lookup_symbol_at_position (which fails for unopened files); change
resolve_hierarchy_item to first extract the symbol hash from item.data (the
int64_t stored by build_type_hierarchy_item) and use a symbol-hash based lookup
path (e.g., a find/lookup-by-hash helper in the symbol table/index) to resolve
the SymbolInfo even when the file is not open, falling back only to
lookup_symbol_at_position if the data hash is missing; update any callers or
helpers accordingly so incoming/outgoing/type-hierarchy expansion uses the
stored hash instead of requiring documents to be loaded.
- Around line 419-423: When compute_preamble_bound(text) returns 0 or when a PCH
rebuild fails, we must clear any stale cache entries so pch_paths and pch_bounds
do not keep serving an invalid PCH; update the early-return branch where bound
== 0 to erase the entry for this file from pch_paths and pch_bounds before
co_return true, and likewise on any rebuild/failure path in the PCH creation
logic (the code that attempts the rebuild and currently returns false) ensure it
removes the same pch_paths/pch_bounds entries for the file (use the same key
used to index those maps) so callers no longer receive stale PCHs.
- Around line 658-663: run_background_indexing currently sends only
params.file/params.directory/params.arguments to workers so shards built from
on-disk content can be used to answer position-based queries against the live
editor buffer in query_index_relations(), causing wrong/missing results for
unsaved edits; fix by tagging these background-indexed shards as disk-only and
making query_index_relations refuse position-based index queries against
disk-only shards. Concretely: in run_background_indexing (where IndexParams
params is constructed) set a boolean flag on IndexParams such as
params.disk_only = true (or params.version/file_snapshot = empty) when no live
buffer was provided, and then in query_index_relations detect that the target
shard has disk_only true and, for queries that resolve by cursor/offset, return
a non-indexed fallback (e.g., skip the shard or signal a miss) so only shards
built with live buffer state answer position-based queries.
- Around line 760-764: The offset for completion/signature requests is computed
from doc.text after the co_await ensure_pch, so a concurrent didChange can make
wp.text and wp.offset refer to different revisions; to fix, capture the same
text snapshot used for wp.text and compute wp.offset from that snapshot before
awaiting ensure_pch (or compute both wp.text and wp.offset into local variables
from the same snapshot and then assign them to wp), updating references around
ensure_pch/ wp.pch; key symbols: ensure_pch, wp.text, wp.offset, doc.text,
pch_paths, pch_bounds, wp.pch.

In `@tests/integration/test_index.py`:
- Around line 181-203: The test test_type_hierarchy_supertypes uses the wrong
position for the 'Dog' symbol; update the Position passed into
TypeHierarchyPrepareParams in test_type_hierarchy_supertypes so the line is 8
(Position(line=8, character=7)) instead of 7, then run the test to ensure the
TypeHierarchyPrepareParams -> type_hierarchy_supertypes_async flow returns the
expected supertypes for the Dog symbol.
- Around line 106-128: The test test_call_hierarchy_incoming is preparing the
call hierarchy at the wrong source position; update the Position used in
CallHierarchyPrepareParams inside test_call_hierarchy_incoming so it queries
line 18 instead of line 17 (i.e., change Position(line=17, character=4) to
Position(line=18, character=4)) when calling
client.text_document_prepare_call_hierarchy_async so the incoming-call check for
"add" matches the actual symbol location.
- Around line 161-178: The test test_type_hierarchy_prepare uses the wrong
0-based line for 'Dog'—update the Position passed to TypeHierarchyPrepareParams
(currently Position(line=7, character=7)) to the correct 0-based line for the
struct declaration (Position(line=8, character=7)) so the prepareTypeHierarchy
query targets the actual 'struct Dog : public Animal {' location; keep the rest
of the test (client calls, asserts, and close) unchanged.
- Around line 86-104: The test test_call_hierarchy_prepare is using the wrong
Position for the 'add' definition: it sets Position(line=17, character=4) but
the function signature int add(int a, int b) { is actually at line 18
(0-indexed); update the Position in the CallHierarchyPrepareParams for
test_call_hierarchy_prepare to Position(line=18, character=4) so the prepare
call targets the actual 'add' CallHierarchyItem (refer to
CallHierarchyPrepareParams, Position, and the test_call_hierarchy_prepare
function).
- Around line 33-52: The test_goto_definition uses incorrect source coordinates
for the 'add' call and its definition; update the DefinitionParams position to
point to the call at line 24, character 12 (0-indexed) and update the expected
definition check to assert any loc.range.start.line == 18 (where int add(...) is
declared) so the test matches main.cpp; adjust only the Position(line=...,
character=...) and the assertion comparing loc.range.start.line in this test.
- Around line 131-153: The test test_call_hierarchy_outgoing is preparing the
call hierarchy at the wrong line for compute; update the Position passed to
text_document_prepare_call_hierarchy_async from line=22 to line=23 (keep
character=4) so the Prepare call targets the `int compute()` declaration; adjust
the inline comment if desired and rerun the test to confirm outgoing calls
include `add`.
- Around line 60-78: The test test_find_references uses the wrong 0-indexed
position for global_var and its usages; update the Position passed into
ReferenceParams (used by client.text_document_references_async) from line=29 to
line=30 to point at the actual declaration, and update the expected usage line
comments/expectations from lines 32 and 36 to 33 and 37 respectively (the
assertion len(result) >= 3 can remain). Also adjust any inline comment that
states the declaration/usage line numbers to match the corrected 0-indexed
lines.

In `@tests/integration/test_pch.py`:
- Around line 22-30: The pch_test workspace used by test_pch_diagnostics_on_open
lacks a CMakeLists.txt so compile_commands.json isn't generated and the test
only sees vacuous empty diagnostics; add a CMakeLists.txt to the pch_test
workspace that declares a target for main.cpp (and any headers), configures
compiler flags/include paths, and enables generation of compile_commands.json
(e.g., via setting CMAKE_EXPORT_COMPILE_COMMANDS or a simple add_executable and
target_include_directories) so the test's CDB can be generated and the PCH build
path exercised when calling client.open_and_wait in
test_pch_diagnostics_on_open.

In `@tests/unit/server/stateless_worker_tests.cpp`:
- Around line 104-106: The test dereferences result.value() after a non-fatal
check; change the first check to a fatal assertion so we never dereference an
empty optional: replace EXPECT_TRUE(result.has_value()) with
ASSERT_TRUE(result.has_value()) (keep the subsequent
EXPECT_TRUE(result.value().success) and
EXPECT_FALSE(result.value().pch_path.empty()) as-is) so the test aborts on
missing value before calling result.value().

---

Outside diff comments:
In `@src/index/merged_index.cpp`:
- Around line 415-436: The buffer-based lookup path in the lookup routine
currently iterates serialized occurrences from self.buffer (using
fbs::GetRoot<binary::MergedIndex> and occurrences) without applying the removed
bitmap check, causing stale entries to be returned when self.impl is null; to
fix, either load and consult the removed bitmap from the on-disk buffer before
iterating (extract the removed bitmap field from the MergedIndex in the buffer
and skip occurrences whose ordinal is marked removed) or simply call
load_in_memory() at the start of lookup when self.impl is null so the existing
in-memory removed-bit filtering logic is reused; update the branch that handles
self.buffer to perform one of these two actions and ensure the callback(*o) is
only invoked for non-removed occurrences.
- Around line 465-482: When iterating relations from the buffer-based lookup in
merged_index.cpp (the branch using self.buffer,
fbs::GetRoot<binary::MergedIndex>, entries and it->relations()), add the same
removed-bitset filtering used by the occurrence lookup: obtain the removed
bitmap from the flatbuffer index and skip any relation whose id/index is marked
removed before checking r->kind and calling callback. In practice, retrieve the
removed bitmap from index (the same field used elsewhere), extract each
relation's identifier (from entry->relation() or the Relation object), test it
against the removed bitmap, and continue/skip if removed; only then apply the
kind check and invoke callback.

---

Nitpick comments:
In `@src/index/schema.fbs`:
- Around line 20-25: The FlatBuffers schema uses multi-line field declarations
(e.g., in table CacheEntry for sha256 and canonical_id) which is unconventional;
update those fields to the conventional single-line format (e.g., "sha256:
string;" and "canonical_id: uint;") throughout schema.fbs, ensuring every
table/field follows the single-line "name: type;" style for consistency and
readability (search for occurrences of "sha256", "canonical_id" and similar
multi-line declarations and collapse them into single-line declarations).

In `@tests/integration/test_pch.py`:
- Around line 76-104: The test_completion_with_pch currently only checks that
result is not None, which can pass for empty completions; update the assertion
to verify that completion items include the expected symbol from the PCH (e.g.,
"Point" from common.h). Locate the test_completion_with_pch function and change
the post-request assertions on result (returned by
text_document_completion_async / CompletionParams) to assert that result.items
(or the result list) is non-empty and contains an entry whose label equals
"Point" (or contains "Point"), and keep the existing cleanup
(text_document_did_close) intact.
🪄 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: 241a7c86-a4d9-42e0-962d-6a0f29a33e01

📥 Commits

Reviewing files that changed from the base of the PR and between 084f3b2 and ef2ca46.

📒 Files selected for processing (25)
  • src/index/merged_index.cpp
  • src/index/project_index.cpp
  • src/index/schema.fbs
  • src/index/tu_index.cpp
  • src/index/tu_index.h
  • src/semantic/ast_utility.cpp
  • src/server/config.cpp
  • src/server/config.h
  • src/server/master_server.cpp
  • src/server/master_server.h
  • src/server/protocol.h
  • src/server/stateless_worker.cpp
  • tests/data/index_features/CMakeLists.txt
  • tests/data/index_features/main.cpp
  • tests/data/pch_test/common.h
  • tests/data/pch_test/main.cpp
  • tests/data/pch_test/no_includes.cpp
  • tests/integration/test_index.py
  • tests/integration/test_pch.py
  • tests/unit/compile/compilation_tests.cpp
  • tests/unit/index/index_query_tests.cpp
  • tests/unit/index/merged_index_tests.cpp
  • tests/unit/index/project_index_tests.cpp
  • tests/unit/server/pch_worker_tests.cpp
  • tests/unit/server/stateless_worker_tests.cpp

Comment thread src/index/project_index.cpp Outdated
Comment thread src/index/tu_index.cpp
Comment thread src/server/master_server.cpp
Comment thread src/server/master_server.cpp
Comment thread src/server/master_server.cpp
Comment thread tests/integration/test_index.py
Comment thread tests/integration/test_index.py
Comment thread tests/integration/test_index.py
Comment thread tests/integration/test_pch.py
Comment thread tests/unit/server/stateless_worker_tests.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/server/master_server.h (1)

12-14: Add direct STL includes for header self-sufficiency.

std::vector (used at lines 96, 140, 147) and std::optional (used at lines 195, 199, 211) require <vector> and <optional> to be directly included. Currently, these types rely on transitive includes, which creates fragility.

Suggested diff
 `#include` <cstdint>
 `#include` <memory>
+#include <optional>
 `#include` <string>
+#include <vector>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/master_server.h` around lines 12 - 14, The header currently
including "index/merged_index.h", "index/project_index.h" and
"semantic/relation_kind.h" relies on transitive includes for std::vector and
std::optional; add direct includes for <vector> and <optional> at the top of
that header so types used (std::vector at the locations noted and std::optional
at the locations noted) are available without transitive dependencies, ensuring
the header is self-sufficient.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/server/master_server.h`:
- Around line 12-14: The header currently including "index/merged_index.h",
"index/project_index.h" and "semantic/relation_kind.h" relies on transitive
includes for std::vector and std::optional; add direct includes for <vector> and
<optional> at the top of that header so types used (std::vector at the locations
noted and std::optional at the locations noted) are available without transitive
dependencies, ensuring the header is self-sufficient.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 28a31a7d-56cc-4302-a181-50a3fc231e1e

📥 Commits

Reviewing files that changed from the base of the PR and between ef2ca46 and 16d76ea.

📒 Files selected for processing (7)
  • src/index/include_graph.h
  • src/index/project_index.cpp
  • src/index/tu_index.cpp
  • src/server/master_server.cpp
  • src/server/master_server.h
  • tests/integration/test_index.py
  • tests/unit/compile/compilation_tests.cpp
✅ Files skipped from review due to trivial changes (2)
  • src/index/include_graph.h
  • tests/integration/test_index.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/compile/compilation_tests.cpp
  • src/index/tu_index.cpp
  • src/server/master_server.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/index/project_index.h (1)

17-58: Consider consolidating path normalization logic.

Two separate PathPool classes exist (clice::index::PathPool here and clice::PathPool in src/support/path_pool.h) with nearly identical backslash normalization code. While both work correctly, this duplication increases maintenance burden if normalization behavior needs to change.

Consider extracting a shared normalize_path() helper or unifying these classes if their use cases permit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index/project_index.h` around lines 17 - 58, The PathPool::path_id
implementation duplicates backslash-normalization logic present elsewhere;
extract that logic into a shared helper (e.g., normalize_path(llvm::StringRef)
or normalize_path(llvm::SmallString<>&) in a new/central header) and call it
from PathPool::path_id (and the other clice::PathPool) instead of inlining the
replacement. Update PathPool::path_id to call the helper before cache lookup and
ensure save(...) still receives the normalized path; remove the duplicated
replace loop from the other PathPool and import the shared helper so both
classes use the same normalization routine.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/index/project_index.h`:
- Around line 17-58: The PathPool::path_id implementation duplicates
backslash-normalization logic present elsewhere; extract that logic into a
shared helper (e.g., normalize_path(llvm::StringRef) or
normalize_path(llvm::SmallString<>&) in a new/central header) and call it from
PathPool::path_id (and the other clice::PathPool) instead of inlining the
replacement. Update PathPool::path_id to call the helper before cache lookup and
ensure save(...) still receives the normalized path; remove the duplicated
replace loop from the other PathPool and import the shared helper so both
classes use the same normalization routine.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 29667716-d030-424f-9cda-a627cb7dde8a

📥 Commits

Reviewing files that changed from the base of the PR and between 16d76ea and cf1bba0.

📒 Files selected for processing (3)
  • src/index/project_index.cpp
  • src/index/project_index.h
  • src/support/path_pool.h

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server/master_server.cpp`:
- Around line 1386-1396: The current code concatenates JSON strings (ref_json
and def_json) which is fragile; instead parse both ref_json and def_json into
concrete objects (e.g., std::vector<protocol::Location>) using your JSON
deserializer, merge the two vectors, reserialize the merged vector back to a
JSON array string, and return that in serde_raw; update the block that
constructs merged (referencing refs, defs, ref_json, def_json, and serde_raw) to
perform deserialize->concat->serialize rather than manual substr splicing so
empty/whitespace/malformed arrays are handled correctly.
🪄 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: cc287b7a-74c8-42bf-af45-06c812a507c3

📥 Commits

Reviewing files that changed from the base of the PR and between cf1bba0 and ed5e59f.

📒 Files selected for processing (2)
  • src/index/project_index.h
  • src/server/master_server.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/index/project_index.h

Comment thread src/server/master_server.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/integration/test_index.py (1)

37-38: Replace fixed sleep with condition-based wait to reduce flakiness.

Using a hardcoded delay at Line 37 can still race on slower CI and adds avoidable runtime. Prefer polling for index readiness (or a helper that retries the query until success/timeout) instead of asyncio.sleep(15).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/test_index.py` around lines 37 - 38, The test currently
uses a fixed delay via asyncio.sleep(15), which is flaky; replace this with a
condition-based wait that polls the index until it is ready (e.g., loop that
retries a simple query/assert against the index client with short sleeps and a
global timeout) or call a helper like
wait_for_index_ready()/retry_until_success() instead; locate the await
asyncio.sleep(15) in tests/integration/test_index.py and implement polling
against the indexing function/client used in the test (or add a small retry
helper used by the test) so the test proceeds as soon as the index is ready and
fails only after a configurable timeout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/integration/test_index.py`:
- Line 52: The list comprehension in tests/integration/test_index.py uses an
ambiguous variable name `l` which triggers Ruff E741; rename that variable to a
clearer identifier (e.g., `loc` or `location`) in the comprehension expression
that builds the tuple list for `locs` and update any references inside the tuple
(the `.uri`, `.range.start.line`, `.range.start.character`) accordingly so the
assertion message reads something like: f"Expected line 18, got locations:
{[(loc.uri, loc.range.start.line, loc.range.start.character) for loc in locs]}".

---

Nitpick comments:
In `@tests/integration/test_index.py`:
- Around line 37-38: The test currently uses a fixed delay via
asyncio.sleep(15), which is flaky; replace this with a condition-based wait that
polls the index until it is ready (e.g., loop that retries a simple query/assert
against the index client with short sleeps and a global timeout) or call a
helper like wait_for_index_ready()/retry_until_success() instead; locate the
await asyncio.sleep(15) in tests/integration/test_index.py and implement polling
against the indexing function/client used in the test (or add a small retry
helper used by the test) so the test proceeds as soon as the index is ready and
fails only after a configurable timeout.
🪄 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: a7edbd4b-a20b-4397-a985-09bb057e652d

📥 Commits

Reviewing files that changed from the base of the PR and between ed5e59f and 0aba747.

📒 Files selected for processing (2)
  • src/server/master_server.cpp
  • tests/integration/test_index.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/server/master_server.cpp

Comment thread tests/integration/test_index.py Outdated
16bit-ykiko and others added 11 commits April 1, 2026 18:36
…andlers

Add complete index infrastructure: TUIndex build/serialize in StatelessWorker,
ProjectIndex/MergedIndex state management in MasterServer with background
idle-triggered indexing, and index-based LSP handlers for GoToDefinition,
FindReferences, CallHierarchy, TypeHierarchy, and WorkspaceSymbol.

Includes unit tests for index queries and Python E2E integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix merge_index_result include_locations filter collecting all locations
- Fix background indexing skipping all files on fresh start
- Fix schedule_indexing race condition with indexing_scheduled flag
- Fix CallHierarchyItem/TypeHierarchyItem with empty URI when def not found
- Fix handleMacroOccurrence ignoring kind parameter
- Fix const_cast in TUIndex::serialize by making path_id const
- Fix signed/unsigned comparison in project_index.cpp
- Remove incorrect file_text.empty() check in query_index_relations
- Fix Python E2E test line positions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Normalize backslashes to forward slashes in both server PathPool and
index PathPool to ensure consistent path matching across URI decoding,
CDB entries, and clang FileManager on Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add PathPool::find() method that normalizes backslashes before lookup,
replacing raw cache.find() calls that failed on Windows where
uri_to_path() returns backslash-separated paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add LOG_INFO/LOG_WARN at key points in query_index_relations,
lookup_symbol_at_position, merge_index_result, schedule_indexing,
and run_background_indexing to diagnose why index queries return
empty on Windows CI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded 15s sleeps with _wait_for_index() that polls
workspace/symbol until indexing is confirmed ready. This is both
faster (7s vs 150s locally) and more reliable on slower CI machines.

Also add diagnostic logging to server-side index query methods and
detailed assertion messages for easier debugging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add diagnostic prints in test_goto_definition to capture:
- workspace_symbol results with locations
- GoToDefinition at multiple positions
- FindReferences result
- CRLF detection in file content

This will reveal whether the issue is offset mismatch, path mismatch,
or MergedIndex not being populated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dump server [diag] and [warn] log lines after each test to understand
why index queries fail on Windows but work on Linux.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Python's read_text() normalizes CRLF to LF, causing byte offset mismatch
between didOpen content and index built from disk files on Windows.

- Add .gitattributes to force LF line endings for test data
- Read test files in binary mode to preserve line endings (like real LSP clients)
- Remove diagnostic logging added during debugging
- Downgrade query_index_relations warnings to debug level

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use the file content that was present at index build time for
offset↔position conversion instead of relying on didOpen text or disk
reads. This eliminates CRLF/LF mismatches on Windows and ensures
queries work even when the file is not open in the editor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Guard against null `name` field in ProjectIndex deserialization
- Use stored symbol hash from `data` field in hierarchy resolution
  instead of re-resolving from position (works for unopened files)
- Rename ambiguous variable `l` to `loc` in test assertions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@16bit-ykiko
16bit-ykiko force-pushed the feature/index-system branch from cc5c62c to 7fdeb4b Compare April 1, 2026 10:43
…ests

The workspace fixture now removes .clice directory before each test,
preventing stale index shards (missing content field) from causing
workspace/symbol and other index queries to return empty results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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