diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index 7d746638e..a075d3a0c 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -119,7 +119,12 @@ InlinedVector fromSorbetLoc(const core::GlobalState &gs, core::Loc l } core::Loc trimColonColonPrefix(const core::GlobalState &gs, core::Loc baseLoc) { - ENFORCE(!baseLoc.empty()); + // Sorbet can assign empty locations to synthesized AST nodes. There is no + // source prefix to trim in that case, and callers will skip emitting an + // occurrence because SCIP ranges must be non-empty. + if (!baseLoc.exists() || baseLoc.empty()) { + return baseLoc; + } auto source = baseLoc.source(gs); if (!source.has_value()) { return baseLoc; @@ -291,6 +296,9 @@ class SCIPState { const SmallVec &rels, optional enclosingLoc = nullopt) { ENFORCE(!symbolString.empty()); + if (!occLoc.exists() || occLoc.empty()) { + return absl::OkStatus(); + } occLoc = trimColonColonPrefix(gs, occLoc); auto range = sorbet::scip_indexer::fromSorbetLoc(gs, occLoc); if (range.size() == 4) { @@ -328,7 +336,11 @@ class SCIPState { void saveReferenceImpl(const core::GlobalState &gs, core::FileRef file, const string &symbolString, const SmallVec &overrideDocs, core::LocOffsets occLocOffsets, int32_t symbol_roles) { ENFORCE(!symbolString.empty()); - auto occLoc = trimColonColonPrefix(gs, core::Loc(file, occLocOffsets)); + auto occLoc = core::Loc(file, occLocOffsets); + if (!occLoc.exists() || occLoc.empty()) { + return; + } + occLoc = trimColonColonPrefix(gs, occLoc); scip::Occurrence occurrence; occurrence.set_symbol(symbolString); occurrence.set_symbol_roles(symbol_roles); diff --git a/test/scip/testdata/minitest_3.rb b/test/scip/testdata/minitest_3.rb index cd009cd97..b43821475 100644 --- a/test/scip/testdata/minitest_3.rb +++ b/test/scip/testdata/minitest_3.rb @@ -11,6 +11,10 @@ def self.describe(name, &blk); end test_each([[1,2], [3,4]]) do |(a,b)| describe "d" do + # `before` inside `test_each` has a synthesized method name with an empty + # source location. The indexer should skip that definition occurrence. + before do + end it "b" do T.reveal_type(a) # error: Revealed type: `Integer` end diff --git a/test/scip/testdata/minitest_3.snapshot.rb b/test/scip/testdata/minitest_3.snapshot.rb index a26e444ba..f496fb82a 100644 --- a/test/scip/testdata/minitest_3.snapshot.rb +++ b/test/scip/testdata/minitest_3.snapshot.rb @@ -31,6 +31,10 @@ def self.describe(name, &blk); end # ^ definition local 2$416088458 describe "d" do + # `before` inside `test_each` has a synthesized method name with an empty + # source location. The indexer should skip that definition occurrence. + before do + end # ⌄ enclosing_range_start [..] Test#``(). it "b" do # ^^^ definition [..] Test#``().