Skip to content

Commit e52a6aa

Browse files
feat: Add support for classes without inheritance (#13)
NOTE: Right now, some of the test output is incorrect. Specifically, method references emitted are not properly qualified. However, it is correct with respect to classes and modules.
1 parent d4df58b commit e52a6aa

File tree

3 files changed

+237
-21
lines changed

3 files changed

+237
-21
lines changed

scip_indexer/SCIPIndexer.cc

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,9 @@ class SCIPState {
321321

322322
// Save definition when you have a sorbet Symbol.
323323
// Meant for methods, fields etc., but not local variables.
324-
absl::Status saveDefinition(const core::GlobalState &gs, core::FileRef file, core::SymbolRef symRef) {
324+
// TODO(varun): Should we always pass in the location instead of sometimes only?
325+
absl::Status saveDefinition(const core::GlobalState &gs, core::FileRef file, core::SymbolRef symRef,
326+
std::optional<core::LocOffsets> loc = std::nullopt) {
325327
// TODO:(varun) Should we cache here too to avoid emitting duplicate definitions?
326328
scip::Symbol symbol;
327329
auto status = symbolForExpr(gs, symRef, symbol);
@@ -333,11 +335,19 @@ class SCIPState {
333335
return valueOrStatus.status();
334336
}
335337
string &symbolString = *valueOrStatus.value();
336-
auto occLocStatus = occurrenceLoc(gs, symRef);
337-
if (!occLocStatus.ok()) {
338-
return occLocStatus.status();
338+
339+
core::Loc occLoc;
340+
if (loc.has_value()) {
341+
occLoc = core::Loc(file, loc.value());
342+
} else {
343+
auto occLocStatus = occurrenceLoc(gs, symRef);
344+
if (!occLocStatus.ok()) {
345+
return occLocStatus.status();
346+
}
347+
occLoc = occLocStatus.value();
339348
}
340-
return this->saveDefinitionImpl(gs, file, symbolString, occLocStatus.value());
349+
350+
return this->saveDefinitionImpl(gs, file, symbolString, occLoc);
341351
}
342352

343353
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
404414
return lookupRecursive(gs, owner.enclosingClass(gs), name);
405415
}
406416

417+
std::string format_ancestry(const core::GlobalState &gs, core::SymbolRef sym) {
418+
UnorderedSet<core::SymbolRef> visited;
419+
auto i = 0;
420+
std::ostringstream out;
421+
while (sym.exists() && !visited.contains(sym)) {
422+
out << fmt::format("#{}{}{}\n", std::string(i * 2, ' '), i == 0 ? "" : "<- ", sym.name(gs).toString(gs));
423+
visited.insert(sym);
424+
sym = sym.owner(gs);
425+
i++;
426+
}
427+
return out.str();
428+
}
429+
407430
class CFGTraversal final {
408431
// A map from each basic block to the locals in it.
409432
//
@@ -540,28 +563,14 @@ class CFGTraversal final {
540563
void traverse(const cfg::CFG &cfg) {
541564
auto &gs = this->ctx.state;
542565
auto method = this->ctx.owner;
566+
auto isMethodFileStaticInit = method == gs.lookupStaticInitForFile(this->ctx.file);
543567

544568
// I don't fully understand the doc comment for forwardsTopoSort; it seems backwards in practice.
545569
for (auto it = cfg.forwardsTopoSort.rbegin(); it != cfg.forwardsTopoSort.rend(); ++it) {
546570
cfg::BasicBlock *bb = *it;
547571
print_dbg("# Looking at block id: {} ptr: {}\n", bb->id, (void *)bb);
548572
this->copyLocalsFromParents(bb, cfg);
549573
for (auto &binding : bb->exprs) {
550-
if (auto *aliasInstr = cfg::cast_instruction<cfg::Alias>(binding.value)) {
551-
auto aliasName = aliasInstr->name;
552-
if (!aliasName.exists()) {
553-
// TODO(varun): When does this happen?
554-
continue;
555-
}
556-
auto aliasedSym = lookupRecursive(gs, method, aliasName);
557-
if (!aliasedSym.exists()) {
558-
print_err("# lookup for symbol {} failed starting from {}\n", aliasName.shortName(gs),
559-
method.toString(gs));
560-
continue;
561-
}
562-
this->addLocal(bb, binding.bind.variable);
563-
continue;
564-
}
565574
if (!binding.loc.exists() || binding.loc.empty()) { // TODO(varun): When can each case happen?
566575
continue;
567576
}
@@ -627,7 +636,37 @@ class CFGTraversal final {
627636
break;
628637
}
629638
case cfg::Tag::Alias: {
630-
ENFORCE(false, "already handled earlier");
639+
auto alias = cfg::cast_instruction<cfg::Alias>(binding.value);
640+
auto aliasedSym = alias->what;
641+
if (!aliasedSym.exists()) {
642+
if (!alias->name.exists()) {
643+
print_dbg("# alias name doesn't exist @ {}, what = {}\n",
644+
core::Loc(this->ctx.file, binding.loc).showRaw(gs), alias->what.showRaw(gs));
645+
break;
646+
}
647+
print_dbg("# missing symbol for RHS {}\n", alias->name.shortName(gs));
648+
break;
649+
} else if (aliasedSym == core::Symbols::Magic()) {
650+
break;
651+
}
652+
absl::Status status;
653+
auto loc = binding.loc;
654+
auto source = this->ctx.locAt(binding.loc).source(gs);
655+
if (source.has_value() && source.value().find("::"sv) != std::string::npos) {
656+
loc.beginLoc = binding.loc.endPos() -
657+
static_cast<uint32_t>(aliasedSym.name(gs).shortName(gs).length());
658+
}
659+
if (aliasedSym.isClassOrModule() &&
660+
(isMethodFileStaticInit ||
661+
method == gs.lookupStaticInitForClass(aliasedSym.asClassOrModuleRef().data(gs)->owner))) {
662+
status = this->scipState.saveDefinition(gs, this->ctx.file, aliasedSym, loc);
663+
} else {
664+
// When we have code like MyModule::MyClass, the source location in binding.loc corresponds
665+
// to 'MyModule::MyClass', whereas we want a range for 'MyClass'. So we cut off the prefix.
666+
status = this->scipState.saveReference(gs, this->ctx.file, aliasedSym, loc, 0);
667+
}
668+
ENFORCE(status.ok());
669+
this->addLocal(bb, binding.bind.variable);
631670
break;
632671
}
633672
case cfg::Tag::Return: {

test/scip/testdata/classes.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# typed: true
2+
3+
_ = 0
4+
5+
class C1
6+
def f()
7+
_a = C1.new
8+
_b = M2::C2.new
9+
return
10+
end
11+
end
12+
13+
module M2
14+
class C2
15+
end
16+
end
17+
18+
class M3::C3
19+
end
20+
21+
def local_class()
22+
localClass = Class.new
23+
# Technically, this is not supported by Sorbet (https://srb.help/3001),
24+
# but make sure we don't crash or do something weird.
25+
def localClass.myMethod()
26+
":)"
27+
end
28+
_c = localClass.new
29+
_m = localClass.myMethod
30+
return
31+
end
32+
33+
module M4
34+
K = 0
35+
end
36+
37+
def module_access()
38+
_ = M4::K
39+
return
40+
end
41+
42+
module M5
43+
module M6
44+
def self.g()
45+
end
46+
end
47+
48+
def self.h()
49+
M6.g()
50+
return
51+
end
52+
end
53+
54+
class C7
55+
module M8
56+
def self.i()
57+
end
58+
end
59+
60+
def j()
61+
M8.j()
62+
return
63+
end
64+
end
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# typed: true
2+
3+
_ = 0
4+
#^ definition local 1~#119448696
5+
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
6+
7+
class C1
8+
#^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
9+
# ^^ definition scip-ruby gem TODO TODO C1#
10+
def f()
11+
# ^^^^^^^ definition scip-ruby gem TODO TODO f().
12+
_a = C1.new
13+
# ^^ definition local 2~#3809224601
14+
# ^^ reference scip-ruby gem TODO TODO C1#
15+
_b = M2::C2.new
16+
# ^^ definition local 5~#3809224601
17+
# ^^ reference scip-ruby gem TODO TODO M2#
18+
# ^^ reference scip-ruby gem TODO TODO C2#
19+
return
20+
end
21+
end
22+
23+
module M2
24+
#^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
25+
# ^^ definition scip-ruby gem TODO TODO M2#
26+
class C2
27+
# ^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
28+
# ^^ definition scip-ruby gem TODO TODO C2#
29+
end
30+
end
31+
32+
class M3::C3
33+
#^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
34+
# ^^ definition scip-ruby gem TODO TODO M3#
35+
# ^^ definition scip-ruby gem TODO TODO C3#
36+
end
37+
38+
def local_class()
39+
#^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO local_class().
40+
localClass = Class.new
41+
# ^^^^^^^^^^ definition local 2~#552113551
42+
# ^^^^^ reference scip-ruby gem TODO TODO Class#
43+
# Technically, this is not supported by Sorbet (https://srb.help/3001),
44+
# but make sure we don't crash or do something weird.
45+
def localClass.myMethod()
46+
# ^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO myMethod().
47+
":)"
48+
end
49+
_c = localClass.new
50+
# ^^ definition local 3~#552113551
51+
# ^^^^^^^^^^ reference local 2~#552113551
52+
_m = localClass.myMethod
53+
# ^^ definition local 4~#552113551
54+
# ^^^^^^^^^^ reference local 2~#552113551
55+
# ^^^^^^^^ reference scip-ruby gem TODO TODO myMethod().
56+
return
57+
end
58+
59+
module M4
60+
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
61+
# ^^ definition scip-ruby gem TODO TODO M4#
62+
K = 0
63+
# ^ definition local 1~#119448696
64+
# ^^^^^ reference local 1~#119448696
65+
end
66+
67+
def module_access()
68+
#^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO module_access().
69+
_ = M4::K
70+
# ^ definition local 2~#3353511840
71+
# ^^ reference scip-ruby gem TODO TODO M4#
72+
# ^ reference scip-ruby gem TODO TODO K.
73+
return
74+
end
75+
76+
module M5
77+
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
78+
# ^^ definition scip-ruby gem TODO TODO M5#
79+
module M6
80+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
81+
# ^^ definition scip-ruby gem TODO TODO M6#
82+
def self.g()
83+
# ^^^^^^^^^^^^ definition scip-ruby gem TODO TODO g().
84+
end
85+
end
86+
87+
def self.h()
88+
# ^^^^^^^^^^^^ definition scip-ruby gem TODO TODO h().
89+
M6.g()
90+
# ^^ reference scip-ruby gem TODO TODO M6#
91+
return
92+
end
93+
end
94+
95+
class C7
96+
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
97+
# ^^ definition scip-ruby gem TODO TODO C7#
98+
module M8
99+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-ruby gem TODO TODO <static-init>().
100+
# ^^ definition scip-ruby gem TODO TODO M8#
101+
def self.i()
102+
# ^^^^^^^^^^^^ definition scip-ruby gem TODO TODO i().
103+
end
104+
end
105+
106+
def j()
107+
# ^^^^^^^ definition scip-ruby gem TODO TODO j().
108+
M8.j()
109+
# ^^ reference scip-ruby gem TODO TODO M8#
110+
# ^ reference scip-ruby gem TODO TODO j().
111+
return
112+
end
113+
end

0 commit comments

Comments
 (0)