Skip to content

Implements support for the enclosing_range field in SCIP occurrences,#504

Open
simonhollis wants to merge 1 commit into
sourcegraph:mainfrom
simonhollis:enclosing-range
Open

Implements support for the enclosing_range field in SCIP occurrences,#504
simonhollis wants to merge 1 commit into
sourcegraph:mainfrom
simonhollis:enclosing-range

Conversation

@simonhollis

Copy link
Copy Markdown

Summary

Implements support for the enclosing_range field in SCIP occurrences, enabling IDE features like:

  • Call hierarchies (determining symbol references within function bodies)
    • Symbol outline/breadcrumbs (showing path from cursor to file root)
    • Expand selection (selecting nearest enclosing AST node)
    • Highlight range (indicating AST expression boundaries for hovers)

Changes

1. Updated SCIP Dependency

Updated schema to SCIP commit 9d5796159c97c3b107355c424f6c06fb1a0ea01c
which includes:

  • The enclosing_range field definition in the protobuf schema
  • The add_enclosing_range() API for populating individual values

2. Extended Indexer API

Added clang::SourceRange enclosingRange parameter (with default
empty value) to:

  • TuIndexer::saveDefinition()
  • TuIndexer::saveReference()
  • TuIndexer::saveOccurrence()
  • TuIndexer::saveOccurrenceImpl()

3. Implemented Range Population

Modified saveOccurrenceImpl() to populate the enclosing_range
field:

  • Format: [startLine-1, startColumn-1, [endLine-1], endColumn-1]
  • Uses 0-based coordinates as per SCIP specification
  • Omits endLine if range is single-line
  • Only populates when valid range is provided

enabling IDE features like:
- Call hierarchies (determining symbol references within function
  bodies)
  - Symbol outline/breadcrumbs (showing path from cursor to file root)
  - Expand selection (selecting nearest enclosing AST node)
  - Highlight range (indicating AST expression boundaries for hovers)

  ## Changes

  ### 1. Updated SCIP Dependency

  Updated to SCIP commit `9d5796159c97c3b107355c424f6c06fb1a0ea01c`
  which includes:
  - The `enclosing_range` field definition in the protobuf schema
  - The `add_enclosing_range()` API for populating individual values

  ### 2. Extended Indexer API

  Added `clang::SourceRange enclosingRange` parameter (with default
  empty value) to:
  - `TuIndexer::saveDefinition()`
  - `TuIndexer::saveReference()`
  - `TuIndexer::saveOccurrence()`
  - `TuIndexer::saveOccurrenceImpl()`

  ### 3. Implemented Range Population

  Modified `saveOccurrenceImpl()` to populate the `enclosing_range`
  field:
  - Format: `[startLine-1, startColumn-1, [endLine-1], endColumn-1]`
  - Uses 0-based coordinates as per SCIP specification
  - Omits endLine if range is single-line
  - Only populates when valid range is provided
@jupblb

jupblb commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Hi! Thank you for the contribution. Can you please regenerate test snapshots with your changes?

pierrick-martin-dev added a commit to pierrick-martin-dev/scip-clang that referenced this pull request Mar 11, 2026
@rakiz

rakiz commented Jul 15, 2026

Copy link
Copy Markdown

Adding a concrete downstream use case in support of this PR.

I'm building a call graph for C++ from scip-clang indexes. To draw a "function A calls function B" edge, I need to know which function body each reference sits inside. scip-clang gives the line of every occurrence, but not the start/end of each function body — so the only fallback is a heuristic: attribute a reference to the nearest preceding definition.

That heuristic misattributes any reference that lives in a class body instead of a function body. The clearest case is a member's own in-class declaration, which gets counted as a "call" from the method declared just above it:

class Mutex {
    void lock() { /* ... */ }   // a definition
    void _lock();               // just a declaration — misread as a call from lock()
};

I can't disambiguate this consumer-side: an in-class declaration void _lock(); and a genuine inline call _lock() are identical occurrences in SCIP (same role, no kind/syntax_kind, and definition ranges cover only the identifier). Any rule I write to drop the false declaration edge also drops real inline calls. enclosing_range is the only thing that tells them apart.

So this PR would enable correct C++ call graphs, beyond the IDE features listed. #323 was closed as not planned, but for code-intelligence consumers this field is really the line between an approximate and an exact call graph. Thanks for picking it up.

rakiz added a commit to rakiz/cppgraph that referenced this pull request Jul 15, 2026
…ng limit

Investigated the nearest-preceding-definition misattribution: proven not
refinable with scip-clang v0.4.0 (in-class declaration vs genuine inline-body
call are structurally identical; every suppression rule drops 15-20% genuine
edges). Kept as safe-direction over-capture. Clean fix needs enclosing_range,
blocked on upstream PR sourcegraph/scip-clang#504.
@jupblb

jupblb commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Pinging @emidoots @peterguy for visibility

Comment thread indexer/Indexer.cc
Comment on lines +1238 to +1239
if (enclosingRange.isValid() && begin.isValid() && end.isValid()
&& begin != end && begin < end) {

@rakiz rakiz Jul 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Testing this PR, workers crash on many TUs with:

Indexer.cc: enforced condition sourceManager.getFileID(end) == fileId ... range should not be split across files

saveOccurrence passes enclosingRange to FileLocalSourceRange::fromNonEmpty, which ENFORCEs both endpoints share a FileID, but an enclosing range can span a macro expansion or #include boundary, so the ENFORCE trips and kills the worker.

The crashed workers don't recover: the driver logs timeout: no workers have responded yet and terminating worker … due to worker timeout, respawns them, they crash again. On one full index this ran ~2.5h with 205 crashes and 197 worker-timeout terminations, and no .scip was ever produced (the run doesn't error out, it just never finishes).

Fix: add a same-file guard to the condition and skip enclosing_range otherwise (it's optional):

if (enclosingRange.isValid() && begin.isValid() && end.isValid()
    && begin != end && begin < end
    && sourceManager.getFileID(begin) == sourceManager.getFileID(end)) {

With this guard: 0 crashes over a full index.

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.

3 participants