diff --git a/scip_indexer/SCIPIndexer.cc b/scip_indexer/SCIPIndexer.cc index c808867f35..06e294b2a5 100644 --- a/scip_indexer/SCIPIndexer.cc +++ b/scip_indexer/SCIPIndexer.cc @@ -321,7 +321,9 @@ class SCIPState { // Save definition when you have a sorbet Symbol. // Meant for methods, fields etc., but not local variables. - absl::Status saveDefinition(const core::GlobalState &gs, core::FileRef file, core::SymbolRef symRef) { + // TODO(varun): Should we always pass in the location instead of sometimes only? + absl::Status saveDefinition(const core::GlobalState &gs, core::FileRef file, core::SymbolRef symRef, + std::optional loc = std::nullopt) { // TODO:(varun) Should we cache here too to avoid emitting duplicate definitions? scip::Symbol symbol; auto status = symbolForExpr(gs, symRef, symbol); @@ -333,11 +335,19 @@ class SCIPState { return valueOrStatus.status(); } string &symbolString = *valueOrStatus.value(); - auto occLocStatus = occurrenceLoc(gs, symRef); - if (!occLocStatus.ok()) { - return occLocStatus.status(); + + core::Loc occLoc; + if (loc.has_value()) { + occLoc = core::Loc(file, loc.value()); + } else { + auto occLocStatus = occurrenceLoc(gs, symRef); + if (!occLocStatus.ok()) { + return occLocStatus.status(); + } + occLoc = occLocStatus.value(); } - return this->saveDefinitionImpl(gs, file, symbolString, occLocStatus.value()); + + return this->saveDefinitionImpl(gs, file, symbolString, occLoc); } absl::Status saveReference(const core::GlobalState &gs, core::FileRef file, OwnedLocal occ, int32_t symbol_roles) { @@ -404,6 +414,19 @@ core::SymbolRef lookupRecursive(const core::GlobalState &gs, const core::SymbolR return lookupRecursive(gs, owner.enclosingClass(gs), name); } +std::string format_ancestry(const core::GlobalState &gs, core::SymbolRef sym) { + UnorderedSet visited; + auto i = 0; + std::ostringstream out; + while (sym.exists() && !visited.contains(sym)) { + out << fmt::format("#{}{}{}\n", std::string(i * 2, ' '), i == 0 ? "" : "<- ", sym.name(gs).toString(gs)); + visited.insert(sym); + sym = sym.owner(gs); + i++; + } + return out.str(); +} + class CFGTraversal final { // A map from each basic block to the locals in it. // @@ -540,6 +563,7 @@ class CFGTraversal final { void traverse(const cfg::CFG &cfg) { auto &gs = this->ctx.state; auto method = this->ctx.owner; + auto isMethodFileStaticInit = method == gs.lookupStaticInitForFile(this->ctx.file); // I don't fully understand the doc comment for forwardsTopoSort; it seems backwards in practice. for (auto it = cfg.forwardsTopoSort.rbegin(); it != cfg.forwardsTopoSort.rend(); ++it) { @@ -547,21 +571,6 @@ class CFGTraversal final { print_dbg("# Looking at block id: {} ptr: {}\n", bb->id, (void *)bb); this->copyLocalsFromParents(bb, cfg); for (auto &binding : bb->exprs) { - if (auto *aliasInstr = cfg::cast_instruction(binding.value)) { - auto aliasName = aliasInstr->name; - if (!aliasName.exists()) { - // TODO(varun): When does this happen? - continue; - } - auto aliasedSym = lookupRecursive(gs, method, aliasName); - if (!aliasedSym.exists()) { - print_err("# lookup for symbol {} failed starting from {}\n", aliasName.shortName(gs), - method.toString(gs)); - continue; - } - this->addLocal(bb, binding.bind.variable); - continue; - } if (!binding.loc.exists() || binding.loc.empty()) { // TODO(varun): When can each case happen? continue; } @@ -627,7 +636,37 @@ class CFGTraversal final { break; } case cfg::Tag::Alias: { - ENFORCE(false, "already handled earlier"); + auto alias = cfg::cast_instruction(binding.value); + auto aliasedSym = alias->what; + if (!aliasedSym.exists()) { + if (!alias->name.exists()) { + print_dbg("# alias name doesn't exist @ {}, what = {}\n", + core::Loc(this->ctx.file, binding.loc).showRaw(gs), alias->what.showRaw(gs)); + break; + } + print_dbg("# missing symbol for RHS {}\n", alias->name.shortName(gs)); + break; + } else if (aliasedSym == core::Symbols::Magic()) { + break; + } + absl::Status status; + auto loc = binding.loc; + auto source = this->ctx.locAt(binding.loc).source(gs); + if (source.has_value() && source.value().find("::"sv) != std::string::npos) { + loc.beginLoc = binding.loc.endPos() - + static_cast(aliasedSym.name(gs).shortName(gs).length()); + } + if (aliasedSym.isClassOrModule() && + (isMethodFileStaticInit || + method == gs.lookupStaticInitForClass(aliasedSym.asClassOrModuleRef().data(gs)->owner))) { + status = this->scipState.saveDefinition(gs, this->ctx.file, aliasedSym, loc); + } else { + // When we have code like MyModule::MyClass, the source location in binding.loc corresponds + // to 'MyModule::MyClass', whereas we want a range for 'MyClass'. So we cut off the prefix. + status = this->scipState.saveReference(gs, this->ctx.file, aliasedSym, loc, 0); + } + ENFORCE(status.ok()); + this->addLocal(bb, binding.bind.variable); break; } case cfg::Tag::Return: { diff --git a/test/scip/testdata/classes.rb b/test/scip/testdata/classes.rb new file mode 100644 index 0000000000..7de2159ce7 --- /dev/null +++ b/test/scip/testdata/classes.rb @@ -0,0 +1,64 @@ +# typed: true + +_ = 0 + +class C1 + def f() + _a = C1.new + _b = M2::C2.new + return + end +end + +module M2 + class C2 + end +end + +class M3::C3 +end + +def local_class() + localClass = Class.new + # Technically, this is not supported by Sorbet (https://srb.help/3001), + # but make sure we don't crash or do something weird. + def localClass.myMethod() + ":)" + end + _c = localClass.new + _m = localClass.myMethod + return +end + +module M4 + K = 0 +end + +def module_access() + _ = M4::K + return +end + +module M5 + module M6 + def self.g() + end + end + + def self.h() + M6.g() + return + end +end + +class C7 + module M8 + def self.i() + end + end + + def j() + M8.j() + return + end +end diff --git a/test/scip/testdata/classes.snapshot.rb b/test/scip/testdata/classes.snapshot.rb new file mode 100644 index 0000000000..198126dc04 --- /dev/null +++ b/test/scip/testdata/classes.snapshot.rb @@ -0,0 +1,113 @@ + # typed: true + + _ = 0 +#^ definition local 1~#119448696 +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO (). + + class C1 +#^^^^^^^^^^ definition scip-ruby gem TODO TODO (). +# ^^ definition scip-ruby gem TODO TODO C1# + def f() +# ^^^^^^^ definition scip-ruby gem TODO TODO f(). + _a = C1.new +# ^^ definition local 2~#3809224601 +# ^^ reference scip-ruby gem TODO TODO C1# + _b = M2::C2.new +# ^^ definition local 5~#3809224601 +# ^^ reference scip-ruby gem TODO TODO M2# +# ^^ reference scip-ruby gem TODO TODO C2# + return + end + end + + module M2 +#^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO (). +# ^^ definition scip-ruby gem TODO TODO M2# + class C2 +# ^^^^^^^^^^^^ definition scip-ruby gem TODO TODO (). +# ^^ definition scip-ruby gem TODO TODO C2# + end + end + + class M3::C3 +#^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO (). +# ^^ definition scip-ruby gem TODO TODO M3# +# ^^ definition scip-ruby gem TODO TODO C3# + end + + def local_class() +#^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO local_class(). + localClass = Class.new +# ^^^^^^^^^^ definition local 2~#552113551 +# ^^^^^ reference scip-ruby gem TODO TODO Class# + # Technically, this is not supported by Sorbet (https://srb.help/3001), + # but make sure we don't crash or do something weird. + def localClass.myMethod() +# ^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO myMethod(). + ":)" + end + _c = localClass.new +# ^^ definition local 3~#552113551 +# ^^^^^^^^^^ reference local 2~#552113551 + _m = localClass.myMethod +# ^^ definition local 4~#552113551 +# ^^^^^^^^^^ reference local 2~#552113551 +# ^^^^^^^^ reference scip-ruby gem TODO TODO myMethod(). + return + end + + module M4 +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO (). +# ^^ definition scip-ruby gem TODO TODO M4# + K = 0 +# ^ definition local 1~#119448696 +# ^^^^^ reference local 1~#119448696 + end + + def module_access() +#^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO module_access(). + _ = M4::K +# ^ definition local 2~#3353511840 +# ^^ reference scip-ruby gem TODO TODO M4# +# ^ reference scip-ruby gem TODO TODO K. + return + end + + module M5 +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO (). +# ^^ definition scip-ruby gem TODO TODO M5# + module M6 +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO (). +# ^^ definition scip-ruby gem TODO TODO M6# + def self.g() +# ^^^^^^^^^^^^ definition scip-ruby gem TODO TODO g(). + end + end + + def self.h() +# ^^^^^^^^^^^^ definition scip-ruby gem TODO TODO h(). + M6.g() +# ^^ reference scip-ruby gem TODO TODO M6# + return + end + end + + class C7 +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO (). +# ^^ definition scip-ruby gem TODO TODO C7# + module M8 +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO (). +# ^^ definition scip-ruby gem TODO TODO M8# + def self.i() +# ^^^^^^^^^^^^ definition scip-ruby gem TODO TODO i(). + end + end + + def j() +# ^^^^^^^ definition scip-ruby gem TODO TODO j(). + M8.j() +# ^^ reference scip-ruby gem TODO TODO M8# +# ^ reference scip-ruby gem TODO TODO j(). + return + end + end