Skip to content

Commit e4f99dd

Browse files
WIP: Add support for fields in mixins.
1 parent 661e9be commit e4f99dd

File tree

6 files changed

+1400
-109
lines changed

6 files changed

+1400
-109
lines changed

scip_indexer/SCIPFieldResolve.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include <vector>
22

3+
#include "absl/strings/ascii.h"
4+
35
#include "common/common.h"
46
#include "core/GlobalState.h"
57
#include "core/SymbolRef.h"
@@ -17,7 +19,7 @@ string FieldQueryResult::Data::showRaw(const core::GlobalState &gs) const {
1719
case Kind::FromDeclared:
1820
return this->originalSymbol().showRaw(gs);
1921
case Kind::FromUndeclared:
20-
return fmt::format("In({})", this->originalClass().showFullName(gs));
22+
return fmt::format("In({})", absl::StripAsciiWhitespace(this->originalClass().showFullName(gs)));
2123
}
2224
}
2325

scip_indexer/SCIPIndexer.cc

Lines changed: 65 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "sorbet_version/sorbet_version.h"
3434

3535
#include "scip_indexer/Debug.h"
36+
#include "scip_indexer/SCIPFieldResolve.h"
3637
#include "scip_indexer/SCIPSymbolRef.h"
3738
#include "scip_indexer/SCIPUtils.h"
3839

@@ -173,6 +174,11 @@ class SCIPState {
173174
UnorderedSet<pair<core::FileRef, string>> emittedSymbols;
174175
UnorderedMap<core::FileRef, vector<scip::SymbolInformation>> symbolMap;
175176

177+
// Stores the relationships that apply to a field or a method.
178+
RelationshipsMap relationshipsMap;
179+
180+
FieldResolver fieldResolver;
181+
176182
vector<scip::Document> documents;
177183
vector<scip::SymbolInformation> externalSymbols;
178184

@@ -222,7 +228,8 @@ class SCIPState {
222228
}
223229

224230
private:
225-
Emitted saveSymbolInfo(core::FileRef file, const string &symbolString, const SmallVec<string> &docs) {
231+
Emitted saveSymbolInfo(core::FileRef file, const string &symbolString, const SmallVec<string> &docs,
232+
const SmallVec<scip::Relationship> &rels) {
226233
if (this->emittedSymbols.contains({file, symbolString})) {
227234
return Emitted::Earlier;
228235
}
@@ -231,15 +238,19 @@ class SCIPState {
231238
for (auto &doc : docs) {
232239
symbolInfo.add_documentation(doc);
233240
}
241+
for (auto &rel : rels) {
242+
*symbolInfo.add_relationships() = rel;
243+
}
234244
this->symbolMap[file].push_back(symbolInfo);
235245
return Emitted::Now;
236246
}
237247

238248
absl::Status saveDefinitionImpl(const core::GlobalState &gs, core::FileRef file, const string &symbolString,
239-
core::Loc occLoc, const SmallVec<string> &docs) {
249+
core::Loc occLoc, const SmallVec<string> &docs,
250+
const SmallVec<scip::Relationship> &rels) {
240251
ENFORCE(!symbolString.empty());
241252

242-
auto emitted = this->saveSymbolInfo(file, symbolString, docs);
253+
auto emitted = this->saveSymbolInfo(file, symbolString, docs, rels);
243254

244255
occLoc = trimColonColonPrefix(gs, occLoc);
245256
scip::Occurrence occurrence;
@@ -336,7 +347,7 @@ class SCIPState {
336347
ENFORCE(var.has_value(), "Failed to find source text for definition of local variable");
337348
docStrings.push_back(fmt::format("```ruby\n{} ({})\n```", var.value(), type.show(gs)));
338349
}
339-
return this->saveDefinitionImpl(gs, file, occ.toSCIPString(gs, file), loc, docStrings);
350+
return this->saveDefinitionImpl(gs, file, occ.toSCIPString(gs, file), loc, docStrings, {});
340351
}
341352

342353
// Save definition when you have a sorbet Symbol.
@@ -349,19 +360,29 @@ class SCIPState {
349360

350361
auto occLoc = loc.has_value() ? core::Loc(file, loc.value()) : symRef.symbolLoc(gs);
351362
scip::Symbol symbol;
352-
auto status = symRef.withoutType().symbolForExpr(gs, this->gemMetadata, occLoc, symbol);
363+
auto untypedSymRef = symRef.withoutType();
364+
auto status = untypedSymRef.symbolForExpr(gs, this->gemMetadata, occLoc, symbol);
353365
if (!status.ok()) {
354366
return status;
355367
}
356-
absl::StatusOr<const string *> valueOrStatus(this->saveSymbolString(gs, symRef.withoutType(), &symbol));
368+
absl::StatusOr<const string *> valueOrStatus(this->saveSymbolString(gs, untypedSymRef, &symbol));
357369
if (!valueOrStatus.ok()) {
358370
return valueOrStatus.status();
359371
}
360372
const string &symbolString = *valueOrStatus.value();
361373

362374
SmallVec<string> docs;
363375
symRef.saveDocStrings(gs, symRef.definitionType(), occLoc, docs);
364-
return this->saveDefinitionImpl(gs, file, symbolString, occLoc, docs);
376+
377+
SmallVec<scip::Relationship> rels;
378+
untypedSymRef.saveRelationships(this->relationshipsMap, rels,
379+
[this, &gs](UntypedGenericSymbolRef sym, std::string &out) {
380+
auto status = this->saveSymbolString(gs, sym, nullptr);
381+
ENFORCE(status.ok());
382+
out = *status.value();
383+
});
384+
385+
return this->saveDefinitionImpl(gs, file, symbolString, occLoc, docs, rels);
365386
}
366387

367388
absl::Status saveReference(const core::GlobalState &gs, core::FileRef file, OwnedLocal occ,
@@ -457,63 +478,6 @@ string format_ancestry(const core::GlobalState &gs, core::SymbolRef sym) {
457478
return out.str();
458479
}
459480

460-
static absl::variant</*owner*/ core::ClassOrModuleRef, core::SymbolRef>
461-
findUnresolvedFieldTransitive(const core::GlobalState &gs, core::Loc loc, core::ClassOrModuleRef start,
462-
core::NameRef field) {
463-
auto fieldText = field.shortName(gs);
464-
auto isInstanceVar = fieldText.size() >= 2 && fieldText[0] == '@' && fieldText[1] != '@';
465-
auto isClassInstanceVar = isInstanceVar && start.data(gs)->isSingletonClass(gs);
466-
// Class instance variables are not inherited, unlike ordinary instance
467-
// variables or class variables.
468-
if (isClassInstanceVar) {
469-
return start;
470-
}
471-
auto isClassVar = fieldText.size() >= 2 && fieldText[0] == '@' && fieldText[1] == '@';
472-
if (isClassVar && !start.data(gs)->isSingletonClass(gs)) {
473-
// Triggered when undeclared class variables are accessed from instance methods.
474-
start = start.data(gs)->lookupSingletonClass(gs);
475-
}
476-
477-
// TODO(varun): Should we add a cache here? It seems wasteful to redo
478-
// work for every occurrence.
479-
if (gs.unresolvedFields.find(start) == gs.unresolvedFields.end() ||
480-
!gs.unresolvedFields.find(start)->second.contains(field)) {
481-
// Triggered by code patterns like:
482-
// # top-level
483-
// def MyClass.method
484-
// # blah
485-
// end
486-
// which is not supported by Sorbet.
487-
LOG_DEBUG(gs, loc,
488-
fmt::format("couldn't find field {} in class {};\n"
489-
"are you using a code pattern like def MyClass.method which is unsupported by Sorbet?",
490-
field.exists() ? field.toString(gs) : "<non-existent>",
491-
start.exists() ? start.showFullName(gs) : "<non-existent>"));
492-
// As a best-effort guess, assume that the definition is
493-
// in this class but we somehow missed it.
494-
return start;
495-
}
496-
497-
auto best = start;
498-
auto cur = start;
499-
while (cur.exists()) {
500-
auto klass = cur.data(gs);
501-
auto sym = klass->findMember(gs, field);
502-
if (sym.exists()) {
503-
return sym;
504-
}
505-
auto it = gs.unresolvedFields.find(cur);
506-
if (it != gs.unresolvedFields.end() && it->second.contains(field)) {
507-
best = cur;
508-
}
509-
if (cur == klass->superClass()) { // FIXME(varun): Handle mix-ins
510-
break;
511-
}
512-
cur = klass->superClass();
513-
}
514-
return best;
515-
}
516-
517481
// Loosely inspired by AliasesAndKeywords in IREmitterContext.cc
518482
class AliasMap final {
519483
public:
@@ -528,7 +492,8 @@ class AliasMap final {
528492
public:
529493
AliasMap() = default;
530494

531-
void populate(const core::Context &ctx, const cfg::CFG &cfg) {
495+
void populate(const core::Context &ctx, const cfg::CFG &cfg, FieldResolver &fieldResolver,
496+
RelationshipsMap &relMap) {
532497
this->map = {};
533498
auto &gs = ctx.state;
534499
auto method = ctx.owner;
@@ -553,25 +518,40 @@ class AliasMap final {
553518
if (sym == core::Symbols::Magic_undeclaredFieldStub()) {
554519
ENFORCE(!bind.loc.empty());
555520
ENFORCE(klass.isClassOrModule());
556-
auto result = findUnresolvedFieldTransitive(ctx, ctx.locAt(bind.loc), klass.asClassOrModuleRef(),
557-
instr->name);
558-
if (absl::holds_alternative<core::ClassOrModuleRef>(result)) {
559-
auto klass = absl::get<core::ClassOrModuleRef>(result);
560-
if (klass.exists()) {
561-
this->map.insert( // no trim(...) because undeclared fields shouldn't have ::
562-
{bind.bind.variable,
563-
{GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type), bind.loc,
564-
false}});
521+
auto [result, cacheHit] = fieldResolver.findUnresolvedFieldTransitive(
522+
ctx, ctx.locAt(bind.loc), {klass.asClassOrModuleRef(), instr->name});
523+
auto checkExists = [&](bool exists, const std::string &text) {
524+
ENFORCE(exists,
525+
"Returned non-existent {} from findUnresolvedFieldTransitive with start={}, "
526+
"field={}, file={}, loc={}",
527+
text, klass.exists() ? klass.toStringFullName(gs) : "<non-existent>",
528+
instr->name.exists() ? instr->name.toString(gs) : "<non-existent>",
529+
ctx.file.data(gs).path(), ctx.locAt(bind.loc).showRawLineColumn(gs));
530+
};
531+
switch (result.inherited.kind()) {
532+
case FieldQueryResult::Kind::FromUndeclared: {
533+
auto klass = result.inherited.originalClass();
534+
checkExists(klass.exists(), "class");
535+
auto namedSymRef = GenericSymbolRef::undeclaredField(klass, instr->name, bind.bind.type);
536+
if (!cacheHit) {
537+
// It may be the case that the mixin values are already stored because of the
538+
// traversal in some other function. In that case, don't bother overriding.
539+
relMap.insert({namedSymRef.withoutType(), result.mixedIn});
540+
}
541+
// no trim(...) because undeclared fields shouldn't have ::
542+
this->map.insert({bind.bind.variable, {namedSymRef, bind.loc, false}});
543+
break;
565544
}
566-
} else if (absl::holds_alternative<core::SymbolRef>(result)) {
567-
auto fieldSym = absl::get<core::SymbolRef>(result);
568-
if (fieldSym.exists()) {
569-
this->map.insert(
570-
{bind.bind.variable,
571-
{GenericSymbolRef::declaredField(fieldSym, bind.bind.type), trim(bind.loc), false}});
545+
case FieldQueryResult::Kind::FromDeclared: {
546+
auto fieldSym = result.inherited.originalSymbol();
547+
checkExists(fieldSym.exists(), "field");
548+
auto namedSymRef = GenericSymbolRef::declaredField(fieldSym, bind.bind.type);
549+
if (!cacheHit) {
550+
relMap.insert({namedSymRef.withoutType(), result.mixedIn});
551+
}
552+
this->map.insert({bind.bind.variable, {namedSymRef, trim(bind.loc), false}});
553+
break;
572554
}
573-
} else {
574-
ENFORCE(false, "Should've handled all cases of variant earlier");
575555
}
576556
continue;
577557
}
@@ -817,7 +797,9 @@ class CFGTraversal final {
817797

818798
public:
819799
void traverse(const cfg::CFG &cfg) {
820-
this->aliasMap.populate(this->ctx, cfg);
800+
this->aliasMap.populate(this->ctx, cfg, this->scipState.fieldResolver, this->scipState.relationshipsMap);
801+
fmt::print(stderr, "log: [traverse] relationshipsMap = {}\n",
802+
showRawRelationshipsMap(this->ctx.state, this->scipState.relationshipsMap));
821803
auto &gs = this->ctx.state;
822804
auto file = this->ctx.file;
823805
auto method = this->ctx.owner;

scip_indexer/SCIPSymbolRef.cc

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,27 @@
66
#include <vector>
77

88
#include "absl/status/status.h"
9+
#include "absl/strings/ascii.h"
910
#include "absl/strings/str_replace.h"
11+
#include "spdlog/fmt/fmt.h"
1012

1113
#include "core/Loc.h"
1214
#include "main/lsp/lsp.h"
1315

16+
#include "scip_indexer/Debug.h"
1417
#include "scip_indexer/SCIPSymbolRef.h"
1518

1619
using namespace std;
1720

1821
namespace sorbet::scip_indexer {
1922

23+
string showRawRelationshipsMap(const core::GlobalState &gs, const RelationshipsMap &relMap) {
24+
return showMap(relMap, [&gs](const UntypedGenericSymbolRef &ugsr, const auto &mixins) -> string {
25+
return fmt::format("{}: {}", ugsr.showRaw(gs),
26+
showVec(*mixins.get(), [&gs](const auto &mixin) -> string { return mixin.showRaw(gs); }));
27+
});
28+
}
29+
2030
// Try to compute a scip::Symbol for this value.
2131
absl::Status UntypedGenericSymbolRef::symbolForExpr(const core::GlobalState &gs, const GemMetadata &metadata,
2232
optional<core::Loc> loc, scip::Symbol &symbol) const {
@@ -83,9 +93,40 @@ absl::Status UntypedGenericSymbolRef::symbolForExpr(const core::GlobalState &gs,
8393

8494
string UntypedGenericSymbolRef::showRaw(const core::GlobalState &gs) const {
8595
if (this->name.exists()) {
86-
return fmt::format("UGSR(owner: {}, name: {})", this->selfOrOwner.showFullName(gs), this->name.toString(gs));
96+
return fmt::format("UGSR({}.{})", absl::StripAsciiWhitespace(this->selfOrOwner.showFullName(gs)),
97+
this->name.toString(gs));
98+
}
99+
return fmt::format("UGSR({})", absl::StripAsciiWhitespace(this->selfOrOwner.showFullName(gs)));
100+
}
101+
102+
void UntypedGenericSymbolRef::saveRelationships(
103+
const RelationshipsMap &relationshipMap, SmallVec<scip::Relationship> &rels,
104+
const absl::FunctionRef<void(UntypedGenericSymbolRef, std::string &)> &saveSymbolString) const {
105+
using SymbolKind = core::SymbolRef::Kind;
106+
if (this->selfOrOwner.kind() != SymbolKind::FieldOrStaticField) {
107+
return;
108+
}
109+
auto it = relationshipMap.find(*this);
110+
if (it == relationshipMap.end()) {
111+
return;
112+
}
113+
rels.reserve(it->second->size());
114+
for (auto data : *it->second.get()) {
115+
scip::Relationship rel;
116+
rel.set_is_reference(true);
117+
using Kind = FieldQueryResult::Kind;
118+
switch (data.kind()) {
119+
case Kind::FromUndeclared:
120+
saveSymbolString(UntypedGenericSymbolRef::undeclared(data.originalClass(), this->name),
121+
*rel.mutable_symbol());
122+
break;
123+
case Kind::FromDeclared:
124+
saveSymbolString(UntypedGenericSymbolRef::declared(data.originalSymbol()), *rel.mutable_symbol());
125+
break;
126+
}
127+
ENFORCE(!rel.symbol().empty());
128+
rels.push_back(move(rel));
87129
}
88-
return fmt::format("UGSR(symbol: {})", this->selfOrOwner.showFullName(gs));
89130
}
90131

91132
string GenericSymbolRef::showRaw(const core::GlobalState &gs) const {
@@ -199,4 +240,4 @@ core::Loc GenericSymbolRef::symbolLoc(const core::GlobalState &gs) const {
199240
}
200241
}
201242

202-
} // namespace sorbet::scip_indexer
243+
} // namespace sorbet::scip_indexer

scip_indexer/SCIPSymbolRef.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
#include "core/Symbols.h"
1616
#include "core/TypePtr.h"
1717

18+
#include "scip_indexer/SCIPFieldResolve.h"
19+
1820
namespace scip { // Avoid needlessly including protobuf header here.
1921
class Symbol;
20-
}
22+
class Relationship;
23+
} // namespace scip
2124

2225
namespace sorbet::scip_indexer {
2326

@@ -49,6 +52,12 @@ class GemMetadata final {
4952
}
5053
};
5154

55+
class UntypedGenericSymbolRef;
56+
57+
using RelationshipsMap = UnorderedMap<UntypedGenericSymbolRef, std::shared_ptr<std::vector<FieldQueryResult::Data>>>;
58+
59+
std::string showRawRelationshipsMap(const core::GlobalState &gs, const RelationshipsMap &relMap);
60+
5261
// Simplified version of GenericSymbolRef that doesn't care about types.
5362
//
5463
// Primarily for use in storing/looking up information in maps/sets,
@@ -84,6 +93,10 @@ class UntypedGenericSymbolRef final {
8493
absl::Status symbolForExpr(const core::GlobalState &gs, const GemMetadata &metadata, std::optional<core::Loc> loc,
8594
scip::Symbol &symbol) const;
8695

96+
void
97+
saveRelationships(const RelationshipsMap &relationshipMap, SmallVec<scip::Relationship> &rels,
98+
const absl::FunctionRef<void(UntypedGenericSymbolRef, std::string &)> &saveSymbolString) const;
99+
87100
std::string showRaw(const core::GlobalState &gs) const;
88101
};
89102

@@ -210,8 +223,10 @@ class GenericSymbolRef final {
210223
return this->selfOrOwner;
211224
}
212225

226+
private:
213227
static bool isSorbetInternal(const core::GlobalState &gs, core::SymbolRef sym);
214228

229+
public:
215230
bool isSorbetInternalClassOrMethod(const core::GlobalState &gs) const {
216231
switch (this->kind()) {
217232
case Kind::UndeclaredField:
@@ -231,4 +246,4 @@ class GenericSymbolRef final {
231246

232247
} // namespace sorbet::scip_indexer
233248

234-
#endif // SORBET_SCIP_SYMBOL_REF
249+
#endif // SORBET_SCIP_SYMBOL_REF

0 commit comments

Comments
 (0)