Skip to content

feat: Add support for classes without inheritance #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 12, 2022
81 changes: 60 additions & 21 deletions scip_indexer/SCIPIndexer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<core::LocOffsets> loc = std::nullopt) {
// TODO:(varun) Should we cache here too to avoid emitting duplicate definitions?
scip::Symbol symbol;
auto status = symbolForExpr(gs, symRef, symbol);
Expand All @@ -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) {
Expand Down Expand Up @@ -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<core::SymbolRef> 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.
//
Expand Down Expand Up @@ -540,28 +563,14 @@ 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) {
cfg::BasicBlock *bb = *it;
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<cfg::Alias>(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;
}
Expand Down Expand Up @@ -627,7 +636,37 @@ class CFGTraversal final {
break;
}
case cfg::Tag::Alias: {
ENFORCE(false, "already handled earlier");
auto alias = cfg::cast_instruction<cfg::Alias>(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<uint32_t>(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: {
Expand Down
64 changes: 64 additions & 0 deletions test/scip/testdata/classes.rb
Original file line number Diff line number Diff line change
@@ -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
113 changes: 113 additions & 0 deletions test/scip/testdata/classes.snapshot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# typed: true

_ = 0
#^ definition local 1~#119448696
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().

class C1
#^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
# ^^ 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 <static-init>().
# ^^ definition scip-ruby gem TODO TODO M2#
class C2
# ^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
# ^^ definition scip-ruby gem TODO TODO C2#
end
end

class M3::C3
#^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
# ^^ 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 <static-init>().
# ^^ 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 <static-init>().
# ^^ definition scip-ruby gem TODO TODO M5#
module M6
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
# ^^ 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 <static-init>().
# ^^ definition scip-ruby gem TODO TODO C7#
module M8
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
# ^^ 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